= ({
{isShowConfigElem && (configElem || null)}
{/* Chat List */}
- {chatList.map((item) => {
+ {chatList.map((item, index) => {
if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1].id
const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]')
@@ -202,7 +207,6 @@ const Chat: FC = ({
feedbackDisabled={feedbackDisabled}
isHideFeedbackEdit={isHideFeedbackEdit}
onFeedback={onFeedback}
- onSubmitAnnotation={onSubmitAnnotation}
displayScene={displayScene ?? 'web'}
isResponsing={isResponsing && isLast}
answerIcon={answerIcon}
@@ -212,6 +216,72 @@ const Chat: FC = ({
dataSets={dataSets}
isShowCitation={isShowCitation}
isShowCitationHitInfo={isShowCitationHitInfo}
+ supportAnnotation={supportAnnotation}
+ appId={appId}
+ question={chatList[index - 1]?.content}
+ onAnnotationEdited={(query, answer) => {
+ onChatListChange?.(chatList.map((item, i) => {
+ if (i === index - 1) {
+ return {
+ ...item,
+ content: query,
+ }
+ }
+ if (i === index) {
+ return {
+ ...item,
+ content: answer,
+ }
+ }
+ return item
+ }))
+ }}
+ onAnnotationAdded={(annotationId, authorName, query, answer) => {
+ onChatListChange?.(chatList.map((item, i) => {
+ if (i === index - 1) {
+ return {
+ ...item,
+ content: query,
+ }
+ }
+ if (i === index) {
+ const answerItem = {
+ ...item,
+ content: item.content,
+ annotation: {
+ id: annotationId,
+ authorName,
+ logAnnotation: {
+ content: answer,
+ account: {
+ id: '',
+ name: authorName,
+ email: '',
+ },
+ },
+ } as Annotation,
+ }
+ return answerItem
+ }
+ return item
+ }))
+ }}
+ onAnnotationRemoved={() => {
+ onChatListChange?.(chatList.map((item, i) => {
+ if (i === index) {
+ return {
+ ...item,
+ content: item.content,
+ annotation: {
+ ...(item.annotation || {}),
+ id: '',
+ } as Annotation,
+ }
+ }
+ return item
+ }))
+ }}
+
/>
}
return (
diff --git a/web/app/components/app/chat/style.module.css b/web/app/components/app/chat/style.module.css
index 65f8a326d7..46a4672625 100644
--- a/web/app/components/app/chat/style.module.css
+++ b/web/app/components/app/chat/style.module.css
@@ -38,7 +38,8 @@
background: url(./icons/answer.svg) no-repeat;
}
-.copyBtn {
+.copyBtn,
+.annotationBtn {
display: none;
}
@@ -63,10 +64,15 @@
max-width: 100%;
}
-.answerWrap:hover .copyBtn {
+.answerWrap:hover .copyBtn,
+.answerWrap:hover .annotationBtn {
display: block;
}
+.answerWrap:hover .hasAnnotationBtn {
+ display: none;
+}
+
.answerWrap .itemOperation {
display: none;
}
diff --git a/web/app/components/app/chat/type.ts b/web/app/components/app/chat/type.ts
index e848a36566..972b9424a8 100644
--- a/web/app/components/app/chat/type.ts
+++ b/web/app/components/app/chat/type.ts
@@ -81,3 +81,12 @@ export type MessageReplace = {
answer: string
conversation_id: string
}
+
+export type AnnotationReply = {
+ id: string
+ task_id: string
+ answer: string
+ conversation_id: string
+ annotation_id: string
+ annotation_author_name: string
+}
diff --git a/web/app/components/app/configuration/config/feature/choose-feature/index.tsx b/web/app/components/app/configuration/config/feature/choose-feature/index.tsx
index 55005d0d68..0a9814b73d 100644
--- a/web/app/components/app/configuration/config/feature/choose-feature/index.tsx
+++ b/web/app/components/app/configuration/config/feature/choose-feature/index.tsx
@@ -10,6 +10,7 @@ import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuratio
import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import { Citations } from '@/app/components/base/icons/src/vender/solid/editor'
import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files'
+import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
type IConfig = {
openingStatement: boolean
moreLikeThis: boolean
@@ -17,6 +18,7 @@ type IConfig = {
speechToText: boolean
citation: boolean
moderation: boolean
+ annotation: boolean
}
export type IChooseFeatureProps = {
@@ -43,7 +45,6 @@ const ChooseFeature: FC = ({
showSpeechToTextItem,
}) => {
const { t } = useTranslation()
-
return (
= ({
value={config.moderation}
onChange={value => onChange('moderation', value)}
/>
+ {isChatApp && (
+ }
+ title={t('appDebug.feature.annotation.title')}
+ description={t('appDebug.feature.annotation.description')}
+ value={config.annotation}
+ onChange={value => onChange('annotation', value)}
+ />
+ )}
>
-
)
}
diff --git a/web/app/components/app/configuration/config/feature/use-feature.tsx b/web/app/components/app/configuration/config/feature/use-feature.tsx
index 3e52b0920e..5ec0d8af02 100644
--- a/web/app/components/app/configuration/config/feature/use-feature.tsx
+++ b/web/app/components/app/configuration/config/feature/use-feature.tsx
@@ -11,6 +11,8 @@ function useFeature({
setSpeechToText,
citation,
setCitation,
+ annotation,
+ setAnnotation,
moderation,
setModeration,
}: {
@@ -24,6 +26,8 @@ function useFeature({
setSpeechToText: (speechToText: boolean) => void
citation: boolean
setCitation: (citation: boolean) => void
+ annotation: boolean
+ setAnnotation: (annotation: boolean) => void
moderation: boolean
setModeration: (moderation: boolean) => void
}) {
@@ -45,6 +49,7 @@ function useFeature({
suggestedQuestionsAfterAnswer,
speechToText,
citation,
+ annotation,
moderation,
}
const handleFeatureChange = (key: string, value: boolean) => {
@@ -67,6 +72,9 @@ function useFeature({
case 'citation':
setCitation(value)
break
+ case 'annotation':
+ setAnnotation(value)
+ break
case 'moderation':
setModeration(value)
}
diff --git a/web/app/components/app/configuration/config/index.tsx b/web/app/components/app/configuration/config/index.tsx
index 60ef9deed0..0e75d7b11a 100644
--- a/web/app/components/app/configuration/config/index.tsx
+++ b/web/app/components/app/configuration/config/index.tsx
@@ -11,6 +11,7 @@ import ExperienceEnchanceGroup from '../features/experience-enchance-group'
import Toolbox from '../toolbox'
import HistoryPanel from '../config-prompt/conversation-histroy/history-panel'
import ConfigVision from '../config-vision'
+import useAnnotationConfig from '../toolbox/annotation/use-annotation-config'
import AddFeatureBtn from './feature/add-feature-btn'
import ChooseFeature from './feature/choose-feature'
import useFeature from './feature/use-feature'
@@ -18,13 +19,16 @@ import AdvancedModeWaring from '@/app/components/app/configuration/prompt-mode/a
import ConfigContext from '@/context/debug-configuration'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import ConfigVar from '@/app/components/app/configuration/config-var'
-import type { PromptVariable } from '@/models/debug'
+import type { CitationConfig, ModelConfig, ModerationConfig, MoreLikeThisConfig, PromptVariable, SpeechToTextConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import { AppType, ModelModeType } from '@/types/app'
import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
+import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal'
+import AnnotationFullModal from '@/app/components/billing/annotation-full/modal'
const Config: FC = () => {
const {
+ appId,
mode,
isAdvancedMode,
modelModeType,
@@ -45,6 +49,8 @@ const Config: FC = () => {
setSpeechToTextConfig,
citationConfig,
setCitationConfig,
+ annotationConfig,
+ setAnnotationConfig,
moderationConfig,
setModerationConfig,
} = useContext(ConfigContext)
@@ -56,7 +62,7 @@ const Config: FC = () => {
const promptVariables = modelConfig.configs.prompt_variables
// simple mode
const handlePromptChange = (newTemplate: string, newVariables: PromptVariable[]) => {
- const newModelConfig = produce(modelConfig, (draft) => {
+ const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.configs.prompt_template = newTemplate
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables]
})
@@ -70,7 +76,7 @@ const Config: FC = () => {
const handlePromptVariablesNameChange = (newVariables: PromptVariable[]) => {
setPrevPromptConfig(modelConfig.configs)
- const newModelConfig = produce(modelConfig, (draft) => {
+ const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.configs.prompt_variables = newVariables
})
setModelConfig(newModelConfig)
@@ -85,31 +91,42 @@ const Config: FC = () => {
setIntroduction,
moreLikeThis: moreLikeThisConfig.enabled,
setMoreLikeThis: (value) => {
- setMoreLikeThisConfig(produce(moreLikeThisConfig, (draft) => {
+ setMoreLikeThisConfig(produce(moreLikeThisConfig, (draft: MoreLikeThisConfig) => {
draft.enabled = value
}))
},
suggestedQuestionsAfterAnswer: suggestedQuestionsAfterAnswerConfig.enabled,
setSuggestedQuestionsAfterAnswer: (value) => {
- setSuggestedQuestionsAfterAnswerConfig(produce(suggestedQuestionsAfterAnswerConfig, (draft) => {
+ setSuggestedQuestionsAfterAnswerConfig(produce(suggestedQuestionsAfterAnswerConfig, (draft: SuggestedQuestionsAfterAnswerConfig) => {
draft.enabled = value
}))
},
speechToText: speechToTextConfig.enabled,
setSpeechToText: (value) => {
- setSpeechToTextConfig(produce(speechToTextConfig, (draft) => {
+ setSpeechToTextConfig(produce(speechToTextConfig, (draft: SpeechToTextConfig) => {
draft.enabled = value
}))
},
citation: citationConfig.enabled,
setCitation: (value) => {
- setCitationConfig(produce(citationConfig, (draft) => {
+ setCitationConfig(produce(citationConfig, (draft: CitationConfig) => {
draft.enabled = value
}))
},
+ annotation: annotationConfig.enabled,
+ setAnnotation: async (value) => {
+ if (value) {
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ setIsShowAnnotationConfigInit(true)
+ }
+ else {
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ await handleDisableAnnotation(annotationConfig.embedding_model)
+ }
+ },
moderation: moderationConfig.enabled,
setModeration: (value) => {
- setModerationConfig(produce(moderationConfig, (draft) => {
+ setModerationConfig(produce(moderationConfig, (draft: ModerationConfig) => {
draft.enabled = value
}))
if (value && !moderationConfig.type) {
@@ -127,7 +144,7 @@ const Config: FC = () => {
},
onSaveCallback: setModerationConfig,
onCancelCallback: () => {
- setModerationConfig(produce(moderationConfig, (draft) => {
+ setModerationConfig(produce(moderationConfig, (draft: ModerationConfig) => {
draft.enabled = false
showChooseFeatureTrue()
}))
@@ -138,8 +155,22 @@ const Config: FC = () => {
},
})
+ const {
+ handleEnableAnnotation,
+ setScore,
+ handleDisableAnnotation,
+ isShowAnnotationConfigInit,
+ setIsShowAnnotationConfigInit,
+ isShowAnnotationFullModal,
+ setIsShowAnnotationFullModal,
+ } = useAnnotationConfig({
+ appId,
+ annotationConfig,
+ setAnnotationConfig,
+ })
+
const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && !!speech2textDefaultModel) || featureConfig.citation)
- const hasToolbox = false
+ const hasToolbox = moderationConfig.enabled || featureConfig.annotation
const wrapRef = useRef(null)
const wrapScroll = useScroll(wrapRef)
@@ -229,10 +260,36 @@ const Config: FC = () => {
{/* Toolbox */}
{
- moderationConfig.enabled && (
-
+ hasToolbox && (
+
)
}
+
+ {
+ setIsShowAnnotationConfigInit(false)
+ showChooseFeatureTrue()
+ }}
+ onSave={async (embeddingModel, score) => {
+ await handleEnableAnnotation(embeddingModel, score)
+ setIsShowAnnotationConfigInit(false)
+ }}
+ annotationConfig={annotationConfig}
+ />
+ {isShowAnnotationFullModal && (
+ setIsShowAnnotationFullModal(false)}
+ />
+ )}
>
)
diff --git a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx
index 54ad45aaed..0778acddf7 100644
--- a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx
+++ b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx
@@ -15,9 +15,12 @@ import IconTypeIcon from '@/app/components/app/configuration/config-var/input-ty
type Option = { name: string; value: string; type: string }
export type Props = {
+ triggerClassName?: string
+ className?: string
value: string | undefined
options: Option[]
onChange: (value: string) => void
+ notSelectedVarTip?: string | null
}
const VarItem: FC<{ item: Option }> = ({ item }) => (
@@ -31,9 +34,12 @@ const VarItem: FC<{ item: Option }> = ({ item }) => (
)
const VarPicker: FC = ({
+ triggerClassName,
+ className,
value,
options,
onChange,
+ notSelectedVarTip,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
@@ -48,9 +54,10 @@ const VarPicker: FC = ({
mainAxis: 8,
}}
>
- setOpen(v => !v)}>
+ setOpen(v => !v)}>
= ({
)
: (
- {t('appDebug.feature.dataSet.queryVariable.choosePlaceholder')}
+ {notSelectedVarTip || t('appDebug.feature.dataSet.queryVariable.choosePlaceholder')}
)}
diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx
index f6d79dc340..aa6224982e 100644
--- a/web/app/components/app/configuration/debug/index.tsx
+++ b/web/app/components/app/configuration/debug/index.tsx
@@ -27,7 +27,7 @@ import { IS_CE_EDITION } from '@/config'
import { useProviderContext } from '@/context/provider-context'
import type { Inputs } from '@/models/debug'
import { fetchFileUploadConfig } from '@/service/common'
-
+import type { Annotation as AnnotationType } from '@/models/log'
type IDebug = {
hasSetAPIKEY: boolean
onSetting: () => void
@@ -67,6 +67,7 @@ const Debug: FC = ({
datasetConfigs,
externalDataToolsConfig,
visionConfig,
+ annotationConfig,
} = useContext(ConfigContext)
const { speech2textDefaultModel } = useProviderContext()
const [chatList, setChatList, getChatList] = useGetState([])
@@ -225,6 +226,7 @@ const Debug: FC = ({
file_upload: {
image: visionConfig,
},
+ annotation_reply: annotationConfig,
}
if (isAdvancedMode) {
@@ -359,6 +361,26 @@ const Debug: FC = ({
onMessageReplace: (messageReplace) => {
responseItem.content = messageReplace.answer
},
+ onAnnotationReply: (annotationReply) => {
+ responseItem.id = annotationReply.id
+ responseItem.content = annotationReply.answer
+ responseItem.annotation = ({
+ id: annotationReply.annotation_id,
+ authorName: annotationReply.annotation_author_name,
+ } as AnnotationType)
+ 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,
+ id: annotationReply.id,
+ })
+ })
+ setChatList(newListWithAnswer)
+ },
onError() {
setResponsingFalse()
// role back placeholder answer
@@ -477,6 +499,13 @@ const Debug: FC = ({
})
}
+ const varList = modelConfig.configs.prompt_variables.map((item: any) => {
+ return {
+ label: item.key,
+ value: inputs[item.key],
+ }
+ })
+
return (
<>
@@ -531,6 +560,9 @@ const Debug: FC = ({
...visionConfig,
image_file_size_limit: fileUploadConfigResponse?.image_file_size_limit,
}}
+ supportAnnotation
+ appId={appId}
+ onChatListChange={setChatList}
/>
@@ -550,6 +582,9 @@ const Debug: FC = ({
messageId={messageId}
isError={false}
onRetry={() => { }}
+ supportAnnotation
+ appId={appId}
+ varList={varList}
/>
)}
@@ -566,7 +601,6 @@ const Debug: FC = ({
/>
)}
-
{!hasSetAPIKEY && ( )}
>
)
diff --git a/web/app/components/app/configuration/features/chat-group/index.tsx b/web/app/components/app/configuration/features/chat-group/index.tsx
index cbf3d31e72..9b61f2f082 100644
--- a/web/app/components/app/configuration/features/chat-group/index.tsx
+++ b/web/app/components/app/configuration/features/chat-group/index.tsx
@@ -8,7 +8,6 @@ import OpeningStatement from './opening-statement'
import SuggestedQuestionsAfterAnswer from './suggested-questions-after-answer'
import SpeechToText from './speech-to-text'
import Citation from './citation'
-
/*
* Include
* 1. Conversation Opener
diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx
index 9e91b2c8fa..083adb333d 100644
--- a/web/app/components/app/configuration/index.tsx
+++ b/web/app/components/app/configuration/index.tsx
@@ -15,6 +15,7 @@ import s from './style.module.css'
import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config'
import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal'
import type {
+ AnnotationReplyConfig,
CompletionParams,
DatasetConfigs,
Inputs,
@@ -41,7 +42,7 @@ import { useProviderContext } from '@/context/provider-context'
import { AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app'
import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows'
import { PromptMode } from '@/models/debug'
-import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
+import { ANNOTATION_DEFAULT, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
import I18n from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
@@ -56,6 +57,7 @@ type PublichConfig = {
const Configuration: FC = () => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
+ const [formattingChanged, setFormattingChanged] = useState(false)
const { setShowAccountSettingModal } = useModalContext()
const [hasFetchedDetail, setHasFetchedDetail] = useState(false)
const isLoading = !hasFetchedDetail
@@ -89,11 +91,25 @@ const Configuration: FC = () => {
const [citationConfig, setCitationConfig] = useState({
enabled: false,
})
+ const [annotationConfig, doSetAnnotationConfig] = useState({
+ id: '',
+ enabled: false,
+ score_threshold: ANNOTATION_DEFAULT.score_threshold,
+ embedding_model: {
+ embedding_provider_name: '',
+ embedding_model_name: '',
+ },
+ })
+ const setAnnotationConfig = (config: AnnotationReplyConfig, notSetFormatChanged?: boolean) => {
+ doSetAnnotationConfig(config)
+ if (!notSetFormatChanged)
+ setFormattingChanged(true)
+ }
+
const [moderationConfig, setModerationConfig] = useState({
enabled: false,
})
const [externalDataToolsConfig, setExternalDataToolsConfig] = useState([])
- const [formattingChanged, setFormattingChanged] = useState(false)
const [inputs, setInputs] = useState({})
const [query, setQuery] = useState('')
const [completionParams, doSetCompletionParams] = useState({
@@ -167,7 +183,7 @@ const Configuration: FC = () => {
setFormattingChanged(true)
if (data.find(item => !item.name)) { // has not loaded selected dataset
- const newSelected = produce(data, (draft) => {
+ const newSelected = produce(data, (draft: any) => {
data.forEach((item, index) => {
if (!item.name) { // not fetched database
const newItem = dataSets.find(i => i.id === item.id)
@@ -230,7 +246,7 @@ const Configuration: FC = () => {
if (hasFetchedDetail && !modelModeType) {
const mode = textGenerationModelList.find(({ model_name }) => model_name === modelConfig.model_id)?.model_mode
if (mode) {
- const newModelConfig = produce(modelConfig, (draft) => {
+ const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.mode = mode
})
setModelConfig(newModelConfig)
@@ -302,7 +318,7 @@ const Configuration: FC = () => {
await migrateToDefaultPrompt(true, ModelModeType.chat)
}
}
- const newModelConfig = produce(modelConfig, (draft) => {
+ const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.provider = provider
draft.model_id = modelId
draft.mode = modeMode
@@ -369,6 +385,9 @@ const Configuration: FC = () => {
if (modelConfig.retriever_resource)
setCitationConfig(modelConfig.retriever_resource)
+ if (modelConfig.annotation_reply)
+ setAnnotationConfig(modelConfig.annotation_reply, true)
+
if (modelConfig.sensitive_word_avoidance)
setModerationConfig(modelConfig.sensitive_word_avoidance)
@@ -580,6 +599,8 @@ const Configuration: FC = () => {
setSpeechToTextConfig,
citationConfig,
setCitationConfig,
+ annotationConfig,
+ setAnnotationConfig,
moderationConfig,
setModerationConfig,
externalDataToolsConfig,
@@ -628,7 +649,7 @@ const Configuration: FC = () => {
onClick={() => setPromptMode(PromptMode.simple)}
className='flex items-center h-6 px-2 bg-indigo-600 shadow-xs border border-gray-200 rounded-lg text-white text-xs font-semibold cursor-pointer space-x-1'
>
-
+
{t('appDebug.promptMode.switchBack')}
)}
diff --git a/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx b/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx
new file mode 100644
index 0000000000..3f92791749
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx
@@ -0,0 +1,132 @@
+'use client'
+import type { FC } from 'react'
+import React, { useRef, useState } from 'react'
+import { useHover } from 'ahooks'
+import cn from 'classnames'
+import { useTranslation } from 'react-i18next'
+import { MessageCheckRemove, MessageFastPlus } from '@/app/components/base/icons/src/vender/line/communication'
+import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
+import { Edit04 } from '@/app/components/base/icons/src/vender/line/general'
+import RemoveAnnotationConfirmModal from '@/app/components/app/annotation/remove-annotation-confirm-modal'
+import TooltipPlus from '@/app/components/base/tooltip-plus'
+import { addAnnotation, delAnnotation } from '@/service/annotation'
+import Toast from '@/app/components/base/toast'
+import { useProviderContext } from '@/context/provider-context'
+import { useModalContext } from '@/context/modal-context'
+
+type Props = {
+ appId: string
+ messageId?: string
+ annotationId?: string
+ className?: string
+ cached: boolean
+ query: string
+ answer: string
+ onAdded: (annotationId: string, authorName: string) => void
+ onEdit: () => void
+ onRemoved: () => void
+}
+
+const CacheCtrlBtn: FC = ({
+ className,
+ cached,
+ query,
+ answer,
+ appId,
+ messageId,
+ annotationId,
+ onAdded,
+ onEdit,
+ onRemoved,
+}) => {
+ const { t } = useTranslation()
+ const { plan, enableBilling } = useProviderContext()
+ const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse)
+ const { setShowAnnotationFullModal } = useModalContext()
+ const [showModal, setShowModal] = useState(false)
+ const cachedBtnRef = useRef(null)
+ const isCachedBtnHovering = useHover(cachedBtnRef)
+ const handleAdd = async () => {
+ if (isAnnotationFull) {
+ setShowAnnotationFullModal()
+ return
+ }
+ const res: any = await addAnnotation(appId, {
+ message_id: messageId,
+ question: query,
+ answer,
+ })
+ Toast.notify({
+ message: t('common.api.actionSuccess') as string,
+ type: 'success',
+ })
+ onAdded(res.id, res.account?.name)
+ }
+
+ const handleRemove = async () => {
+ await delAnnotation(appId, annotationId!)
+ Toast.notify({
+ message: t('common.api.actionSuccess') as string,
+ type: 'success',
+ })
+ onRemoved()
+ setShowModal(false)
+ }
+ return (
+
+
+ {cached
+ ? (
+
+
setShowModal(true)}
+ >
+ {!isCachedBtnHovering
+ ? (
+ <>
+
+
{t('appDebug.feature.annotation.cached')}
+ >
+ )
+ : <>
+
+
{t('appDebug.feature.annotation.remove')}
+ >}
+
+
+ )
+ : (
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
setShowModal(false)}
+ onRemove={handleRemove}
+ />
+
+ )
+}
+export default React.memo(CacheCtrlBtn)
diff --git a/web/app/components/app/configuration/toolbox/annotation/config-param-modal.tsx b/web/app/components/app/configuration/toolbox/annotation/config-param-modal.tsx
new file mode 100644
index 0000000000..e025f287aa
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/annotation/config-param-modal.tsx
@@ -0,0 +1,138 @@
+'use client'
+import type { FC } from 'react'
+import React, { useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import ScoreSlider from '../score-slider'
+import { Item } from './config-param'
+import Modal from '@/app/components/base/modal'
+import Button from '@/app/components/base/button'
+import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
+import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector/portal-select'
+import { useProviderContext } from '@/context/provider-context'
+import Toast from '@/app/components/base/toast'
+import type { AnnotationReplyConfig } from '@/models/debug'
+import { ANNOTATION_DEFAULT } from '@/config'
+
+type Props = {
+ appId: string
+ isShow: boolean
+ onHide: () => void
+ onSave: (embeddingModel: {
+ embedding_provider_name: string
+ embedding_model_name: string
+ }, score: number) => void
+ isInit?: boolean
+ annotationConfig: AnnotationReplyConfig
+}
+
+const ConfigParamModal: FC = ({
+ isShow,
+ onHide: doHide,
+ onSave,
+ isInit,
+ annotationConfig: oldAnnotationConfig,
+}) => {
+ const { t } = useTranslation()
+ const {
+ embeddingsDefaultModel,
+ isEmbeddingsDefaultModelValid,
+ } = useProviderContext()
+ const [annotationConfig, setAnnotationConfig] = useState(oldAnnotationConfig)
+
+ const [isLoading, setLoading] = useState(false)
+ const [embeddingModel, setEmbeddingModel] = useState(oldAnnotationConfig.embedding_model
+ ? {
+ providerName: oldAnnotationConfig.embedding_model.embedding_provider_name,
+ modelName: oldAnnotationConfig.embedding_model.embedding_model_name,
+ }
+ : (embeddingsDefaultModel
+ ? {
+ providerName: embeddingsDefaultModel.model_provider.provider_name,
+ modelName: embeddingsDefaultModel.model_name,
+ }
+ : undefined))
+ const onHide = () => {
+ if (!isLoading)
+ doHide()
+ }
+
+ const handleSave = async () => {
+ if (!embeddingModel || !embeddingModel.modelName || (embeddingModel.modelName === embeddingsDefaultModel?.model_name && !isEmbeddingsDefaultModelValid)) {
+ Toast.notify({
+ message: t('common.modelProvider.embeddingModel.required'),
+ type: 'error',
+ })
+ return
+ }
+ setLoading(true)
+ await onSave({
+ embedding_provider_name: embeddingModel.providerName,
+ embedding_model_name: embeddingModel.modelName,
+ }, annotationConfig.score_threshold)
+ setLoading(false)
+ }
+
+ return (
+
+
+ {t(`appAnnotation.initSetup.${isInit ? 'title' : 'configTitle'}`)}
+
+
+
+
-
+
{
+ setAnnotationConfig({
+ ...annotationConfig,
+ score_threshold: val / 100,
+ })
+ }}
+ />
+
+
+
-
+
+ {
+ setEmbeddingModel({
+ providerName: val.model_provider.provider_name,
+ modelName: val.model_name,
+ })
+ }}
+ />
+
+
+
+
+
+
{t('common.operation.cancel')}
+
+
+ {t(`appAnnotation.initSetup.${isInit ? 'confirmBtn' : 'configConfirmBtn'}`)}
+
+
+
+ )
+}
+export default React.memo(ConfigParamModal)
diff --git a/web/app/components/app/configuration/toolbox/annotation/config-param.tsx b/web/app/components/app/configuration/toolbox/annotation/config-param.tsx
new file mode 100644
index 0000000000..a068a7d76d
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/annotation/config-param.tsx
@@ -0,0 +1,124 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { useContext } from 'use-context-selector'
+import { usePathname, useRouter } from 'next/navigation'
+import ConfigParamModal from './config-param-modal'
+import Panel from '@/app/components/app/configuration/base/feature-panel'
+import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
+import TooltipPlus from '@/app/components/base/tooltip-plus'
+import { HelpCircle, LinkExternal02, Settings04 } from '@/app/components/base/icons/src/vender/line/general'
+import ConfigContext from '@/context/debug-configuration'
+import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type'
+import { updateAnnotationScore } from '@/service/annotation'
+
+type Props = {
+ onEmbeddingChange: (embeddingModel: EmbeddingModelConfig) => void
+ onScoreChange: (score: number, embeddingModel?: EmbeddingModelConfig) => void
+}
+
+export const Item: FC<{ title: string; tooltip: string; children: JSX.Element }> = ({
+ title,
+ tooltip,
+ children,
+}) => {
+ return (
+
+ {children}
+
+ )
+}
+
+const AnnotationReplyConfig: FC = ({
+ onEmbeddingChange,
+ onScoreChange,
+}) => {
+ const { t } = useTranslation()
+ const router = useRouter()
+ const pathname = usePathname()
+ const matched = pathname.match(/\/app\/([^/]+)/)
+ const appId = (matched?.length && matched[1]) ? matched[1] : ''
+ const {
+ annotationConfig,
+ } = useContext(ConfigContext)
+
+ const [isShowEdit, setIsShowEdit] = React.useState(false)
+
+ return (
+ <>
+
+ }
+ title={t('appDebug.feature.annotation.title')}
+ headerRight={
+
+
{ setIsShowEdit(true) }}
+ >
+
+
+
+ {t('common.operation.params')}
+
+
+
{
+ router.push(`/app/${appId}/annotations`)
+ }}>
+
{t('appDebug.feature.annotation.cacheManagement')}
+
+
+
+ }
+ noBodySpacing
+ />
+ {isShowEdit && (
+ {
+ setIsShowEdit(false)
+ }}
+ onSave={async (embeddingModel, score) => {
+ let isEmbeddingModelChanged = false
+ if (
+ embeddingModel.embedding_model_name !== annotationConfig.embedding_model.embedding_model_name
+ && embeddingModel.embedding_provider_name !== annotationConfig.embedding_model.embedding_provider_name
+ ) {
+ await onEmbeddingChange(embeddingModel)
+ isEmbeddingModelChanged = true
+ }
+
+ if (score !== annotationConfig.score_threshold) {
+ await updateAnnotationScore(appId, annotationConfig.id, score)
+ if (isEmbeddingModelChanged)
+ onScoreChange(score, embeddingModel)
+
+ else
+ onScoreChange(score)
+ }
+
+ setIsShowEdit(false)
+ }}
+ annotationConfig={annotationConfig}
+ />
+ )}
+ >
+ )
+}
+export default React.memo(AnnotationReplyConfig)
diff --git a/web/app/components/app/configuration/toolbox/annotation/type.ts b/web/app/components/app/configuration/toolbox/annotation/type.ts
new file mode 100644
index 0000000000..910453478c
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/annotation/type.ts
@@ -0,0 +1,4 @@
+export enum PageType {
+ log = 'log',
+ annotation = 'annotation',
+}
diff --git a/web/app/components/app/configuration/toolbox/annotation/use-annotation-config.ts b/web/app/components/app/configuration/toolbox/annotation/use-annotation-config.ts
new file mode 100644
index 0000000000..540302cb27
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/annotation/use-annotation-config.ts
@@ -0,0 +1,89 @@
+import React, { useState } from 'react'
+import produce from 'immer'
+import type { AnnotationReplyConfig } from '@/models/debug'
+import { queryAnnotationJobStatus, updateAnnotationStatus } from '@/service/annotation'
+import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type'
+import { AnnotationEnableStatus, JobStatus } from '@/app/components/app/annotation/type'
+import { sleep } from '@/utils'
+import { ANNOTATION_DEFAULT } from '@/config'
+import { useProviderContext } from '@/context/provider-context'
+
+type Params = {
+ appId: string
+ annotationConfig: AnnotationReplyConfig
+ setAnnotationConfig: (annotationConfig: AnnotationReplyConfig) => void
+}
+const useAnnotationConfig = ({
+ appId,
+ annotationConfig,
+ setAnnotationConfig,
+}: Params) => {
+ const { plan, enableBilling } = useProviderContext()
+ const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse)
+ const [isShowAnnotationFullModal, setIsShowAnnotationFullModal] = useState(false)
+ const [isShowAnnotationConfigInit, doSetIsShowAnnotationConfigInit] = React.useState(false)
+ const setIsShowAnnotationConfigInit = (isShow: boolean) => {
+ if (isShow) {
+ if (isAnnotationFull) {
+ setIsShowAnnotationFullModal(true)
+ return
+ }
+ }
+ doSetIsShowAnnotationConfigInit(isShow)
+ }
+ const ensureJobCompleted = async (jobId: string, status: AnnotationEnableStatus) => {
+ let isCompleted = false
+ while (!isCompleted) {
+ const res: any = await queryAnnotationJobStatus(appId, status, jobId)
+ isCompleted = res.job_status === JobStatus.completed
+ if (isCompleted)
+ break
+
+ await sleep(2000)
+ }
+ }
+
+ const handleEnableAnnotation = async (embeddingModel: EmbeddingModelConfig, score?: number) => {
+ if (isAnnotationFull)
+ return
+
+ const { job_id: jobId }: any = await updateAnnotationStatus(appId, AnnotationEnableStatus.enable, embeddingModel, score)
+ await ensureJobCompleted(jobId, AnnotationEnableStatus.enable)
+ setAnnotationConfig(produce(annotationConfig, (draft: AnnotationReplyConfig) => {
+ draft.enabled = true
+ draft.embedding_model = embeddingModel
+ if (!draft.score_threshold)
+ draft.score_threshold = ANNOTATION_DEFAULT.score_threshold
+ }))
+ }
+
+ const setScore = (score: number, embeddingModel?: EmbeddingModelConfig) => {
+ setAnnotationConfig(produce(annotationConfig, (draft: AnnotationReplyConfig) => {
+ draft.score_threshold = score
+ if (embeddingModel)
+ draft.embedding_model = embeddingModel
+ }))
+ }
+
+ const handleDisableAnnotation = async (embeddingModel: EmbeddingModelConfig) => {
+ if (!annotationConfig.enabled)
+ return
+
+ await updateAnnotationStatus(appId, AnnotationEnableStatus.disable, embeddingModel)
+ setAnnotationConfig(produce(annotationConfig, (draft: AnnotationReplyConfig) => {
+ draft.enabled = false
+ }))
+ }
+
+ return {
+ handleEnableAnnotation,
+ handleDisableAnnotation,
+ isShowAnnotationConfigInit,
+ setIsShowAnnotationConfigInit,
+ isShowAnnotationFullModal,
+ setIsShowAnnotationFullModal,
+ setScore,
+ }
+}
+
+export default useAnnotationConfig
diff --git a/web/app/components/app/configuration/toolbox/index.tsx b/web/app/components/app/configuration/toolbox/index.tsx
index d79b72371c..488ca86b90 100644
--- a/web/app/components/app/configuration/toolbox/index.tsx
+++ b/web/app/components/app/configuration/toolbox/index.tsx
@@ -5,12 +5,22 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import GroupName from '../base/group-name'
import Moderation from './moderation'
+import Annotation from './annotation/config-param'
+import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type'
export type ToolboxProps = {
showModerationSettings: boolean
+ showAnnotation: boolean
+ onEmbeddingChange: (embeddingModel: EmbeddingModelConfig) => void
+ onScoreChange: (score: number, embeddingModel?: EmbeddingModelConfig) => void
}
-const Toolbox: FC = ({ showModerationSettings }) => {
+const Toolbox: FC = ({
+ showModerationSettings,
+ showAnnotation,
+ onEmbeddingChange,
+ onScoreChange,
+}) => {
const { t } = useTranslation()
return (
@@ -21,6 +31,14 @@ const Toolbox: FC = ({ showModerationSettings }) => {
)
}
+ {
+ (showAnnotation || true) && (
+
+ )
+ }
)
}
diff --git a/web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx b/web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx
new file mode 100644
index 0000000000..b659e14f40
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx
@@ -0,0 +1,38 @@
+import ReactSlider from 'react-slider'
+import cn from 'classnames'
+import s from './style.module.css'
+
+type ISliderProps = {
+ className?: string
+ value: number
+ max?: number
+ min?: number
+ step?: number
+ disabled?: boolean
+ onChange: (value: number) => void
+}
+
+const Slider: React.FC = ({ className, max, min, step, value, disabled, onChange }) => {
+ return (
+
+
+
+ {(state.valueNow / 100).toFixed(2)}
+
+
+
+ )}
+ />
+}
+
+export default Slider
diff --git a/web/app/components/app/configuration/toolbox/score-slider/base-slider/style.module.css b/web/app/components/app/configuration/toolbox/score-slider/base-slider/style.module.css
new file mode 100644
index 0000000000..4e93b39563
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/score-slider/base-slider/style.module.css
@@ -0,0 +1,20 @@
+.slider {
+ position: relative;
+}
+
+.slider.disabled {
+ opacity: 0.6;
+}
+
+.slider-thumb:focus {
+ outline: none;
+}
+
+.slider-track {
+ background-color: #528BFF;
+ height: 2px;
+}
+
+.slider-track-1 {
+ background-color: #E5E7EB;
+}
\ No newline at end of file
diff --git a/web/app/components/app/configuration/toolbox/score-slider/index.tsx b/web/app/components/app/configuration/toolbox/score-slider/index.tsx
new file mode 100644
index 0000000000..9826cbadcf
--- /dev/null
+++ b/web/app/components/app/configuration/toolbox/score-slider/index.tsx
@@ -0,0 +1,46 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import Slider from '@/app/components/app/configuration/toolbox/score-slider/base-slider'
+
+type Props = {
+ className?: string
+ value: number
+ onChange: (value: number) => void
+}
+
+const ScoreSlider: FC = ({
+ className,
+ value,
+ onChange,
+}) => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
+
+
0.8
+
·
+
{t('appDebug.feature.annotation.scoreThreshold.easyMatch')}
+
+
+
1.0
+
·
+
{t('appDebug.feature.annotation.scoreThreshold.accurateMatch')}
+
+
+
+ )
+}
+export default React.memo(ScoreSlider)
diff --git a/web/app/components/app/log-annotation/index.tsx b/web/app/components/app/log-annotation/index.tsx
new file mode 100644
index 0000000000..08351908c5
--- /dev/null
+++ b/web/app/components/app/log-annotation/index.tsx
@@ -0,0 +1,45 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { useRouter } from 'next/navigation'
+import Log from '@/app/components/app/log'
+import Annotation from '@/app/components/app/annotation'
+import { PageType } from '@/app/components/app/configuration/toolbox/annotation/type'
+import TabSlider from '@/app/components/base/tab-slider-plain'
+
+type Props = {
+ pageType: PageType
+ appId: string
+}
+
+const LogAnnotation: FC = ({
+ pageType,
+ appId,
+}) => {
+ const { t } = useTranslation()
+ const router = useRouter()
+
+ const options = [
+ { value: PageType.log, text: t('appLog.title') },
+ { value: PageType.annotation, text: t('appAnnotation.title') },
+ ]
+
+ return (
+
+
{
+ router.push(`/app/${appId}/${value === PageType.log ? 'logs' : 'annotations'}`)
+ }}
+ options={options}
+ />
+
+ {pageType === PageType.log && (
)}
+ {pageType === PageType.annotation && (
)}
+
+
+ )
+}
+export default React.memo(LogAnnotation)
diff --git a/web/app/components/app/log/index.tsx b/web/app/components/app/log/index.tsx
index 07cca88558..db853299fa 100644
--- a/web/app/components/app/log/index.tsx
+++ b/web/app/components/app/log/index.tsx
@@ -15,7 +15,7 @@ import s from './style.module.css'
import Loading from '@/app/components/base/loading'
import { fetchChatConversations, fetchCompletionConversations } from '@/service/log'
import { fetchAppDetail } from '@/service/apps'
-
+import { APP_PAGE_LIMIT } from '@/config'
export type ILogsProps = {
appId: string
}
@@ -26,9 +26,6 @@ export type QueryParam = {
keyword?: string
}
-// Custom page count is not currently supported.
-const limit = 10
-
const ThreeDotsIcon = ({ className }: SVGProps) => {
return
@@ -60,7 +57,7 @@ const Logs: FC = ({ appId }) => {
const query = {
page: currPage + 1,
- limit,
+ limit: APP_PAGE_LIMIT,
...(queryParams.period !== 'all'
? {
start: dayjs().subtract(queryParams.period as number, 'day').startOf('day').format('YYYY-MM-DD HH:mm'),
@@ -93,11 +90,8 @@ const Logs: FC = ({ appId }) => {
return (
-
-
{t('appLog.title')}
-
{t('appLog.description')}
-
-
+
{t('appLog.description')}
+
{total === undefined
?
@@ -106,14 +100,14 @@ const Logs: FC
= ({ appId }) => {
:
}
{/* Show Pagination only if the total is more than the limit */}
- {(total && total > limit)
+ {(total && total > APP_PAGE_LIMIT)
?
@@ -131,8 +125,8 @@ const Logs: FC = ({ appId }) => {
/>
+ disabled={currPage === Math.ceil(total / APP_PAGE_LIMIT) - 1}
+ className={`flex items-center mr-2 text-gray-500 focus:outline-none ${currPage === Math.ceil(total / APP_PAGE_LIMIT) - 1 ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:text-gray-600 dark:hover:text-gray-200'}`} >
{t('appLog.table.pagination.next')}
diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx
index 9cb8d86cac..32a64ec0ac 100644
--- a/web/app/components/app/log/list.tsx
+++ b/web/app/components/app/log/list.tsx
@@ -34,6 +34,7 @@ import ModelIcon from '@/app/components/app/configuration/config-model/model-ico
import ModelName from '@/app/components/app/configuration/config-model/model-name'
import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/model-mode-type-label'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+import TextGeneration from '@/app/components/app/text-generate/item'
type IConversationList = {
logs?: ChatConversationsResponse | CompletionConversationsResponse
@@ -83,7 +84,6 @@ const getFormattedChatList = (messages: ChatMessage[]) => {
log: item.message as any,
message_files: item.message_files,
})
-
newChatList.push({
id: item.id,
content: item.answer,
@@ -96,7 +96,26 @@ const getFormattedChatList = (messages: ChatMessage[]) => {
tokens: item.answer_tokens + item.message_tokens,
latency: item.provider_response_latency.toFixed(2),
},
- annotation: item.annotation,
+ annotation: (() => {
+ if (item.annotation_hit_history) {
+ return {
+ id: item.annotation_hit_history.annotation_id,
+ authorName: item.annotation_hit_history.annotation_create_account.name,
+ created_at: item.annotation_hit_history.created_at,
+ }
+ }
+
+ if (item.annotation) {
+ return {
+ id: '',
+ authorName: '',
+ logAnnotation: item.annotation,
+ created_at: 0,
+ }
+ }
+
+ return undefined
+ })(),
})
})
return newChatList
@@ -253,14 +272,26 @@ function DetailPanel
-
+
+
{t('appLog.table.header.output')}
+
+
+ { }}
+ isInstalledApp={false}
+ supportFeedback
+ feedback={detail.message.feedbacks.find((item: any) => item.from_source === 'admin')}
+ onFeedback={feedback => onFeedback(detail.message.id, feedback)}
+ supportAnnotation
+ appId={appDetail?.id}
+ varList={varList}
/>
: items.length < 8
@@ -269,9 +300,11 @@ function DetailPanel
:
@@ -427,7 +459,7 @@ const ConversationList: FC = ({ logs, appDetail, onRefresh })
- {`${t('appLog.detail.annotationTip', { user: annotation?.account?.name })} ${dayjs.unix(annotation?.created_at || dayjs().unix()).format('MM-DD hh:mm A')}`}
+ {`${t('appLog.detail.annotationTip', { user: annotation?.logAnnotation?.account?.name })} ${dayjs.unix(annotation?.created_at || dayjs().unix()).format('MM-DD hh:mm A')}`}
}
className={(isHighlight && !isChatMode) ? '' : '!hidden'}
diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx
index 796dbeae4a..d8967d2992 100644
--- a/web/app/components/app/text-generate/item/index.tsx
+++ b/web/app/components/app/text-generate/item/index.tsx
@@ -19,6 +19,8 @@ import { Bookmark } from '@/app/components/base/icons/src/vender/line/general'
import { Stars02 } from '@/app/components/base/icons/src/vender/line/weather'
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
import { fetchTextGenerationMessge } from '@/service/debug'
+import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
+import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
const MAX_DEPTH = 3
export type IGenerationItemProps = {
@@ -41,6 +43,10 @@ export type IGenerationItemProps = {
installedAppId?: string
taskId?: string
controlClearMoreLikeThis?: number
+ supportFeedback?: boolean
+ supportAnnotation?: boolean
+ appId?: string
+ varList?: { label: string; value: string | number | object }[]
}
export const SimpleBtn = ({ className, isDisabled, onClick, children }: {
@@ -82,6 +88,10 @@ const GenerationItem: FC = ({
installedAppId,
taskId,
controlClearMoreLikeThis,
+ supportFeedback,
+ supportAnnotation,
+ appId,
+ varList,
}) => {
const { t } = useTranslation()
const params = useParams()
@@ -100,6 +110,8 @@ const GenerationItem: FC = ({
setChildFeedback(childFeedback)
}
+ const [isShowReplyModal, setIsShowReplyModal] = useState(false)
+ const question = (varList && varList?.length > 0) ? varList?.map(({ label, value }) => `${label}:${value}`).join('&') : ''
const [isQuerying, { setTrue: startQuerying, setFalse: stopQuerying }] = useBoolean(false)
const childProps = {
@@ -168,6 +180,57 @@ const GenerationItem: FC = ({
setModal(true)
}
+ const ratingContent = (
+ <>
+ {!isError && messageId && !feedback?.rating && (
+
+ <>
+ {
+ onFeedback?.({
+ rating: 'like',
+ })
+ }}
+ className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
+
+
+ {
+ onFeedback?.({
+ rating: 'dislike',
+ })
+ }}
+ className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
+
+
+ >
+
+ )}
+ {!isError && messageId && feedback?.rating === 'like' && (
+ {
+ onFeedback?.({
+ rating: null,
+ })
+ }}
+ className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'>
+
+
+ )}
+ {!isError && messageId && feedback?.rating === 'dislike' && (
+ {
+ onFeedback?.({
+ rating: null,
+ })
+ }}
+ className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'>
+
+
+ )}
+ >
+ )
+
return (
= ({
{isError
?
{t('share.generation.batchFailed.outputPlaceholder')}
: (
-
+
)}
@@ -214,7 +277,7 @@ const GenerationItem: FC = ({
showModal => (
handleOpenLogModal(showModal)}>
{!isMobile && {t('common.operation.log')}
}
@@ -261,54 +324,50 @@ const GenerationItem: FC = ({
{!isMobile && {t('share.generation.batchFailed.retry')}
}
}
{!isError && messageId &&
}
- {!isError && messageId && !feedback?.rating && (
-
- <>
- {
- onFeedback?.({
- rating: 'like',
- })
- }}
- className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
-
-
- {
- onFeedback?.({
- rating: 'dislike',
- })
- }}
- className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
-
-
- >
-
- )}
- {!isError && messageId && feedback?.rating === 'like' && (
- {
- onFeedback?.({
- rating: null,
- })
- }}
- className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'>
-
-
- )}
- {!isError && messageId && feedback?.rating === 'dislike' && (
- {
- onFeedback?.({
- rating: null,
- })
- }}
- className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'>
-
-
- )}
+ {ratingContent}
>
)}
+
+ {supportAnnotation && (
+ <>
+
+ {
+
+ }}
+ onEdit={() => setIsShowReplyModal(true)}
+ onRemoved={() => { }}
+ />
+ >
+ )}
+
+ setIsShowReplyModal(false)}
+ query={question}
+ answer={content}
+ onAdded={() => { }}
+ onEdited={() => { }}
+ createdAt={0}
+ onRemove={() => { }}
+ onlyEditResponse
+ />
+
+ {supportFeedback && (
+
+ {ratingContent}
+
+ )
+ }
{content?.length} {t('common.unit.char')}
diff --git a/web/app/components/base/drawer-plus/index.tsx b/web/app/components/base/drawer-plus/index.tsx
new file mode 100644
index 0000000000..61504860e8
--- /dev/null
+++ b/web/app/components/base/drawer-plus/index.tsx
@@ -0,0 +1,69 @@
+'use client'
+import type { FC } from 'react'
+import React, { useRef } from 'react'
+import Drawer from '@/app/components/base/drawer'
+import { XClose } from '@/app/components/base/icons/src/vender/line/general'
+import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+
+type Props = {
+ isShow: boolean
+ onHide: () => void
+ maxWidthClassName?: string
+ height?: number | string
+ title: string | JSX.Element
+ body: JSX.Element
+ foot?: JSX.Element
+}
+
+const DrawerPlus: FC = ({
+ isShow,
+ onHide,
+ maxWidthClassName = '!max-w-[640px]',
+ height = 'calc(100vh - 72px)',
+ title,
+ body,
+ foot,
+}) => {
+ const ref = useRef(null)
+ const media = useBreakpoints()
+ const isMobile = media === MediaType.mobile
+
+ if (!isShow)
+ return null
+
+ return (
+ // clickOutsideNotOpen to fix confirm modal click cause drawer close
+
+
+
+
+ {body}
+
+ {foot && (
+
+ {foot}
+
+ )}
+
+
+ )
+}
+export default React.memo(DrawerPlus)
diff --git a/web/app/components/base/drawer/index.tsx b/web/app/components/base/drawer/index.tsx
index 1911350831..2a14e41205 100644
--- a/web/app/components/base/drawer/index.tsx
+++ b/web/app/components/base/drawer/index.tsx
@@ -14,6 +14,7 @@ export type IDrawerProps = {
isOpen: boolean
// closable: boolean
showClose?: boolean
+ clickOutsideNotOpen?: boolean
onClose: () => void
onCancel?: () => void
onOk?: () => void
@@ -28,6 +29,7 @@ export default function Drawer({
mask = true,
showClose = false,
isOpen,
+ clickOutsideNotOpen,
onClose,
onCancel,
onOk,
@@ -37,7 +39,7 @@ export default function Drawer({
onClose()}
+ onClose={() => !clickOutsideNotOpen && onClose()}
className="fixed z-30 inset-0 overflow-y-auto"
>
diff --git a/web/app/components/base/icons/assets/public/avatar/robot.svg b/web/app/components/base/icons/assets/public/avatar/robot.svg
new file mode 100644
index 0000000000..55b70bb013
--- /dev/null
+++ b/web/app/components/base/icons/assets/public/avatar/robot.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/app/components/base/icons/assets/public/avatar/user.svg b/web/app/components/base/icons/assets/public/avatar/user.svg
new file mode 100644
index 0000000000..1608b0a324
--- /dev/null
+++ b/web/app/components/base/icons/assets/public/avatar/user.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/app/components/base/icons/assets/public/billing/sparkles.svg b/web/app/components/base/icons/assets/public/billing/sparkles.svg
index 68cf299e17..fced090786 100644
--- a/web/app/components/base/icons/assets/public/billing/sparkles.svg
+++ b/web/app/components/base/icons/assets/public/billing/sparkles.svg
@@ -1,9 +1,9 @@
-
+
-
+
diff --git a/web/app/components/base/icons/assets/vender/line/communication/message-check-remove.svg b/web/app/components/base/icons/assets/vender/line/communication/message-check-remove.svg
new file mode 100644
index 0000000000..ea0c2d50bd
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/line/communication/message-check-remove.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/web/app/components/base/icons/assets/vender/line/communication/message-fast-plus.svg b/web/app/components/base/icons/assets/vender/line/communication/message-fast-plus.svg
new file mode 100644
index 0000000000..4d399f0b46
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/line/communication/message-fast-plus.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/web/app/components/base/icons/assets/vender/line/files/file-download-02.svg b/web/app/components/base/icons/assets/vender/line/files/file-download-02.svg
new file mode 100644
index 0000000000..e96132bbb4
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/line/files/file-download-02.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/web/app/components/base/icons/assets/vender/line/general/edit-04.svg b/web/app/components/base/icons/assets/vender/line/general/edit-04.svg
new file mode 100644
index 0000000000..9318930440
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/line/general/edit-04.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/web/app/components/base/icons/assets/vender/line/time/clock-fast-forward.svg b/web/app/components/base/icons/assets/vender/line/time/clock-fast-forward.svg
new file mode 100644
index 0000000000..5e499f5d4d
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/line/time/clock-fast-forward.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/web/app/components/base/icons/assets/vender/solid/communication/message-fast.svg b/web/app/components/base/icons/assets/vender/solid/communication/message-fast.svg
new file mode 100644
index 0000000000..66a206f4f4
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/solid/communication/message-fast.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/web/app/components/base/icons/assets/vender/solid/general/edit-04.svg b/web/app/components/base/icons/assets/vender/solid/general/edit-04.svg
new file mode 100644
index 0000000000..805e39ad74
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/solid/general/edit-04.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/web/app/components/base/icons/src/public/avatar/Robot.json b/web/app/components/base/icons/src/public/avatar/Robot.json
new file mode 100644
index 0000000000..babc0f87a0
--- /dev/null
+++ b/web/app/components/base/icons/src/public/avatar/Robot.json
@@ -0,0 +1,92 @@
+{
+ "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",
+ "xmlns:xlink": "http://www.w3.org/1999/xlink"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "rect",
+ "attributes": {
+ "width": "24",
+ "height": "24",
+ "rx": "12",
+ "fill": "#D5F5F6"
+ },
+ "children": []
+ },
+ {
+ "type": "element",
+ "name": "rect",
+ "attributes": {
+ "x": "0.25",
+ "y": "0.25",
+ "width": "23.5",
+ "height": "23.5",
+ "rx": "11.75",
+ "stroke": "black",
+ "stroke-opacity": "0.05",
+ "stroke-width": "0.5"
+ },
+ "children": []
+ },
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M4 20.12H20V4.12H4V20.12Z",
+ "fill": "url(#pattern0)"
+ },
+ "children": []
+ },
+ {
+ "type": "element",
+ "name": "defs",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "pattern",
+ "attributes": {
+ "id": "pattern0",
+ "patternContentUnits": "objectBoundingBox",
+ "width": "1",
+ "height": "1"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "use",
+ "attributes": {
+ "xlink:href": "#image0_13843_72627",
+ "transform": "scale(0.00625)"
+ },
+ "children": []
+ }
+ ]
+ },
+ {
+ "type": "element",
+ "name": "image",
+ "attributes": {
+ "id": "image0_13843_72627",
+ "width": "160",
+ "height": "160",
+ "xlink:href": "data:image/png;base64,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"
+ },
+ "children": []
+ }
+ ]
+ }
+ ]
+ },
+ "name": "Robot"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/public/avatar/Robot.tsx b/web/app/components/base/icons/src/public/avatar/Robot.tsx
new file mode 100644
index 0000000000..07251c5b09
--- /dev/null
+++ b/web/app/components/base/icons/src/public/avatar/Robot.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Robot.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,
+) => )
+
+Icon.displayName = 'Robot'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/public/avatar/User.json b/web/app/components/base/icons/src/public/avatar/User.json
new file mode 100644
index 0000000000..01fb8e39c3
--- /dev/null
+++ b/web/app/components/base/icons/src/public/avatar/User.json
@@ -0,0 +1,89 @@
+{
+ "icon": {
+ "type": "element",
+ "isRootNode": true,
+ "name": "svg",
+ "attributes": {
+ "width": "512",
+ "height": "512",
+ "viewBox": "0 0 512 512",
+ "fill": "none",
+ "xmlns": "http://www.w3.org/2000/svg"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {
+ "clip-path": "url(#clip0_5968_39205)"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "rect",
+ "attributes": {
+ "width": "512",
+ "height": "512",
+ "rx": "256",
+ "fill": "#B2DDFF"
+ },
+ "children": []
+ },
+ {
+ "type": "element",
+ "name": "circle",
+ "attributes": {
+ "opacity": "0.68",
+ "cx": "256",
+ "cy": "196",
+ "r": "84",
+ "fill": "white"
+ },
+ "children": []
+ },
+ {
+ "type": "element",
+ "name": "ellipse",
+ "attributes": {
+ "opacity": "0.68",
+ "cx": "256",
+ "cy": "583.5",
+ "rx": "266",
+ "ry": "274.5",
+ "fill": "white"
+ },
+ "children": []
+ }
+ ]
+ },
+ {
+ "type": "element",
+ "name": "defs",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "clipPath",
+ "attributes": {
+ "id": "clip0_5968_39205"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "rect",
+ "attributes": {
+ "width": "512",
+ "height": "512",
+ "rx": "256",
+ "fill": "white"
+ },
+ "children": []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "name": "User"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/public/avatar/User.tsx b/web/app/components/base/icons/src/public/avatar/User.tsx
new file mode 100644
index 0000000000..3fb53054df
--- /dev/null
+++ b/web/app/components/base/icons/src/public/avatar/User.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './User.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,
+) => )
+
+Icon.displayName = 'User'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/public/avatar/index.ts b/web/app/components/base/icons/src/public/avatar/index.ts
new file mode 100644
index 0000000000..7355b6bd89
--- /dev/null
+++ b/web/app/components/base/icons/src/public/avatar/index.ts
@@ -0,0 +1,2 @@
+export { default as Robot } from './Robot'
+export { default as User } from './User'
diff --git a/web/app/components/base/icons/src/public/billing/Sparkles.json b/web/app/components/base/icons/src/public/billing/Sparkles.json
index 2fdb6c6487..ea2bae44e7 100644
--- a/web/app/components/base/icons/src/public/billing/Sparkles.json
+++ b/web/app/components/base/icons/src/public/billing/Sparkles.json
@@ -25,7 +25,7 @@
"attributes": {
"width": "600",
"height": "600",
- "fill": "url(#pattern0)"
+ "fill": "url(#pattern999)"
},
"children": []
}
@@ -40,7 +40,7 @@
"type": "element",
"name": "pattern",
"attributes": {
- "id": "pattern0",
+ "id": "pattern999",
"patternContentUnits": "objectBoundingBox",
"width": "1",
"height": "1"
diff --git a/web/app/components/base/icons/src/vender/line/communication/MessageCheckRemove.json b/web/app/components/base/icons/src/vender/line/communication/MessageCheckRemove.json
new file mode 100644
index 0000000000..a536c9f341
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/communication/MessageCheckRemove.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": "g",
+ "attributes": {
+ "id": "message-check-remove"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "id": "Vector",
+ "d": "M15.2 2.99994H7.8C6.11984 2.99994 5.27976 2.99994 4.63803 3.32693C4.07354 3.61455 3.6146 4.07349 3.32698 4.63797C3 5.27971 3 6.11979 3 7.79994V13.9999C3 14.9299 3 15.3949 3.10222 15.7764C3.37962 16.8117 4.18827 17.6203 5.22354 17.8977C5.60504 17.9999 6.07003 17.9999 7 17.9999V20.3354C7 20.8683 7 21.1347 7.10923 21.2716C7.20422 21.3906 7.34827 21.4598 7.50054 21.4596C7.67563 21.4594 7.88367 21.293 8.29976 20.9601L10.6852 19.0518C11.1725 18.6619 11.4162 18.467 11.6875 18.3284C11.9282 18.2054 12.1844 18.1155 12.4492 18.0612C12.7477 17.9999 13.0597 17.9999 13.6837 17.9999H16.2C17.8802 17.9999 18.7202 17.9999 19.362 17.673C19.9265 17.3853 20.3854 16.9264 20.673 16.3619C21 15.7202 21 14.8801 21 13.1999V8.79994M12.3333 13.4999L14 10.4999H10L11.6667 7.49994M19.2322 4.76771L21 2.99994M21 2.99994L22.7678 1.23218M21 2.99994L19.2322 1.23218M21 2.99994L22.7678 4.76771",
+ "stroke": "currentColor",
+ "stroke-width": "2",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round"
+ },
+ "children": []
+ }
+ ]
+ }
+ ]
+ },
+ "name": "MessageCheckRemove"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/vender/line/communication/MessageCheckRemove.tsx b/web/app/components/base/icons/src/vender/line/communication/MessageCheckRemove.tsx
new file mode 100644
index 0000000000..d2805c85dd
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/communication/MessageCheckRemove.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './MessageCheckRemove.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,
+) => )
+
+Icon.displayName = 'MessageCheckRemove'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/vender/line/communication/MessageFastPlus.json b/web/app/components/base/icons/src/vender/line/communication/MessageFastPlus.json
new file mode 100644
index 0000000000..7d40cc7425
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/communication/MessageFastPlus.json
@@ -0,0 +1,29 @@
+{
+ "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": "M15.2 3H7.8C6.11984 3 5.27976 3 4.63803 3.32698C4.07354 3.6146 3.6146 4.07354 3.32698 4.63803C3 5.27976 3 6.11984 3 7.8V14C3 14.93 3 15.395 3.10222 15.7765C3.37962 16.8117 4.18827 17.6204 5.22354 17.8978C5.60504 18 6.07003 18 7 18V20.3355C7 20.8684 7 21.1348 7.10923 21.2716C7.20422 21.3906 7.34827 21.4599 7.50054 21.4597C7.67563 21.4595 7.88367 21.2931 8.29976 20.9602L10.6852 19.0518C11.1725 18.662 11.4162 18.4671 11.6875 18.3285C11.9282 18.2055 12.1844 18.1156 12.4492 18.0613C12.7477 18 13.0597 18 13.6837 18H16.2C17.8802 18 18.7202 18 19.362 17.673C19.9265 17.3854 20.3854 16.9265 20.673 16.362C21 15.7202 21 14.8802 21 13.2V8.8M12.3333 13.5L14 10.5H10L11.6667 7.5M21 5V3M21 3V1M21 3H19M21 3H23",
+ "stroke": "currentColor",
+ "stroke-width": "2",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round"
+ },
+ "children": []
+ }
+ ]
+ },
+ "name": "MessageFastPlus"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/vender/line/communication/MessageFastPlus.tsx b/web/app/components/base/icons/src/vender/line/communication/MessageFastPlus.tsx
new file mode 100644
index 0000000000..03c24af5c1
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/communication/MessageFastPlus.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './MessageFastPlus.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,
+) => )
+
+Icon.displayName = 'MessageFastPlus'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/vender/line/communication/index.ts b/web/app/components/base/icons/src/vender/line/communication/index.ts
index e7841aebe2..6e3154777e 100644
--- a/web/app/components/base/icons/src/vender/line/communication/index.ts
+++ b/web/app/components/base/icons/src/vender/line/communication/index.ts
@@ -1 +1,3 @@
export { default as ChatBot } from './ChatBot'
+export { default as MessageCheckRemove } from './MessageCheckRemove'
+export { default as MessageFastPlus } from './MessageFastPlus'
diff --git a/web/app/components/base/icons/src/vender/line/files/FileDownload02.json b/web/app/components/base/icons/src/vender/line/files/FileDownload02.json
new file mode 100644
index 0000000000..a0dccc280f
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/files/FileDownload02.json
@@ -0,0 +1,29 @@
+{
+ "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": "M20 12.5V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.1198 22 8.79986 22H12.5M14 11H8M10 15H8M16 7H8M15 19L18 22M18 22L21 19M18 22V16",
+ "stroke": "currentColor",
+ "stroke-width": "2",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round"
+ },
+ "children": []
+ }
+ ]
+ },
+ "name": "FileDownload02"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/vender/line/files/FileDownload02.tsx b/web/app/components/base/icons/src/vender/line/files/FileDownload02.tsx
new file mode 100644
index 0000000000..6457fa2623
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/files/FileDownload02.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './FileDownload02.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,
+) => )
+
+Icon.displayName = 'FileDownload02'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/vender/line/files/index.ts b/web/app/components/base/icons/src/vender/line/files/index.ts
index ffe1dd3476..5dc424bb17 100644
--- a/web/app/components/base/icons/src/vender/line/files/index.ts
+++ b/web/app/components/base/icons/src/vender/line/files/index.ts
@@ -1,4 +1,5 @@
export { default as ClipboardCheck } from './ClipboardCheck'
export { default as Clipboard } from './Clipboard'
export { default as File02 } from './File02'
+export { default as FileDownload02 } from './FileDownload02'
export { default as FilePlus02 } from './FilePlus02'
diff --git a/web/app/components/base/icons/src/vender/line/general/Edit04.json b/web/app/components/base/icons/src/vender/line/general/Edit04.json
new file mode 100644
index 0000000000..73f275b732
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/general/Edit04.json
@@ -0,0 +1,29 @@
+{
+ "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": "M21 18L19.9999 19.094C19.4695 19.6741 18.7502 20 18.0002 20C17.2501 20 16.5308 19.6741 16.0004 19.094C15.4693 18.5151 14.75 18.1901 14.0002 18.1901C13.2504 18.1901 12.5312 18.5151 12 19.094M3.00003 20H4.67457C5.16376 20 5.40835 20 5.63852 19.9447C5.84259 19.8957 6.03768 19.8149 6.21663 19.7053C6.41846 19.5816 6.59141 19.4086 6.93732 19.0627L19.5001 6.49998C20.3285 5.67156 20.3285 4.32841 19.5001 3.49998C18.6716 2.67156 17.3285 2.67156 16.5001 3.49998L3.93729 16.0627C3.59139 16.4086 3.41843 16.5816 3.29475 16.7834C3.18509 16.9624 3.10428 17.1574 3.05529 17.3615C3.00003 17.5917 3.00003 17.8363 3.00003 18.3255V20Z",
+ "stroke": "currentColor",
+ "stroke-width": "2",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round"
+ },
+ "children": []
+ }
+ ]
+ },
+ "name": "Edit04"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/vender/line/general/Edit04.tsx b/web/app/components/base/icons/src/vender/line/general/Edit04.tsx
new file mode 100644
index 0000000000..d918872d91
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/general/Edit04.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Edit04.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,
+) => )
+
+Icon.displayName = 'Edit04'
+
+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 20f155d733..f9fca25d19 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
@@ -4,6 +4,7 @@ export { default as Check } from './Check'
export { default as DotsHorizontal } from './DotsHorizontal'
export { default as Edit02 } from './Edit02'
export { default as Edit03 } from './Edit03'
+export { default as Edit04 } from './Edit04'
export { default as Hash02 } from './Hash02'
export { default as HelpCircle } from './HelpCircle'
export { default as InfoCircle } from './InfoCircle'
diff --git a/web/app/components/base/icons/src/vender/line/time/ClockFastForward.json b/web/app/components/base/icons/src/vender/line/time/ClockFastForward.json
new file mode 100644
index 0000000000..26b72084bf
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/time/ClockFastForward.json
@@ -0,0 +1,29 @@
+{
+ "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.7 11.5L20.7005 13.5L18.7 11.5M20.9451 13C20.9814 12.6717 21 12.338 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C14.8273 21 17.35 19.6963 19 17.6573M12 7V12L15 14",
+ "stroke": "currentColor",
+ "stroke-width": "2",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round"
+ },
+ "children": []
+ }
+ ]
+ },
+ "name": "ClockFastForward"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/vender/line/time/ClockFastForward.tsx b/web/app/components/base/icons/src/vender/line/time/ClockFastForward.tsx
new file mode 100644
index 0000000000..6bcf1d4f65
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/time/ClockFastForward.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './ClockFastForward.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,
+) => )
+
+Icon.displayName = 'ClockFastForward'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/vender/line/time/index.ts b/web/app/components/base/icons/src/vender/line/time/index.ts
new file mode 100644
index 0000000000..74b1f565d2
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/line/time/index.ts
@@ -0,0 +1 @@
+export { default as ClockFastForward } from './ClockFastForward'
diff --git a/web/app/components/base/icons/src/vender/solid/communication/MessageFast.json b/web/app/components/base/icons/src/vender/solid/communication/MessageFast.json
new file mode 100644
index 0000000000..4580398f31
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/solid/communication/MessageFast.json
@@ -0,0 +1,28 @@
+{
+ "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": "M16.2414 2H7.7588C6.95383 1.99999 6.28946 1.99998 5.74827 2.04419C5.18617 2.09012 4.66947 2.18868 4.18413 2.43598C3.43149 2.81947 2.81956 3.43139 2.43607 4.18404C2.18878 4.66937 2.09022 5.18608 2.04429 5.74818C2.00007 6.28937 2.00008 6.95373 2.0001 7.7587L2.00005 14.1376C1.99962 14.933 1.9993 15.5236 2.13639 16.0353C2.50626 17.4156 3.58445 18.4938 4.96482 18.8637C5.27229 18.9461 5.60829 18.9789 6.0001 18.9918L6.00009 20.371C6.00005 20.6062 6 20.846 6.01785 21.0425C6.03492 21.2305 6.08012 21.5852 6.32778 21.8955C6.61276 22.2525 7.0449 22.4602 7.50172 22.4597C7.8987 22.4593 8.20394 22.273 8.36137 22.1689C8.52597 22.06 8.7132 21.9102 8.89688 21.7632L11.31 19.8327C11.8286 19.4178 11.9826 19.3007 12.1425 19.219C12.303 19.137 12.4738 19.0771 12.6504 19.0408C12.8263 19.0047 13.0197 19 13.6838 19H16.2414C17.0464 19 17.7107 19 18.2519 18.9558C18.814 18.9099 19.3307 18.8113 19.8161 18.564C20.5687 18.1805 21.1806 17.5686 21.5641 16.816C21.8114 16.3306 21.91 15.8139 21.9559 15.2518C22.0001 14.7106 22.0001 14.0463 22.0001 13.2413V7.75868C22.0001 6.95372 22.0001 6.28936 21.9559 5.74818C21.91 5.18608 21.8114 4.66937 21.5641 4.18404C21.1806 3.43139 20.5687 2.81947 19.8161 2.43598C19.3307 2.18868 18.814 2.09012 18.2519 2.04419C17.7107 1.99998 17.0464 1.99999 16.2414 2ZM12.681 5.5349C12.8938 5.61898 13.0218 5.83714 12.9916 6.06386L12.5688 9.23501L14.48 9.23501C14.5899 9.23498 14.7038 9.23496 14.7979 9.24356C14.8905 9.25203 15.0589 9.27446 15.2095 9.39066C15.3851 9.52617 15.4913 9.73269 15.4996 9.95432C15.5066 10.1444 15.427 10.2945 15.38 10.3747C15.3324 10.4563 15.2661 10.549 15.2022 10.6384L11.9072 15.2514C11.7743 15.4375 11.5317 15.5092 11.319 15.4251C11.1063 15.341 10.9782 15.1229 11.0084 14.8961L11.4312 11.725L9.52004 11.725C9.41011 11.725 9.29618 11.725 9.20206 11.7164C9.10948 11.708 8.94106 11.6855 8.79051 11.5693C8.61493 11.4338 8.50866 11.2273 8.50044 11.0057C8.49339 10.8156 8.57303 10.6655 8.61996 10.5853C8.66766 10.5037 8.7339 10.411 8.79781 10.3216L12.0928 5.70858C12.2257 5.52246 12.4683 5.45083 12.681 5.5349Z",
+ "fill": "currentColor"
+ },
+ "children": []
+ }
+ ]
+ },
+ "name": "MessageFast"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/vender/solid/communication/MessageFast.tsx b/web/app/components/base/icons/src/vender/solid/communication/MessageFast.tsx
new file mode 100644
index 0000000000..836da906d8
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/solid/communication/MessageFast.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './MessageFast.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,
+) => )
+
+Icon.displayName = 'MessageFast'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/vender/solid/communication/index.ts b/web/app/components/base/icons/src/vender/solid/communication/index.ts
new file mode 100644
index 0000000000..7e8eb36da9
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/solid/communication/index.ts
@@ -0,0 +1 @@
+export { default as MessageFast } from './MessageFast'
diff --git a/web/app/components/base/icons/src/vender/solid/general/Edit04.json b/web/app/components/base/icons/src/vender/solid/general/Edit04.json
new file mode 100644
index 0000000000..aa923c2862
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/solid/general/Edit04.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": "M21.6747 17.2619C22.0824 17.6345 22.1107 18.2671 21.7381 18.6747L20.738 19.7687C20.0284 20.5448 19.0458 21 18.0002 21C16.9549 21 15.9726 20.5452 15.2631 19.7696C14.9112 19.3863 14.4549 19.1901 14.0002 19.1901C13.5454 19.1901 13.0889 19.3864 12.7369 19.7701C12.3635 20.177 11.7309 20.2043 11.324 19.8309C10.917 19.4575 10.8898 18.8249 11.2632 18.418C11.9735 17.6438 12.9555 17.1901 14.0002 17.1901C15.045 17.1901 16.0269 17.6438 16.7373 18.418L16.7384 18.4192C17.0897 18.8034 17.5458 19 18.0002 19C18.4545 19 18.9106 18.8034 19.2618 18.4193L20.2619 17.3253C20.6346 16.9177 21.2671 16.8893 21.6747 17.2619Z",
+ "fill": "currentColor"
+ },
+ "children": []
+ },
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "fill-rule": "evenodd",
+ "clip-rule": "evenodd",
+ "d": "M15.793 2.79287C17.0119 1.57393 18.9882 1.57392 20.2072 2.79287C21.4261 4.01183 21.4261 5.98814 20.2072 7.20709L7.64443 19.7698C7.62463 19.7896 7.60502 19.8093 7.58556 19.8288C7.29811 20.1168 7.04467 20.3707 6.73914 20.5579C6.47072 20.7224 6.17809 20.8436 5.87198 20.9171C5.52353 21.0007 5.16478 21.0004 4.75788 21C4.73034 21 4.70258 21 4.67458 21H3.00004C2.44776 21 2.00004 20.5523 2.00004 20V18.3255C2.00004 18.2975 2.00001 18.2697 1.99999 18.2422C1.99961 17.8353 1.99928 17.4765 2.08293 17.1281C2.15642 16.822 2.27763 16.5293 2.44212 16.2609C2.62936 15.9554 2.88327 15.7019 3.17125 15.4145C3.19075 15.395 3.2104 15.3754 3.23019 15.3556L15.793 2.79287Z",
+ "fill": "currentColor"
+ },
+ "children": []
+ }
+ ]
+ },
+ "name": "Edit04"
+}
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/vender/solid/general/Edit04.tsx b/web/app/components/base/icons/src/vender/solid/general/Edit04.tsx
new file mode 100644
index 0000000000..d918872d91
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/solid/general/Edit04.tsx
@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Edit04.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,
+) => )
+
+Icon.displayName = 'Edit04'
+
+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 dba2667c63..3b9307b6a7 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,6 +1,7 @@
export { default as CheckCircle } from './CheckCircle'
export { default as CheckDone01 } from './CheckDone01'
export { default as Download02 } from './Download02'
+export { default as Edit04 } from './Edit04'
export { default as Eye } from './Eye'
export { default as MessageClockCircle } from './MessageClockCircle'
export { default as Target04 } from './Target04'
diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx
index b3cc78f37b..d8cb6ce256 100644
--- a/web/app/components/base/markdown.tsx
+++ b/web/app/components/base/markdown.tsx
@@ -81,11 +81,11 @@ const useLazyLoad = (ref: RefObject): boolean => {
return isIntersecting
}
-export function Markdown(props: { content: string }) {
+export function Markdown(props: { content: string; className?: string }) {
const [isCopied, setIsCopied] = useState(false)
const [isSVG, setIsSVG] = useState(false)
return (
-
- { (language === 'mermaid' && isSVG)
+ {(language === 'mermaid' && isSVG)
? ( )
: ( void
+ onRemove: () => void
+ text?: string
+ children?: JSX.Element
+}
+
+const DeleteConfirmModal: FC = ({
+ isShow,
+ onHide,
+ onRemove,
+ children,
+ text,
+}) => {
+ const { t } = useTranslation()
+ if (!isShow)
+ return null
+
+ return (
+
+ {
+ e.stopPropagation()
+ e.stopPropagation()
+ e.nativeEvent.stopImmediatePropagation()
+ }}>
+
+ {text
+ ? (
+
{text}
+ )
+ : children}
+
+
+ {t('common.operation.cancel')}
+
+ {t('common.operation.sure')}
+
+
+
+
+ )
+}
+export default React.memo(DeleteConfirmModal)
diff --git a/web/app/components/base/modal/delete-confirm-modal/style.module.css b/web/app/components/base/modal/delete-confirm-modal/style.module.css
new file mode 100644
index 0000000000..3e4953f47f
--- /dev/null
+++ b/web/app/components/base/modal/delete-confirm-modal/style.module.css
@@ -0,0 +1,16 @@
+.delModal {
+ background: linear-gradient(180deg,
+ rgba(217, 45, 32, 0.05) 0%,
+ rgba(217, 45, 32, 0) 24.02%),
+ #f9fafb;
+ box-shadow: 0px 20px 24px -4px rgba(16, 24, 40, 0.08),
+ 0px 8px 8px -4px rgba(16, 24, 40, 0.03);
+ @apply rounded-2xl p-8;
+}
+
+.warningWrapper {
+ box-shadow: 0px 20px 24px -4px rgba(16, 24, 40, 0.08),
+ 0px 8px 8px -4px rgba(16, 24, 40, 0.03);
+ background: rgba(255, 255, 255, 0.9);
+ @apply h-12 w-12 border-[0.5px] border-gray-100 rounded-xl mb-3 flex items-center justify-center;
+}
\ No newline at end of file
diff --git a/web/app/components/base/modal/index.tsx b/web/app/components/base/modal/index.tsx
index 3eea042ef8..b228fed6fc 100644
--- a/web/app/components/base/modal/index.tsx
+++ b/web/app/components/base/modal/index.tsx
@@ -63,8 +63,13 @@ export default function Modal({
{description}
}
{closable
- &&
-
+ &&
+ {
+ e.stopPropagation()
+ onClose()
+ }
+ } />
}
{children}
diff --git a/web/app/components/base/tab-slider-plain/index.tsx b/web/app/components/base/tab-slider-plain/index.tsx
new file mode 100644
index 0000000000..78a88231c7
--- /dev/null
+++ b/web/app/components/base/tab-slider-plain/index.tsx
@@ -0,0 +1,68 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import cn from 'classnames'
+
+type Option = {
+ value: string
+ text: string | JSX.Element
+}
+
+type ItemProps = {
+ className?: string
+ isActive: boolean
+ onClick: (v: string) => void
+ option: Option
+}
+const Item: FC
= ({
+ className,
+ isActive,
+ onClick,
+ option,
+}) => {
+ return (
+ !isActive && onClick(option.value)}
+ >
+
{option.text}
+ {isActive && (
+
+ )}
+
+ )
+}
+
+type Props = {
+ className?: string
+ value: string
+ onChange: (v: string) => void
+ options: Option[]
+ noBorderBottom?: boolean
+ itemClassName?: string
+}
+
+const TabSlider: FC = ({
+ className,
+ value,
+ onChange,
+ options,
+ noBorderBottom,
+ itemClassName,
+}) => {
+ return (
+
+ {options.map(option => (
+
+ ))}
+
+ )
+}
+export default React.memo(TabSlider)
diff --git a/web/app/components/billing/annotation-full/index.tsx b/web/app/components/billing/annotation-full/index.tsx
new file mode 100644
index 0000000000..7283828f29
--- /dev/null
+++ b/web/app/components/billing/annotation-full/index.tsx
@@ -0,0 +1,31 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from 'classnames'
+import UpgradeBtn from '../upgrade-btn'
+import Usage from './usage'
+import s from './style.module.css'
+import GridMask from '@/app/components/base/grid-mask'
+
+const AnnotationFull: FC = () => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
{t('billing.annotatedResponse.fullTipLine1')}
+
{t('billing.annotatedResponse.fullTipLine2')}
+
+
+
+
+
+
+
+
+ )
+}
+export default React.memo(AnnotationFull)
diff --git a/web/app/components/billing/annotation-full/modal.tsx b/web/app/components/billing/annotation-full/modal.tsx
new file mode 100644
index 0000000000..c30abeccb1
--- /dev/null
+++ b/web/app/components/billing/annotation-full/modal.tsx
@@ -0,0 +1,47 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from 'classnames'
+import UpgradeBtn from '../upgrade-btn'
+import Modal from '../../base/modal'
+import Usage from './usage'
+import s from './style.module.css'
+import GridMask from '@/app/components/base/grid-mask'
+
+type Props = {
+ show: boolean
+ onHide: () => void
+}
+const AnnotationFullModal: FC = ({
+ show,
+ onHide,
+}) => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
+
{t('billing.annotatedResponse.fullTipLine1')}
+
{t('billing.annotatedResponse.fullTipLine2')}
+
+
+
+
+
+
+
+
+
+
+ )
+}
+export default React.memo(AnnotationFullModal)
diff --git a/web/app/components/billing/annotation-full/style.module.css b/web/app/components/billing/annotation-full/style.module.css
new file mode 100644
index 0000000000..7ad3180a5a
--- /dev/null
+++ b/web/app/components/billing/annotation-full/style.module.css
@@ -0,0 +1,7 @@
+.textGradient {
+ background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ text-fill-color: transparent;
+}
\ No newline at end of file
diff --git a/web/app/components/billing/annotation-full/usage.tsx b/web/app/components/billing/annotation-full/usage.tsx
new file mode 100644
index 0000000000..44a97deda5
--- /dev/null
+++ b/web/app/components/billing/annotation-full/usage.tsx
@@ -0,0 +1,32 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { MessageFastPlus } from '../../base/icons/src/vender/line/communication'
+import UsageInfo from '../usage-info'
+import { useProviderContext } from '@/context/provider-context'
+
+type Props = {
+ className?: string
+}
+
+const Usage: FC = ({
+ className,
+}) => {
+ const { t } = useTranslation()
+ const { plan } = useProviderContext()
+ const {
+ usage,
+ total,
+ } = plan
+ return (
+
+ )
+}
+export default React.memo(Usage)
diff --git a/web/app/components/billing/config.ts b/web/app/components/billing/config.ts
index c995502917..435b78c77c 100644
--- a/web/app/components/billing/config.ts
+++ b/web/app/components/billing/config.ts
@@ -63,10 +63,12 @@ export const defaultPlan = {
vectorSpace: 1,
buildApps: 1,
teamMembers: 1,
+ annotatedResponse: 1,
},
total: {
vectorSpace: 10,
buildApps: 10,
teamMembers: 1,
+ annotatedResponse: 10,
},
}
diff --git a/web/app/components/billing/progress-bar/index.tsx b/web/app/components/billing/progress-bar/index.tsx
index 62acde8092..ec2ffcf627 100644
--- a/web/app/components/billing/progress-bar/index.tsx
+++ b/web/app/components/billing/progress-bar/index.tsx
@@ -7,7 +7,7 @@ const ProgressBar = ({
color = '#2970FF',
}: ProgressBarProps) => {
return (
-
+
+export type UsagePlanInfo = Pick
export enum DocumentProcessingPriority {
standard = 'standard',
@@ -48,6 +48,10 @@ export type CurrentPlanInfoBackend = {
size: number
limit: number // total. 0 means unlimited
}
+ annotation_quota_limit: {
+ size: number
+ limit: number // total. 0 means unlimited
+ }
docs_processing: DocumentProcessingPriority
}
diff --git a/web/app/components/billing/utils/index.ts b/web/app/components/billing/utils/index.ts
index 462b0500a1..405d8656a1 100644
--- a/web/app/components/billing/utils/index.ts
+++ b/web/app/components/billing/utils/index.ts
@@ -15,11 +15,13 @@ export const parseCurrentPlan = (data: CurrentPlanInfoBackend) => {
vectorSpace: data.vector_space.size,
buildApps: data.apps?.size || 0,
teamMembers: data.members.size,
+ annotatedResponse: data.annotation_quota_limit.size,
},
total: {
vectorSpace: parseLimit(data.vector_space.limit),
buildApps: parseLimit(data.apps?.limit) || 0,
teamMembers: parseLimit(data.members.limit),
+ annotatedResponse: parseLimit(data.annotation_quota_limit.limit),
},
}
}
diff --git a/web/app/components/header/account-setting/model-page/model-selector/portal-select.tsx b/web/app/components/header/account-setting/model-page/model-selector/portal-select.tsx
new file mode 100644
index 0000000000..19ad97c399
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/model-selector/portal-select.tsx
@@ -0,0 +1,358 @@
+import type { FC } from 'react'
+import React, { Fragment, useEffect, useRef, useState } from 'react'
+import useSWR from 'swr'
+import { useTranslation } from 'react-i18next'
+import _ from 'lodash-es'
+import cn from 'classnames'
+import ModelModal from '../model-modal'
+import cohereConfig from '../configs/cohere'
+import s from './style.module.css'
+import type { BackendModel, FormValue, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
+import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
+import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
+import { Check, LinkExternal01, SearchLg } from '@/app/components/base/icons/src/vender/line/general'
+import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
+import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
+import Tooltip from '@/app/components/base/tooltip'
+import ModelIcon from '@/app/components/app/configuration/config-model/model-icon'
+import ModelName from '@/app/components/app/configuration/config-model/model-name'
+import ProviderName from '@/app/components/app/configuration/config-model/provider-name'
+import { useProviderContext } from '@/context/provider-context'
+import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/model-mode-type-label'
+import type { ModelModeType } from '@/types/app'
+import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
+import { useModalContext } from '@/context/modal-context'
+import { useEventEmitterContextContext } from '@/context/event-emitter'
+import { fetchDefaultModal, setModelProvider } from '@/service/common'
+import { useToastContext } from '@/app/components/base/toast'
+import {
+ PortalToFollowElem,
+ PortalToFollowElemContent,
+ PortalToFollowElemTrigger,
+} from '@/app/components/base/portal-to-follow-elem'
+
+type Props = {
+ value: {
+ providerName: ProviderEnum
+ modelName: string
+ } | undefined
+ modelType: ModelType
+ isShowModelModeType?: boolean
+ isShowAddModel?: boolean
+ supportAgentThought?: boolean
+ onChange: (value: BackendModel) => void
+ popClassName?: string
+ readonly?: boolean
+ triggerIconSmall?: boolean
+ whenEmptyGoToSetting?: boolean
+ onUpdate?: () => void
+ widthSameToTrigger?: boolean
+}
+
+type ModelOption = {
+ type: 'model'
+ value: string
+ providerName: ProviderEnum
+ modelDisplayName: string
+ model_mode: ModelModeType
+} | {
+ type: 'provider'
+ value: ProviderEnum
+}
+
+const ModelSelector: FC = ({
+ value,
+ modelType,
+ isShowModelModeType,
+ isShowAddModel,
+ supportAgentThought,
+ onChange,
+ popClassName,
+ readonly,
+ triggerIconSmall,
+ whenEmptyGoToSetting,
+ onUpdate,
+ widthSameToTrigger,
+}) => {
+ const { t } = useTranslation()
+ const { setShowAccountSettingModal } = useModalContext()
+ const {
+ textGenerationModelList,
+ embeddingsModelList,
+ speech2textModelList,
+ rerankModelList,
+ agentThoughtModelList,
+ updateModelList,
+ } = useProviderContext()
+ const [search, setSearch] = useState('')
+ const modelList = supportAgentThought
+ ? agentThoughtModelList
+ : ({
+ [ModelType.textGeneration]: textGenerationModelList,
+ [ModelType.embeddings]: embeddingsModelList,
+ [ModelType.speech2text]: speech2textModelList,
+ [ModelType.reranking]: rerankModelList,
+ })[modelType]
+ const currModel = modelList.find(item => item.model_name === value?.modelName && item.model_provider.provider_name === value.providerName)
+ const allModelNames = (() => {
+ if (!search)
+ return {}
+
+ const res: Record = {}
+ modelList.forEach(({ model_name, model_display_name }) => {
+ res[model_name] = model_display_name
+ })
+ return res
+ })()
+ const filteredModelList = search
+ ? modelList.filter(({ model_name }) => {
+ if (allModelNames[model_name].includes(search))
+ return true
+
+ return false
+ })
+ : modelList
+
+ const hasRemoved = (value && value.modelName && value.providerName) && !modelList.find(({ model_name, model_provider }) => model_name === value.modelName && model_provider.provider_name === value.providerName)
+
+ const modelOptions: ModelOption[] = (() => {
+ const providers = _.uniq(filteredModelList.map(item => item.model_provider.provider_name))
+ const res: ModelOption[] = []
+ providers.forEach((providerName) => {
+ res.push({
+ type: 'provider',
+ value: providerName,
+ })
+ const models = filteredModelList.filter(m => m.model_provider.provider_name === providerName)
+ models.forEach(({ model_name, model_display_name, model_mode }) => {
+ res.push({
+ type: 'model',
+ providerName,
+ value: model_name,
+ modelDisplayName: model_display_name,
+ model_mode,
+ })
+ })
+ })
+ return res
+ })()
+ const { eventEmitter } = useEventEmitterContextContext()
+ const [showRerankModal, setShowRerankModal] = useState(false)
+ const [shouldFetchRerankDefaultModel, setShouldFetchRerankDefaultModel] = useState(false)
+ const { notify } = useToastContext()
+ const { data: rerankDefaultModel } = useSWR(shouldFetchRerankDefaultModel ? '/workspaces/current/default-model?model_type=reranking' : null, fetchDefaultModal)
+ const handleOpenRerankModal = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ setShowRerankModal(true)
+ }
+ const handleRerankModalSave = async (originValue?: FormValue) => {
+ if (originValue) {
+ try {
+ eventEmitter?.emit('provider-save')
+ const res = await setModelProvider({
+ url: `/workspaces/current/model-providers/${cohereConfig.modal.key}`,
+ body: {
+ config: originValue,
+ },
+ })
+ if (res.result === 'success') {
+ notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
+ updateModelList(ModelType.reranking)
+ setShowRerankModal(false)
+ setShouldFetchRerankDefaultModel(true)
+ if (onUpdate)
+ onUpdate()
+ }
+ eventEmitter?.emit('')
+ }
+ catch (e) {
+ eventEmitter?.emit('')
+ }
+ }
+ }
+
+ const [open, setOpen] = useState(false)
+ const triggerRef = useRef(null)
+
+ useEffect(() => {
+ if (rerankDefaultModel && whenEmptyGoToSetting)
+ onChange(rerankDefaultModel)
+ }, [rerankDefaultModel])
+
+ return (
+
+
+
setOpen(v => !v)} className={cn('flex items-center px-2.5 w-full h-9 rounded-lg', readonly ? '!cursor-auto bg-gray-100 opacity-50' : 'bg-gray-100', hasRemoved && '!bg-[#FEF3F2]')}>
+ {
+
+ {
+ (value && value.modelName && value.providerName)
+ ? (
+ <>
+
+
+
+ {isShowModelModeType && (
+
+ )}
+
+ >
+ )
+ : whenEmptyGoToSetting
+ ? (
+
+
+
+ {t('common.modelProvider.selector.rerankTip')}
+
+
+
+ )
+ : (
+
{t('common.modelProvider.selectModel')}
+ )
+ }
+ {
+ hasRemoved && (
+
{t('common.modelProvider.selector.tip')}
+ }
+ >
+
+
+ )
+ }
+ {
+ !readonly && !whenEmptyGoToSetting && (
+
+ )
+ }
+ {
+ whenEmptyGoToSetting && (value && value.modelName && value.providerName) && (
+
+ )
+ }
+
+ }
+
+ {!readonly && (
+
+
+
+
+
+ setSearch(e.target.value)}
+ className={`
+ block w-full h-8 bg-transparent text-[13px] text-gray-700
+ outline-none appearance-none border-none
+ `}
+ placeholder={t('common.modelProvider.searchModel') || ''}
+ />
+
+ {
+ search && (
+
setSearch('')}>
+
+
+ )
+ }
+
+
+ {
+ modelOptions.map((model) => {
+ if (model.type === 'provider') {
+ return (
+
+ )
+ }
+
+ if (model.type === 'model') {
+ return (
+ {
+ const selectedModel = modelList.find((item) => {
+ return item.model_name === model.value && item.model_provider.provider_name === model.providerName
+ })
+ onChange(selectedModel as BackendModel)
+ setOpen(false)
+ }}
+ >
+
+
+
+ {isShowModelModeType && (
+
+ )}
+
+ {(value?.providerName === model.providerName && value?.modelName === model.value) &&
}
+
+ )
+ }
+
+ return null
+ })
+ }
+ {modelList.length !== 0 && (search && filteredModelList.length === 0) && (
+ {t('common.modelProvider.noModelFound', { model: search })}
+ )}
+
+ {isShowAddModel && (
+ setShowAccountSettingModal({ payload: 'provider' })}
+ >
+
+
{t('common.model.addMoreModel')}
+
+ )}
+
+ )}
+
+
setShowRerankModal(false)}
+ onSave={handleRerankModalSave}
+ mode={'add'}
+ />
+
+ )
+}
+
+export default ModelSelector
diff --git a/web/app/components/share/chat/index.tsx b/web/app/components/share/chat/index.tsx
index 18164c4756..fa9142cb40 100644
--- a/web/app/components/share/chat/index.tsx
+++ b/web/app/components/share/chat/index.tsx
@@ -613,6 +613,22 @@ const Main: FC = ({
))
}
},
+ onAnnotationReply: (annotationReply) => {
+ responseItem.content = annotationReply.answer
+ 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,
+ id: annotationReply.id,
+ })
+ })
+ setChatList(newListWithAnswer)
+ tempNewConversationId = annotationReply.conversation_id
+ },
onError() {
setResponsingFalse()
// role back placeholder answer
diff --git a/web/app/components/share/text-generation/result/index.tsx b/web/app/components/share/text-generation/result/index.tsx
index 0ff746b528..b71bbec807 100644
--- a/web/app/components/share/text-generation/result/index.tsx
+++ b/web/app/components/share/text-generation/result/index.tsx
@@ -14,6 +14,7 @@ import type { PromptConfig } from '@/models/debug'
import type { InstalledApp } from '@/models/explore'
import type { ModerationService } from '@/models/common'
import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app'
+
export type IResultProps = {
isCallBatchAPI: boolean
isPC: boolean
diff --git a/web/config/index.ts b/web/config/index.ts
index b3db24d281..9a530a0849 100644
--- a/web/config/index.ts
+++ b/web/config/index.ts
@@ -146,3 +146,9 @@ export const DATASET_DEFAULT = {
top_k: 2,
score_threshold: 0.5,
}
+
+export const APP_PAGE_LIMIT = 10
+
+export const ANNOTATION_DEFAULT = {
+ score_threshold: 0.9,
+}
diff --git a/web/context/debug-configuration.ts b/web/context/debug-configuration.ts
index 9fc629e1ed..17d61de166 100644
--- a/web/context/debug-configuration.ts
+++ b/web/context/debug-configuration.ts
@@ -1,6 +1,7 @@
import { createContext } from 'use-context-selector'
import { PromptMode } from '@/models/debug'
import type {
+ AnnotationReplyConfig,
BlockStatus,
ChatPromptConfig,
CitationConfig,
@@ -21,7 +22,7 @@ import type { ExternalDataTool } from '@/models/common'
import type { DataSet } from '@/models/datasets'
import type { VisionSettings } from '@/types/app'
import { ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app'
-import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
+import { ANNOTATION_DEFAULT, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
type IDebugConfiguration = {
appId: string
@@ -58,6 +59,8 @@ type IDebugConfiguration = {
setSpeechToTextConfig: (speechToTextConfig: SpeechToTextConfig) => void
citationConfig: CitationConfig
setCitationConfig: (citationConfig: CitationConfig) => void
+ annotationConfig: AnnotationReplyConfig
+ setAnnotationConfig: (annotationConfig: AnnotationReplyConfig) => void
moderationConfig: ModerationConfig
setModerationConfig: (moderationConfig: ModerationConfig) => void
externalDataToolsConfig: ExternalDataTool[]
@@ -138,13 +141,23 @@ const DebugConfigurationContext = createContext({
citationConfig: {
enabled: false,
},
- setCitationConfig: () => {},
+ setCitationConfig: () => { },
moderationConfig: {
enabled: false,
},
- setModerationConfig: () => {},
+ annotationConfig: {
+ id: '',
+ enabled: false,
+ score_threshold: ANNOTATION_DEFAULT.score_threshold,
+ embedding_model: {
+ embedding_model_name: '',
+ embedding_provider_name: '',
+ },
+ },
+ setAnnotationConfig: () => { },
+ setModerationConfig: () => { },
externalDataToolsConfig: [],
- setExternalDataToolsConfig: () => {},
+ setExternalDataToolsConfig: () => { },
formattingChanged: false,
setFormattingChanged: () => { },
inputs: {},
@@ -189,7 +202,7 @@ const DebugConfigurationContext = createContext({
score_threshold_enabled: false,
score_threshold: 0.7,
},
- setDatasetConfigs: () => {},
+ setDatasetConfigs: () => { },
hasSetContextVar: false,
isShowVisionConfig: false,
visionConfig: {
@@ -198,7 +211,7 @@ const DebugConfigurationContext = createContext({
detail: Resolution.low,
transfer_methods: [TransferMethod.remote_url],
},
- setVisionConfig: () => {},
+ setVisionConfig: () => { },
})
export default DebugConfigurationContext
diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx
index 7b58f1838e..6a2aaba26c 100644
--- a/web/context/modal-context.tsx
+++ b/web/context/modal-context.tsx
@@ -8,6 +8,8 @@ import AccountSetting from '@/app/components/header/account-setting'
import ApiBasedExtensionModal from '@/app/components/header/account-setting/api-based-extension-page/modal'
import ModerationSettingModal from '@/app/components/app/configuration/toolbox/moderation/moderation-setting-modal'
import ExternalDataToolModal from '@/app/components/app/configuration/tools/external-data-tool-modal'
+import AnnotationFullModal from '@/app/components/billing/annotation-full/modal'
+
import Pricing from '@/app/components/billing/pricing'
import type { ModerationConfig } from '@/models/debug'
import type {
@@ -28,13 +30,15 @@ const ModalContext = createContext<{
setShowModerationSettingModal: Dispatch | null>>
setShowExternalDataToolModal: Dispatch | null>>
setShowPricingModal: Dispatch>
+ setShowAnnotationFullModal: () => void
}>({
- setShowAccountSettingModal: () => {},
- setShowApiBasedExtensionModal: () => {},
- setShowModerationSettingModal: () => {},
- setShowExternalDataToolModal: () => {},
- setShowPricingModal: () => {},
-})
+ setShowAccountSettingModal: () => { },
+ setShowApiBasedExtensionModal: () => { },
+ setShowModerationSettingModal: () => { },
+ setShowExternalDataToolModal: () => { },
+ setShowPricingModal: () => { },
+ setShowAnnotationFullModal: () => { },
+ })
export const useModalContext = () => useContext(ModalContext)
@@ -51,7 +55,7 @@ export const ModalContextProvider = ({
const searchParams = useSearchParams()
const router = useRouter()
const [showPricingModal, setShowPricingModal] = useState(searchParams.get('show-pricing') === '1')
-
+ const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false)
const handleCancelAccountSettingModal = () => {
setShowAccountSettingModal(null)
@@ -101,6 +105,7 @@ export const ModalContextProvider = ({
setShowModerationSettingModal,
setShowExternalDataToolModal,
setShowPricingModal: () => setShowPricingModal(true),
+ setShowAnnotationFullModal: () => setShowAnnotationFullModal(true),
}}>
<>
{children}
@@ -152,6 +157,14 @@ export const ModalContextProvider = ({
}} />
)
}
+
+ {
+ showAnnotationFullModal && (
+ setShowAnnotationFullModal(false)} />
+ )
+ }
>
)
diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx
index d8f62e6017..7978030c97 100644
--- a/web/context/provider-context.tsx
+++ b/web/context/provider-context.tsx
@@ -22,6 +22,7 @@ const ProviderContext = createContext<{
textGenerationDefaultModel?: BackendModel
mutateTextGenerationDefaultModel: () => void
embeddingsDefaultModel?: BackendModel
+ isEmbeddingsDefaultModelValid: boolean
mutateEmbeddingsDefaultModel: () => void
speech2textDefaultModel?: BackendModel
mutateSpeech2textDefaultModel: () => void
@@ -42,16 +43,17 @@ const ProviderContext = createContext<{
speech2textModelList: [],
rerankModelList: [],
agentThoughtModelList: [],
- updateModelList: () => {},
+ updateModelList: () => { },
textGenerationDefaultModel: undefined,
- mutateTextGenerationDefaultModel: () => {},
+ mutateTextGenerationDefaultModel: () => { },
speech2textDefaultModel: undefined,
- mutateSpeech2textDefaultModel: () => {},
+ mutateSpeech2textDefaultModel: () => { },
embeddingsDefaultModel: undefined,
- mutateEmbeddingsDefaultModel: () => {},
+ isEmbeddingsDefaultModelValid: false,
+ mutateEmbeddingsDefaultModel: () => { },
rerankDefaultModel: undefined,
isRerankDefaultModelVaild: false,
- mutateRerankDefaultModel: () => {},
+ mutateRerankDefaultModel: () => { },
supportRetrievalMethods: [],
plan: {
type: Plan.sandbox,
@@ -59,11 +61,13 @@ const ProviderContext = createContext<{
vectorSpace: 32,
buildApps: 12,
teamMembers: 1,
+ annotatedResponse: 1,
},
total: {
vectorSpace: 200,
buildApps: 50,
teamMembers: 1,
+ annotatedResponse: 10,
},
},
isFetchedPlan: false,
@@ -97,6 +101,10 @@ export const ProviderContextProvider = ({
item => item.model_name === rerankDefaultModel?.model_name && item.model_provider.provider_name === rerankDefaultModel?.model_provider.provider_name,
)
+ const isEmbeddingsDefaultModelValid = !!embeddingsModelList?.find(
+ item => item.model_name === embeddingsDefaultModel?.model_name && item.model_provider.provider_name === embeddingsDefaultModel?.model_provider.provider_name,
+ )
+
const updateModelList = (type: ModelType) => {
if (type === ModelType.textGeneration)
mutateTextGenerationModelList()
@@ -118,6 +126,13 @@ export const ProviderContextProvider = ({
setEnableBilling(enabled)
if (enabled) {
setPlan(parseCurrentPlan(data))
+ // setPlan(parseCurrentPlan({
+ // ...data,
+ // annotation_quota_limit: {
+ // ...data.annotation_quota_limit,
+ // limit: 10,
+ // },
+ // }))
setIsFetchedPlan(true)
}
})()
@@ -139,6 +154,7 @@ export const ProviderContextProvider = ({
mutateSpeech2textDefaultModel,
rerankDefaultModel,
isRerankDefaultModelVaild,
+ isEmbeddingsDefaultModelValid,
mutateRerankDefaultModel,
supportRetrievalMethods: supportRetrievalMethods?.retrieval_method || [],
plan,
diff --git a/web/i18n/i18next-config.ts b/web/i18n/i18next-config.ts
index 71a0769930..ac121cc1e3 100644
--- a/web/i18n/i18next-config.ts
+++ b/web/i18n/i18next-config.ts
@@ -19,6 +19,8 @@ import appApiEn from './lang/app-api.en'
import appApiZh from './lang/app-api.zh'
import appLogEn from './lang/app-log.en'
import appLogZh from './lang/app-log.zh'
+import appAnnotationEn from './lang/app-annotation.en'
+import appAnnotationZh from './lang/app-annotation.zh'
import shareEn from './lang/share-app.en'
import shareZh from './lang/share-app.zh'
import datasetEn from './lang/dataset.en'
@@ -49,6 +51,7 @@ const resources = {
appDebug: appDebugEn,
appApi: appApiEn,
appLog: appLogEn,
+ appAnnotation: appAnnotationEn,
// share
share: shareEn,
dataset: datasetEn,
@@ -73,6 +76,7 @@ const resources = {
appDebug: appDebugZh,
appApi: appApiZh,
appLog: appLogZh,
+ appAnnotation: appAnnotationZh,
// share
share: shareZh,
dataset: datasetZh,
diff --git a/web/i18n/lang/app-annotation.en.ts b/web/i18n/lang/app-annotation.en.ts
new file mode 100644
index 0000000000..ae59a25234
--- /dev/null
+++ b/web/i18n/lang/app-annotation.en.ts
@@ -0,0 +1,87 @@
+const translation = {
+ title: 'Annotations',
+ name: 'Annotated Response',
+ editBy: 'Answer edited by {{author}}',
+ noData: {
+ title: 'No annotations',
+ description: 'You can edit annotations in app debuggiung, or import annotations in bulk here for high-quality response.',
+ },
+ table: {
+ header: {
+ question: 'question',
+ answer: 'answer',
+ createdAt: 'created at',
+ hits: 'hits',
+ actions: 'actions',
+ addAnnotation: 'Add Annotation',
+ bulkImport: 'Bulk Import',
+ bulkExport: 'Bulk Export',
+ clearAll: 'Clear All Annotation',
+ },
+ },
+ editModal: {
+ title: 'Edit Annotated Response',
+ queryName: 'User Query',
+ answerName: 'Storyteller Bot',
+ yourAnswer: 'Your Answer',
+ answerPlaceholder: 'Type your answer here',
+ yourQuery: 'Your Query',
+ queryPlaceholder: 'Type your query here',
+ removeThisCache: 'Remove this Annotation',
+ createdAt: 'Created At',
+ },
+ addModal: {
+ title: 'Add Annotated Response',
+ queryName: 'Question',
+ answerName: 'Answer',
+ answerPlaceholder: 'Type answer here',
+ queryPlaceholder: 'Type query here',
+ createNext: 'Add another annotated response',
+ },
+ batchModal: {
+ title: 'Bulk Import',
+ csvUploadTitle: 'Drag and drop your CSV file here, or ',
+ browse: 'browse',
+ tip: 'The CSV file must conform to the following structure:',
+ question: 'question',
+ answer: 'answer',
+ contentTitle: 'chunk content',
+ content: 'content',
+ template: 'Download the template here',
+ cancel: 'Cancel',
+ run: 'Run Batch',
+ runError: 'Run batch failed',
+ processing: 'In batch processing',
+ completed: 'Import completed',
+ error: 'Import Error',
+ ok: 'OK',
+ },
+ errorMessage: {
+ answerRequired: 'Answer is required',
+ queryRequired: 'Question is required',
+ },
+ viewModal: {
+ annotatedResponse: 'Annotated Response',
+ hitHistory: 'Hit History',
+ hit: 'Hit',
+ hits: 'Hits',
+ noHitHistory: 'No hit history',
+ },
+ hitHistoryTable: {
+ query: 'Query',
+ match: 'Match',
+ response: 'Response',
+ source: 'Source',
+ score: 'Score',
+ time: 'Time',
+ },
+ initSetup: {
+ title: 'Annotated Response Initial Setup',
+ configTitle: 'Annotated Response Setup',
+ confirmBtn: 'Save & Enable',
+ configConfirmBtn: 'Save',
+ },
+ embeddingModelSwitchTip: 'Annotation text vectorization model, switching models will be re-embedded, resulting in additional costs.',
+}
+
+export default translation
diff --git a/web/i18n/lang/app-annotation.zh.ts b/web/i18n/lang/app-annotation.zh.ts
new file mode 100644
index 0000000000..3a6cacf5b5
--- /dev/null
+++ b/web/i18n/lang/app-annotation.zh.ts
@@ -0,0 +1,90 @@
+const translation = {
+ title: '标注',
+ name: '标注回复',
+ editBy: '{{author}}编辑的答案',
+ noData: {
+ title: '没有标注',
+ description: '你可以在应用会话调试中编辑标注,也可以在此批量导入标注用于高质量回复。',
+ },
+ table: {
+ header: {
+ question: '提问',
+ match: '匹配',
+ response: '回复',
+ answer: '答案',
+ createdAt: '创建时间',
+ hits: '命中次数',
+ actions: '操作',
+ addAnnotation: '添加标注',
+ bulkImport: '批量导入',
+ bulkExport: '批量导出',
+ clearAll: '删除所有标注',
+ },
+ },
+ editModal: {
+ title: '编辑标注回复',
+ queryName: '用户提问',
+ answerName: '机器回复',
+ yourAnswer: '您的回复',
+ answerPlaceholder: '在这里输入您的回复',
+ yourQuery: '您的提问',
+ queryPlaceholder: '在这里输入您的提问',
+ removeThisCache: '删除此标注',
+ createdAt: '创建于',
+ },
+ addModal: {
+ title: '添加标注回复',
+ queryName: '提问',
+ answerName: '回复',
+ answerPlaceholder: '输入回复',
+ queryPlaceholder: '输入提问',
+ createNext: '添加下一个标注回复',
+ },
+ batchModal: {
+ title: '批量导入',
+ csvUploadTitle: '将您的 CSV 文件拖放到此处,或',
+ browse: '选择文件',
+ tip: 'CSV 文件必须符合以下结构:',
+ question: '问题',
+ answer: '回答',
+ contentTitle: '分段内容',
+ content: '内容',
+ template: '下载模板',
+ cancel: '取消',
+ run: '导入',
+ runError: '批量导入失败',
+ processing: '批量处理中',
+ completed: '导入完成',
+ error: '导入出错',
+ ok: '确定',
+ },
+ errorMessage: {
+ answerRequired: '回复不能为空',
+ queryRequired: '提问不能为空',
+ },
+ viewModal: {
+ annotatedResponse: '标注回复',
+ hitHistory: '命中历史',
+ hit: '次命中',
+ hits: '次命中',
+ noHitHistory: '没有命中历史',
+ },
+ hitHistoryTable: {
+ question: '问题',
+ query: '提问',
+ match: '匹配',
+ response: '回复',
+ source: '来源',
+ score: '分数',
+ time: '时间',
+ },
+ initSetup: {
+ title: '标注回复初始设置',
+ configTitle: '标注回复设置',
+ confirmBtn: '保存并启用',
+ configConfirmBtn: '保存',
+ },
+ embeddingModelSwitchTip: '标注文本向量化模型,切换模型会重新嵌入,产生额外费用消耗',
+}
+
+export default translation
diff --git a/web/i18n/lang/app-debug.en.ts b/web/i18n/lang/app-debug.en.ts
index 1b2b0f41fd..3c372d2c26 100644
--- a/web/i18n/lang/app-debug.en.ts
+++ b/web/i18n/lang/app-debug.en.ts
@@ -75,6 +75,27 @@ const translation = {
description: 'Once enabled, show source document and attributed section of the generated content.',
resDes: 'Citations and Attributions is enabled',
},
+ annotation: {
+ title: 'Annotated Response',
+ description: 'You can manually add high-quality response to the cache for prioritized matching with similar user questions.',
+ resDes: 'Annotation Response is enabled',
+ scoreThreshold: {
+ title: 'Score Threshold',
+ description: 'Used to set the similarity threshold for annotation response.',
+ easyMatch: 'Easy Match',
+ accurateMatch: 'Accurate Match',
+ },
+ matchVariable: {
+ title: 'Match Variable',
+ choosePlaceholder: 'Choose match variable',
+ },
+ cacheManagement: 'Annotations',
+ cached: 'Annotationed',
+ remove: 'Remove',
+ removeConfirm: 'Delete this annotation ?',
+ add: 'Add annotation',
+ edit: 'Edit annotation',
+ },
dataSet: {
title: 'Context',
noData: 'You can import Knowledge as context',
diff --git a/web/i18n/lang/app-debug.zh.ts b/web/i18n/lang/app-debug.zh.ts
index 2fde802d3b..a8a3ed70fe 100644
--- a/web/i18n/lang/app-debug.zh.ts
+++ b/web/i18n/lang/app-debug.zh.ts
@@ -75,6 +75,27 @@ const translation = {
description: '启用后,显示源文档和生成内容的归属部分。',
resDes: '引用和归属已启用',
},
+ annotation: {
+ title: '标注回复',
+ description: '启用后,将标注用户的回复,以便在用户重复提问时快速响应。',
+ resDes: '标注回复已启用',
+ scoreThreshold: {
+ title: '分数阈值',
+ description: '用于设置标注回复的匹配相似度阈值。',
+ easyMatch: '容易匹配',
+ accurateMatch: '精准匹配',
+ },
+ matchVariable: {
+ title: '匹配变量',
+ choosePlaceholder: '请选择变量',
+ },
+ cacheManagement: '标注管理',
+ cached: '已标注',
+ remove: '移除',
+ removeConfirm: '删除这个标注?',
+ add: '添加标注',
+ edit: '编辑标注',
+ },
dataSet: {
title: '上下文',
noData: '您可以导入知识库作为上下文',
diff --git a/web/i18n/lang/app-log.en.ts b/web/i18n/lang/app-log.en.ts
index 465535007d..4620ce8be4 100644
--- a/web/i18n/lang/app-log.en.ts
+++ b/web/i18n/lang/app-log.en.ts
@@ -1,5 +1,5 @@
const translation = {
- title: 'Logs & Annotations',
+ title: 'Logs',
description: 'The logs record the running status of the application, including user inputs and AI replies.',
dateTimeFormat: 'MM/DD/YYYY hh:mm A',
table: {
diff --git a/web/i18n/lang/app-log.zh.ts b/web/i18n/lang/app-log.zh.ts
index a300a6f6b9..c038bae3e5 100644
--- a/web/i18n/lang/app-log.zh.ts
+++ b/web/i18n/lang/app-log.zh.ts
@@ -1,5 +1,5 @@
const translation = {
- title: '日志与标注',
+ title: '日志',
description: '日志记录了应用的运行情况,包括用户的输入和 AI 的回复。',
dateTimeFormat: 'YYYY-MM-DD HH:mm',
table: {
diff --git a/web/i18n/lang/billing.en.ts b/web/i18n/lang/billing.en.ts
index c43f4650ce..955b3e45b0 100644
--- a/web/i18n/lang/billing.en.ts
+++ b/web/i18n/lang/billing.en.ts
@@ -98,6 +98,11 @@ const translation = {
fullTipLine1: 'Upgrade your plan to',
fullTipLine2: 'build more apps.',
},
+ annotatedResponse: {
+ fullTipLine1: 'Upgrade your plan to',
+ fullTipLine2: 'annotate more conversations.',
+ quotaTitle: 'Annotated Response Quota',
+ },
}
export default translation
diff --git a/web/i18n/lang/billing.zh.ts b/web/i18n/lang/billing.zh.ts
index 9c2c68860a..b668b50472 100644
--- a/web/i18n/lang/billing.zh.ts
+++ b/web/i18n/lang/billing.zh.ts
@@ -98,6 +98,11 @@ const translation = {
fullTipLine1: '升级您的套餐以',
fullTipLine2: '构建更多的程序。',
},
+ annotatedResponse: {
+ fullTipLine1: '升级您的套餐以',
+ fullTipLine2: '标注更多对话。',
+ quotaTitle: '标注的配额',
+ },
}
export default translation
diff --git a/web/i18n/lang/common.en.ts b/web/i18n/lang/common.en.ts
index 65e79003cf..75f201ef8a 100644
--- a/web/i18n/lang/common.en.ts
+++ b/web/i18n/lang/common.en.ts
@@ -1,6 +1,7 @@
const translation = {
api: {
success: 'Success',
+ actionSuccess: 'Action succeeded',
saved: 'Saved',
create: 'Created',
remove: 'Removed',
@@ -31,6 +32,7 @@ const translation = {
ok: 'OK',
log: 'Log',
learnMore: 'Learn More',
+ params: 'Params',
},
placeholder: {
input: 'Please enter',
@@ -236,6 +238,7 @@ const translation = {
embeddingModel: {
key: 'Embedding Model',
tip: 'Set the default model for document embedding processing of the Knowledge, both retrieval and import of the Knowledge use this Embedding model for vectorization processing. Switching will cause the vector dimension between the imported Knowledge and the question to be inconsistent, resulting in retrieval failure. To avoid retrieval failure, please do not switch this model at will.',
+ required: 'Embedding Model is required',
},
speechToTextModel: {
key: 'Speech-to-Text Model',
diff --git a/web/i18n/lang/common.zh.ts b/web/i18n/lang/common.zh.ts
index 2e0056b60f..3c5f56a678 100644
--- a/web/i18n/lang/common.zh.ts
+++ b/web/i18n/lang/common.zh.ts
@@ -1,6 +1,7 @@
const translation = {
api: {
success: '成功',
+ actionSuccess: '操作成功',
saved: '已保存',
create: '已创建',
remove: '已移除',
@@ -31,6 +32,7 @@ const translation = {
ok: '好的',
log: '日志',
learnMore: '了解更多',
+ params: '参数',
},
placeholder: {
input: '请输入',
@@ -236,6 +238,7 @@ const translation = {
embeddingModel: {
key: 'Embedding 模型',
tip: '设置知识库文档嵌入处理的默认模型,检索和导入知识库均使用该Embedding模型进行向量化处理,切换后将导致已导入的知识库与问题之间的向量维度不一致,从而导致检索失败。为避免检索失败,请勿随意切换该模型。',
+ required: '请选择 Embedding 模型',
},
speechToTextModel: {
key: '语音转文本模型',
diff --git a/web/models/debug.ts b/web/models/debug.ts
index 92e2cb3d94..eaac7cd57e 100644
--- a/web/models/debug.ts
+++ b/web/models/debug.ts
@@ -73,6 +73,16 @@ export type SpeechToTextConfig = MoreLikeThisConfig
export type CitationConfig = MoreLikeThisConfig
+export type AnnotationReplyConfig = {
+ id: string
+ enabled: boolean
+ score_threshold: number
+ embedding_model: {
+ embedding_provider_name: string
+ embedding_model_name: string
+ }
+}
+
export type ModerationContentConfig = {
enabled: boolean
preset_response?: string
diff --git a/web/models/log.ts b/web/models/log.ts
index ced5165ef7..c46c0ae0f1 100644
--- a/web/models/log.ts
+++ b/web/models/log.ts
@@ -51,13 +51,19 @@ export type ModelConfigDetail = {
completion_params: CompletionParamsType
}
-export type Annotation = {
+export type LogAnnotation = {
content: string
account: {
id: string
name: string
email: string
}
+}
+
+export type Annotation = {
+ id: string
+ authorName: string
+ logAnnotation?: LogAnnotation
created_at?: number
}
@@ -73,7 +79,16 @@ export type MessageContent = {
answer: string
provider_response_latency: number
created_at: number
- annotation: Annotation
+ annotation: LogAnnotation
+ annotation_hit_history: {
+ annotation_id: string
+ annotation_create_account: {
+ id: string
+ name: string
+ email: string
+ }
+ created_at: number
+ }
feedbacks: Array<{
rating: 'like' | 'dislike' | null
content: string | null
diff --git a/web/service/annotation.ts b/web/service/annotation.ts
new file mode 100644
index 0000000000..5096a4f58a
--- /dev/null
+++ b/web/service/annotation.ts
@@ -0,0 +1,65 @@
+import type { Fetcher } from 'swr'
+import { del, get, post } from './base'
+import type { AnnotationEnableStatus, AnnotationItemBasic, EmbeddingModelConfig } from '@/app/components/app/annotation/type'
+import { ANNOTATION_DEFAULT } from '@/config'
+
+export const fetchAnnotationConfig = (appId: string) => {
+ return get(`apps/${appId}/annotation-setting`)
+}
+export const updateAnnotationStatus = (appId: string, action: AnnotationEnableStatus, embeddingModel?: EmbeddingModelConfig, score?: number) => {
+ let body: any = {
+ score_threshold: score || ANNOTATION_DEFAULT.score_threshold,
+ }
+ if (embeddingModel) {
+ body = {
+ ...body,
+ ...embeddingModel,
+ }
+ }
+
+ return post(`apps/${appId}/annotation-reply/${action}`, {
+ body,
+ })
+}
+
+export const updateAnnotationScore = (appId: string, settingId: string, score: number) => {
+ return post(`apps/${appId}/annotation-settings/${settingId}`, {
+ body: { score_threshold: score },
+ })
+}
+
+export const queryAnnotationJobStatus = (appId: string, action: AnnotationEnableStatus, jobId: string) => {
+ return get(`apps/${appId}/annotation-reply/${action}/status/${jobId}`)
+}
+
+export const fetchAnnotationList = (appId: string, params: Record) => {
+ return get(`apps/${appId}/annotations`, { params })
+}
+
+export const fetchExportAnnotationList = (appId: string) => {
+ return get(`apps/${appId}/annotations/export`)
+}
+
+export const addAnnotation = (appId: string, body: AnnotationItemBasic) => {
+ return post(`apps/${appId}/annotations`, { body })
+}
+
+export const annotationBatchImport: Fetcher<{ job_id: string; job_status: string }, { url: string; body: FormData }> = ({ url, body }) => {
+ return post<{ job_id: string; job_status: string }>(url, { body }, { bodyStringify: false, deleteContentType: true })
+}
+
+export const checkAnnotationBatchImportProgress: Fetcher<{ job_id: string; job_status: string }, { jobID: string; appId: string }> = ({ jobID, appId }) => {
+ return get<{ job_id: string; job_status: string }>(`/apps/${appId}/annotations/batch-import-status/${jobID}`)
+}
+
+export const editAnnotation = (appId: string, annotationId: string, body: AnnotationItemBasic) => {
+ return post(`apps/${appId}/annotations/${annotationId}`, { body })
+}
+
+export const delAnnotation = (appId: string, annotationId: string) => {
+ return del(`apps/${appId}/annotations/${annotationId}`)
+}
+
+export const fetchHitHistoryList = (appId: string, annotationId: string, params: Record) => {
+ return get(`apps/${appId}/annotations/${annotationId}/hit-histories`, { params })
+}
diff --git a/web/service/base.ts b/web/service/base.ts
index 62ba655387..60882dc4b9 100644
--- a/web/service/base.ts
+++ b/web/service/base.ts
@@ -1,6 +1,6 @@
import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
import Toast from '@/app/components/base/toast'
-import type { MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/app/chat/type'
+import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/app/chat/type'
const TIME_OUT = 100000
@@ -34,6 +34,7 @@ export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDa
export type IOnThought = (though: ThoughtItem) => void
export type IOnMessageEnd = (messageEnd: MessageEnd) => void
export type IOnMessageReplace = (messageReplace: MessageReplace) => void
+export type IOnAnnotationReply = (messageReplace: AnnotationReply) => void
export type IOnCompleted = (hasError?: boolean) => void
export type IOnError = (msg: string, code?: string) => void
@@ -46,6 +47,7 @@ type IOtherOptions = {
onThought?: IOnThought
onMessageEnd?: IOnMessageEnd
onMessageReplace?: IOnMessageReplace
+ onAnnotationReply?: IOnAnnotationReply
onError?: IOnError
onCompleted?: IOnCompleted // for stream
getAbortController?: (abortController: AbortController) => void
@@ -79,7 +81,7 @@ export function format(text: string) {
return res.replaceAll('\n', ' ').replaceAll('```', '')
}
-const handleStream = (response: Response, onData: IOnData, onCompleted?: IOnCompleted, onThought?: IOnThought, onMessageEnd?: IOnMessageEnd, onMessageReplace?: IOnMessageReplace) => {
+const handleStream = (response: Response, onData: IOnData, onCompleted?: IOnCompleted, onThought?: IOnThought, onMessageEnd?: IOnMessageEnd, onMessageReplace?: IOnMessageReplace, onAnnotationReply?: IOnAnnotationReply) => {
if (!response.ok)
throw new Error('Network response was not ok')
@@ -140,6 +142,9 @@ const handleStream = (response: Response, onData: IOnData, onCompleted?: IOnComp
else if (bufferObj.event === 'message_replace') {
onMessageReplace?.(bufferObj as MessageReplace)
}
+ else if (bufferObj.event === 'annotation') {
+ onAnnotationReply?.(bufferObj as AnnotationReply)
+ }
}
})
buffer = lines[lines.length - 1]
@@ -345,7 +350,7 @@ export const upload = (options: any, isPublicAPI?: boolean): Promise => {
})
}
-export const ssePost = (url: string, fetchOptions: FetchOptionType, { isPublicAPI = false, onData, onCompleted, onThought, onMessageEnd, onMessageReplace, onError, getAbortController }: IOtherOptions) => {
+export const ssePost = (url: string, fetchOptions: FetchOptionType, { isPublicAPI = false, onData, onCompleted, onThought, onMessageEnd, onMessageReplace, onAnnotationReply, onError, getAbortController }: IOtherOptions) => {
const abortController = new AbortController()
const options = Object.assign({}, baseOptions, {
@@ -384,7 +389,7 @@ export const ssePost = (url: string, fetchOptions: FetchOptionType, { isPublicAP
return
}
onData?.(str, isFirstMessage, moreInfo)
- }, onCompleted, onThought, onMessageEnd, onMessageReplace)
+ }, onCompleted, onThought, onMessageEnd, onMessageReplace, onAnnotationReply)
}).catch((e) => {
if (e.toString() !== 'AbortError: The user aborted a request.')
Toast.notify({ type: 'error', message: e })
diff --git a/web/service/debug.ts b/web/service/debug.ts
index f90bdad8b7..41157cd341 100644
--- a/web/service/debug.ts
+++ b/web/service/debug.ts
@@ -1,4 +1,4 @@
-import type { IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
+import type { IOnAnnotationReply, IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
import { get, post, ssePost } from './base'
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
import type { ModelModeType } from '@/types/app'
@@ -9,11 +9,12 @@ export type AutomaticRes = {
opening_statement: string
}
-export const sendChatMessage = async (appId: string, body: Record, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace }: {
+export const sendChatMessage = async (appId: string, body: Record, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply }: {
onData: IOnData
onCompleted: IOnCompleted
onMessageEnd: IOnMessageEnd
onMessageReplace: IOnMessageReplace
+ onAnnotationReply: IOnAnnotationReply
onError: IOnError
getAbortController?: (abortController: AbortController) => void
}) => {
@@ -22,7 +23,7 @@ export const sendChatMessage = async (appId: string, body: Record,
...body,
response_mode: 'streaming',
},
- }, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace })
+ }, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply })
}
export const stopChatMessageResponding = async (appId: string, taskId: string) => {
diff --git a/web/service/share.ts b/web/service/share.ts
index 2f98314d98..a0b82d8b20 100644
--- a/web/service/share.ts
+++ b/web/service/share.ts
@@ -1,4 +1,4 @@
-import type { IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
+import type { IOnAnnotationReply, IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
import {
del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost,
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
@@ -22,12 +22,13 @@ function getUrl(url: string, isInstalledApp: boolean, installedAppId: string) {
return isInstalledApp ? `installed-apps/${installedAppId}/${url.startsWith('/') ? url.slice(1) : url}` : url
}
-export const sendChatMessage = async (body: Record, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace }: {
+export const sendChatMessage = async (body: Record, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply }: {
onData: IOnData
onCompleted: IOnCompleted
onError: IOnError
onMessageEnd?: IOnMessageEnd
onMessageReplace?: IOnMessageReplace
+ onAnnotationReply: IOnAnnotationReply
getAbortController?: (abortController: AbortController) => void
}, isInstalledApp: boolean, installedAppId = '') => {
return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), {
@@ -35,7 +36,7 @@ export const sendChatMessage = async (body: Record, { onData, onCom
...body,
response_mode: 'streaming',
},
- }, { onData, onCompleted, isPublicAPI: !isInstalledApp, onError, getAbortController, onMessageEnd, onMessageReplace })
+ }, { onData, onCompleted, isPublicAPI: !isInstalledApp, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply })
}
export const stopChatMessageResponding = async (appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => {
diff --git a/web/types/app.ts b/web/types/app.ts
index 0dfe321392..9712173912 100644
--- a/web/types/app.ts
+++ b/web/types/app.ts
@@ -1,4 +1,4 @@
-import type { ChatPromptConfig, CompletionPromptConfig, DatasetConfigs, PromptMode } from '@/models/debug.ts'
+import type { AnnotationReplyConfig, ChatPromptConfig, CompletionPromptConfig, DatasetConfigs, PromptMode } from '@/models/debug.ts'
import type { ExternalDataTool } from '@/models/common'
export enum ProviderType {
openai = 'openai',
@@ -130,6 +130,7 @@ export type ModelConfig = {
enabled: boolean
}
external_data_tools: ExternalDataTool[]
+ annotation_reply?: AnnotationReplyConfig
agent_mode: {
enabled: boolean
tools: ToolItem[]