diff --git a/web/app/components/app/chat/citation/popup.tsx b/web/app/components/app/chat/citation/popup.tsx index 20e04df63f04a1..ea2e8a9b24fded 100644 --- a/web/app/components/app/chat/citation/popup.tsx +++ b/web/app/components/app/chat/citation/popup.tsx @@ -100,7 +100,11 @@ const Popup: FC = ({ data={source.index_node_hash.substring(0, 7)} icon={} /> - + { + source.score && ( + + ) + } ) } diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index 7936445d993e92..8019011f7ecf24 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -59,6 +59,7 @@ const SettingsModal: FC = ({ const { rerankDefaultModel, isRerankDefaultModelVaild, + rerankModelList, } = useProviderContext() const handleValueChange = (type: string, value: string) => { @@ -78,6 +79,7 @@ const SettingsModal: FC = ({ !isReRankModelSelected({ rerankDefaultModel, isRerankDefaultModelVaild, + rerankModelList, retrievalConfig, indexMethod, }) @@ -270,7 +272,7 @@ const SettingsModal: FC = ({ )}
{ - const rerankModel = (retrievalConfig.reranking_model?.reranking_model_name ? retrievalConfig.reranking_model : undefined) || (isRerankDefaultModelVaild ? rerankDefaultModel : undefined) + const rerankModelSelected = (() => { + if (retrievalConfig.reranking_model?.reranking_model_name) + return !!rerankModelList.find(({ model_name }) => model_name === retrievalConfig.reranking_model?.reranking_model_name) + + if (isRerankDefaultModelVaild) + return !!rerankDefaultModel + + return false + })() + if ( indexMethod === 'high_quality' - && (retrievalConfig.reranking_enable || retrievalConfig.search_method === RETRIEVE_METHOD.fullText) - && !rerankModel + && (retrievalConfig.reranking_enable || retrievalConfig.search_method === RETRIEVE_METHOD.hybrid) + && !rerankModelSelected ) return false @@ -35,7 +46,7 @@ export const ensureRerankModelSelected = ({ const rerankModel = retrievalConfig.reranking_model?.reranking_model_name ? retrievalConfig.reranking_model : undefined if ( indexMethod === 'high_quality' - && (retrievalConfig.reranking_enable || retrievalConfig.search_method === RETRIEVE_METHOD.fullText) + && (retrievalConfig.reranking_enable || retrievalConfig.search_method === RETRIEVE_METHOD.hybrid) && !rerankModel ) { return { diff --git a/web/app/components/datasets/common/retrieval-method-config/index.tsx b/web/app/components/datasets/common/retrieval-method-config/index.tsx index 57871d803c6669..b49cea2b349eef 100644 --- a/web/app/components/datasets/common/retrieval-method-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-method-config/index.tsx @@ -16,11 +16,23 @@ type Props = { } const RetrievalMethodConfig: FC = ({ - value, + value: passValue, onChange, }) => { const { t } = useTranslation() - const { supportRetrievalMethods } = useProviderContext() + const { supportRetrievalMethods, rerankDefaultModel } = useProviderContext() + const value = (() => { + if (!passValue.reranking_model.reranking_model_name) { + return { + ...passValue, + reranking_model: { + reranking_provider_name: rerankDefaultModel?.model_provider.provider_name || '', + reranking_model_name: rerankDefaultModel?.model_name || '', + }, + } + } + return passValue + })() return (
{supportRetrievalMethods.includes(RETRIEVE_METHOD.semantic) && ( diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index b689f11e573cfb..639d8631dc59a4 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -263,6 +263,7 @@ const StepTwo = ({ const { rerankDefaultModel, isRerankDefaultModelVaild, + rerankModelList, } = useProviderContext() const getCreationParams = () => { let params @@ -282,6 +283,7 @@ const StepTwo = ({ !isReRankModelSelected({ rerankDefaultModel, isRerankDefaultModelVaild, + rerankModelList, // eslint-disable-next-line @typescript-eslint/no-use-before-define retrievalConfig, indexMethod: indexMethod as string, @@ -359,6 +361,9 @@ const StepTwo = ({ try { let res const params = getCreationParams() + if (!params) + return false + setIsCreating(true) if (!datasetId) { res = await createFirstDocument({ diff --git a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx index 057f70c59a9cce..4ddd8d654d4a2b 100644 --- a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx +++ b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx @@ -3,11 +3,14 @@ import type { FC } from 'react' import React, { useRef, useState } from 'react' import { useClickAway } from 'ahooks' import { useTranslation } from 'react-i18next' +import Toast from '../../base/toast' import { XClose } from '@/app/components/base/icons/src/vender/line/general' import type { RetrievalConfig } from '@/types/app' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import Button from '@/app/components/base/button' +import { useProviderContext } from '@/context/provider-context' +import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' type Props = { indexMethod: string @@ -33,6 +36,32 @@ const ModifyRetrievalModal: FC = ({ onHide() }, ref) + const { + rerankDefaultModel, + isRerankDefaultModelVaild, + rerankModelList, + } = useProviderContext() + + const handleSave = () => { + if ( + !isReRankModelSelected({ + rerankDefaultModel, + isRerankDefaultModelVaild, + rerankModelList, + retrievalConfig, + indexMethod, + }) + ) { + Toast.notify({ type: 'error', message: t('appDebug.datasetConfig.rerankModelRequired') }) + return + } + onSave(ensureRerankModelSelected({ + rerankDefaultModel: rerankDefaultModel!, + retrievalConfig, + indexMethod, + })) + } + if (!isShow) return null @@ -87,7 +116,7 @@ const ModifyRetrievalModal: FC = ({ }} > - +
) diff --git a/web/app/components/datasets/settings/form/index.tsx b/web/app/components/datasets/settings/form/index.tsx index 239ba6b7a149dc..4afddb82241a4d 100644 --- a/web/app/components/datasets/settings/form/index.tsx +++ b/web/app/components/datasets/settings/form/index.tsx @@ -59,6 +59,7 @@ const Form = () => { const { rerankDefaultModel, isRerankDefaultModelVaild, + rerankModelList, } = useProviderContext() const handleSave = async () => { @@ -72,6 +73,7 @@ const Form = () => { !isReRankModelSelected({ rerankDefaultModel, isRerankDefaultModelVaild, + rerankModelList, retrievalConfig, indexMethod, }) diff --git a/web/app/components/header/account-setting/model-page/configs/cohere.tsx b/web/app/components/header/account-setting/model-page/configs/cohere.tsx index 80e342a4aca999..11bad8a9d7afe5 100644 --- a/web/app/components/header/account-setting/model-page/configs/cohere.tsx +++ b/web/app/components/header/account-setting/model-page/configs/cohere.tsx @@ -16,12 +16,16 @@ const config: ProviderConfig = { 'en': , 'zh-Hans': , }, + hit: { + 'en': 'Rerank Model Supported', + 'zh-Hans': '支持 Rerank 模型', + }, }, modal: { key: ProviderEnum.cohere, title: { - 'en': 'cohere', - 'zh-Hans': 'cohere', + 'en': 'Rerank Model', + 'zh-Hans': 'Rerank 模型', }, icon: , link: { diff --git a/web/app/components/header/account-setting/model-page/index.tsx b/web/app/components/header/account-setting/model-page/index.tsx index 1618f8825e30b0..dce2005cf638d6 100644 --- a/web/app/components/header/account-setting/model-page/index.tsx +++ b/web/app/components/header/account-setting/model-page/index.tsx @@ -26,6 +26,7 @@ import { ModelType } from '@/app/components/header/account-setting/model-page/de import { useEventEmitterContextContext } from '@/context/event-emitter' import { useProviderContext } from '@/context/provider-context' import I18n from '@/context/i18n' +import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' const MODEL_CARD_LIST = [ config.openai, @@ -42,6 +43,10 @@ const ModelPage = () => { const { locale } = useContext(I18n) const { updateModelList, + textGenerationDefaultModel, + embeddingsDefaultModel, + speech2textDefaultModel, + rerankDefaultModel, } = useProviderContext() const { data: providers, mutate: mutateProviders } = useSWR('/workspaces/current/model-providers', fetchModelProviders) const [showModal, setShowModal] = useState(false) @@ -196,11 +201,22 @@ const ModelPage = () => { } } + const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel + return (
-
-
{t('common.modelProvider.models')}
- +
+ { + defaultModelNotConfigured + ? ( +
+ + {t('common.modelProvider.notConfigured')} +
+ ) + :
{t('common.modelProvider.models')}
+ } + mutateProviders()} />
{ diff --git a/web/app/components/header/account-setting/model-page/model-modal/index.tsx b/web/app/components/header/account-setting/model-page/model-modal/index.tsx index 385e064ffb6e91..88c60c8c13b9b4 100644 --- a/web/app/components/header/account-setting/model-page/model-modal/index.tsx +++ b/web/app/components/header/account-setting/model-page/model-modal/index.tsx @@ -2,7 +2,6 @@ import { useCallback, useState } from 'react' import type { FC } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import { Portal } from '@headlessui/react' import type { FormValue, ProviderConfigModal } from '../declarations' import { ConfigurableProviders } from '../utils' import Form from './Form' @@ -12,6 +11,10 @@ import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import { useEventEmitterContextContext } from '@/context/event-emitter' +import { + PortalToFollowElem, + PortalToFollowElemContent, +} from '@/app/components/base/portal-to-follow-elem' type ModelModalProps = { isShow: boolean @@ -90,75 +93,77 @@ const ModelModal: FC = ({ return null return ( - -
-
-
-
-
{renderTitlePrefix()}
- {modelModal?.icon} -
-
setValue(newValue)} - onValidatedError={handleValidatedError} - mode={mode} - cleared={cleared} - onClearedChange={setCleared} - onValidating={handleValidating} - /> -
- - {modelModal?.link.label[locale]} - - -
- - + {modelModal?.link.label[locale]} + + +
+ + +
-
-
- { - errorMessage - ? ( -
- - {errorMessage} -
- ) - : ( -
- - {t('common.modelProvider.encrypted.front')} - - PKCS1_OAEP - - {t('common.modelProvider.encrypted.back')} -
- ) - } +
+ { + errorMessage + ? ( +
+ + {errorMessage} +
+ ) + : ( +
+ + {t('common.modelProvider.encrypted.front')} + + PKCS1_OAEP + + {t('common.modelProvider.encrypted.back')} +
+ ) + } +
-
-
+ + ) } diff --git a/web/app/components/header/account-setting/model-page/model-selector/index.tsx b/web/app/components/header/account-setting/model-page/model-selector/index.tsx index 380911e62d57fe..69b46e0c7b1f3b 100644 --- a/web/app/components/header/account-setting/model-page/model-selector/index.tsx +++ b/web/app/components/header/account-setting/model-page/model-selector/index.tsx @@ -1,14 +1,17 @@ import type { FC } from 'react' -import { Fragment, useState } from 'react' +import React, { Fragment, useEffect, useState } from 'react' +import useSWR from 'swr' import { Popover, Transition } from '@headlessui/react' 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, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations' +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, SearchLg } from '@/app/components/base/icons/src/vender/line/general' +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' @@ -20,6 +23,9 @@ import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/ 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' type Props = { value: { @@ -35,6 +41,7 @@ type Props = { readonly?: boolean triggerIconSmall?: boolean whenEmptyGoToSetting?: boolean + onUpdate?: () => void } type ModelOption = { @@ -59,6 +66,7 @@ const ModelSelector: FC = ({ readonly, triggerIconSmall, whenEmptyGoToSetting, + onUpdate, }) => { const { t } = useTranslation() const { setShowAccountSettingModal } = useModalContext() @@ -68,6 +76,7 @@ const ModelSelector: FC = ({ speech2textModelList, rerankModelList, agentThoughtModelList, + updateModelList, } = useProviderContext() const [search, setSearch] = useState('') const modelList = supportAgentThought @@ -98,7 +107,7 @@ const ModelSelector: FC = ({ }) : modelList - const hasRemoved = value && !modelList.find(({ model_name, model_provider }) => model_name === value.modelName && model_provider.provider_name === value.providerName) + 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)) @@ -121,6 +130,45 @@ const ModelSelector: FC = ({ }) 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('') + } + } + } + + useEffect(() => { + if (rerankDefaultModel && whenEmptyGoToSetting) + onChange(rerankDefaultModel) + }, [rerankDefaultModel]) return (
@@ -130,7 +178,7 @@ const ModelSelector: FC = ({ ({ open }) => ( <> { - value + (value && value.modelName && value.providerName) ? ( <> = ({
) - : ( -
{t('common.modelProvider.selectModel')}
- ) + : whenEmptyGoToSetting + ? ( +
+
+ + {t('common.modelProvider.selector.rerankTip')} +
+ +
+ ) + : ( +
{t('common.modelProvider.selectModel')}
+ ) } { hasRemoved && ( @@ -162,7 +220,16 @@ const ModelSelector: FC = ({ ) } - {!readonly && } + { + !readonly && !whenEmptyGoToSetting && ( + + ) + } + { + whenEmptyGoToSetting && (value && value.modelName && value.providerName) && ( + + ) + } ) } @@ -246,21 +313,6 @@ const ModelSelector: FC = ({ return null }) } - { - whenEmptyGoToSetting && modelList.length === 0 && ( -
-
- -
-
- {t('common.modelProvider.selector.emptyTip')} -
-
- setShowAccountSettingModal({ payload: 'provider' })}>{t('common.modelProvider.selector.emptySetting')} -
-
- ) - } {modelList.length !== 0 && (search && filteredModelList.length === 0) && (
{t('common.modelProvider.noModelFound', { model: search })}
)} @@ -281,6 +333,13 @@ const ModelSelector: FC = ({ )} + setShowRerankModal(false)} + onSave={handleRerankModalSave} + mode={'add'} + />
) } diff --git a/web/app/components/header/account-setting/model-page/system-model/index.tsx b/web/app/components/header/account-setting/model-page/system-model/index.tsx index e442a60bb1b810..b55dc7d8e5ef63 100644 --- a/web/app/components/header/account-setting/model-page/system-model/index.tsx +++ b/web/app/components/header/account-setting/model-page/system-model/index.tsx @@ -1,3 +1,4 @@ +import type { FC } from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import ModelSelector from '../model-selector' @@ -17,7 +18,12 @@ import { ModelType } from '@/app/components/header/account-setting/model-page/de import { useToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' -const SystemModel = () => { +type SystemModelProps = { + onUpdate: () => void +} +const SystemModel: FC = ({ + onUpdate, +}) => { const { t } = useTranslation() const { textGenerationDefaultModel, @@ -91,7 +97,7 @@ const SystemModel = () => { > setOpen(v => !v)}>
@@ -158,6 +164,8 @@ const SystemModel = () => { value={selectedModel[ModelType.reranking]} modelType={ModelType.reranking} onChange={v => handleChangeDefaultModel(ModelType.reranking, v)} + whenEmptyGoToSetting + onUpdate={onUpdate} />
diff --git a/web/i18n/lang/app-debug.en.ts b/web/i18n/lang/app-debug.en.ts index ebf9a358d1332b..a6f23817779783 100644 --- a/web/i18n/lang/app-debug.en.ts +++ b/web/i18n/lang/app-debug.en.ts @@ -305,7 +305,7 @@ const translation = { }, result: 'Output Text', datasetConfig: { - settingTitle: 'Retrieve Settings', + settingTitle: 'Retrieval settings', retrieveOneWay: { title: 'N-to-1 retrieval', description: 'Based on user intent and dataset descriptions, the Agent autonomously selects the best dataset for querying. Best for applications with distinct, limited datasets.', diff --git a/web/i18n/lang/common.en.ts b/web/i18n/lang/common.en.ts index 662bbc6cbffe43..2e2be7f36e443f 100644 --- a/web/i18n/lang/common.en.ts +++ b/web/i18n/lang/common.en.ts @@ -223,6 +223,7 @@ const translation = { }, }, modelProvider: { + notConfigured: 'The system model has not yet been fully configured, and some functions may be unavailable.', systemModelSettings: 'System Model Settings', systemModelSettingsLink: 'Why is it necessary to set up a system model?', selectModel: 'Select your model', @@ -252,6 +253,7 @@ const translation = { tip: 'This model has been removed. Please add a model or select another model.', emptyTip: 'No available models', emptySetting: 'Please go to settings to configure', + rerankTip: 'Please set up the Rerank model', }, card: { quota: 'QUOTA', diff --git a/web/i18n/lang/common.zh.ts b/web/i18n/lang/common.zh.ts index 8da1a1d8f0436f..ed473514e1db2d 100644 --- a/web/i18n/lang/common.zh.ts +++ b/web/i18n/lang/common.zh.ts @@ -223,6 +223,7 @@ const translation = { }, }, modelProvider: { + notConfigured: '系统模型尚未完全配置,部分功能可能无法使用。', systemModelSettings: '系统模型设置', systemModelSettingsLink: '为什么需要设置系统模型?', selectModel: '选择您的模型', @@ -252,6 +253,7 @@ const translation = { tip: '该模型已被删除。请添模型或选择其他模型。', emptyTip: '无可用模型', emptySetting: '请前往设置进行配置', + rerankTip: '请设置 Rerank 模型', }, card: { quota: '额度',