From b185a70c214c3af445c2b59d7021d83f5c7f5028 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 22 Aug 2023 14:55:20 +0800 Subject: [PATCH] Fix/speech to text button (#947) --- .../[appId]/overview/page.tsx | 1 - .../[appId]/overview/welcome-banner.tsx | 200 ------------------ .../app/configuration/config/index.tsx | 8 +- .../app/configuration/debug/index.tsx | 4 +- web/app/components/datasets/create/index.tsx | 14 +- .../documents/detail/settings/index.tsx | 17 +- .../account-setting/model-page/index.tsx | 10 +- web/context/provider-context.tsx | 27 +-- 8 files changed, 35 insertions(+), 246 deletions(-) delete mode 100644 web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/welcome-banner.tsx diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx index e9fb69969c..892b561a9e 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx @@ -16,7 +16,6 @@ const Overview = async ({ const { t } = await useTranslation(locale, 'app-overview') return (
- {/* */}
{t('overview.title')} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/welcome-banner.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/welcome-banner.tsx deleted file mode 100644 index b5c544106b..0000000000 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/welcome-banner.tsx +++ /dev/null @@ -1,200 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useState } from 'react' -import { useContext } from 'use-context-selector' -import { useTranslation } from 'react-i18next' -import Link from 'next/link' -import useSWR, { useSWRConfig } from 'swr' -import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline' -import { ExclamationCircleIcon } from '@heroicons/react/24/solid' -import { debounce } from 'lodash-es' -import Popover from '@/app/components/base/popover' -import Button from '@/app/components/base/button' -import Tag from '@/app/components/base/tag' -import { ToastContext } from '@/app/components/base/toast' -import { updateOpenAIKey, validateOpenAIKey } from '@/service/apps' -import { fetchTenantInfo } from '@/service/common' -import I18n from '@/context/i18n' - -type IStatusType = 'normal' | 'verified' | 'error' | 'error-api-key-exceed-bill' - -const STATUS_COLOR_MAP = { - 'normal': { color: '', bgColor: 'bg-primary-50', borderColor: 'border-primary-100' }, - 'error': { color: 'text-red-600', bgColor: 'bg-red-50', borderColor: 'border-red-100' }, - 'verified': { color: '', bgColor: 'bg-green-50', borderColor: 'border-green-100' }, - 'error-api-key-exceed-bill': { color: 'text-red-600', bgColor: 'bg-red-50', borderColor: 'border-red-100' }, -} - -const CheckCircleIcon: FC<{ className?: string }> = ({ className }) => { - return - - - -} - -type IEditKeyDiv = { - className?: string - showInPopover?: boolean - onClose?: () => void - getTenantInfo?: () => void -} - -const EditKeyDiv: FC = ({ className = '', showInPopover = false, onClose, getTenantInfo }) => { - const [inputValue, setInputValue] = useState() - const [editStatus, setEditStatus] = useState('normal') - const [loading, setLoading] = useState(false) - const [validating, setValidating] = useState(false) - const { notify } = useContext(ToastContext) - const { t } = useTranslation() - const { locale } = useContext(I18n) - - // Hide the pop-up window and need to get the latest key again - // If the key is valid, the edit button will be hidden later - const onClosePanel = () => { - getTenantInfo && getTenantInfo() - onClose && onClose() - } - - const onSaveKey = async () => { - if (editStatus === 'verified') { - setLoading(true) - try { - await updateOpenAIKey({ url: '/providers/openai/token', body: { token: inputValue ?? '' } }) - notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - onClosePanel() - } - catch (err) { - notify({ type: 'error', message: t('common.actionMsg.modificationFailed') }) - } - finally { - setLoading(false) - } - } - } - - const validateKey = async (value: string) => { - try { - setValidating(true) - const res = await validateOpenAIKey({ url: '/providers/openai/token-validate', body: { token: value ?? '' } }) - setEditStatus(res.result === 'success' ? 'verified' : 'error') - } - catch (err: any) { - if (err.status === 400) { - err.json().then(({ code }: any) => { - if (code === 'provider_request_failed') - setEditStatus('error-api-key-exceed-bill') - }) - } - else { - setEditStatus('error') - } - } - finally { - setValidating(false) - } - } - const renderErrorMessage = () => { - if (validating) { - return ( -
- {t('common.provider.validating')} -
- ) - } - if (editStatus === 'error-api-key-exceed-bill') { - return ( -
- {t('common.provider.apiKeyExceedBill')} - {locale === 'en' ? ' ' : ''} - - {locale === 'en' ? 'this link' : 'θΏ™η―‡ζ–‡ζ‘£'} - -
- ) - } - if (editStatus === 'error') { - return ( -
- {t('common.provider.invalidKey')} -
- ) - } - return null - } - - return ( -
- {!showInPopover &&

{t('appOverview.welcome.firstStepTip')}

} -

{t('appOverview.welcome.enterKeyTip')} {showInPopover ? '' : 'πŸ‘‡'}

-
- { - setInputValue(e.target.value) - if (!e.target.value) { - setEditStatus('normal') - return - } - validateKey(e.target.value) - }, 300)} - /> - {editStatus === 'verified' &&
- -
} - {(editStatus === 'error' || editStatus === 'error-api-key-exceed-bill') &&
- -
} - {showInPopover ? null : } -
- {renderErrorMessage()} - - {t('appOverview.welcome.getKeyTip')} -
- ) -} - -const WelcomeBanner: FC = () => { - const { data: userInfo } = useSWR({ url: '/info' }, fetchTenantInfo) - if (!userInfo) - return null - return userInfo?.providers?.find(({ token_is_set }) => token_is_set) ? null : -} - -export const EditKeyPopover: FC = () => { - const { data: userInfo } = useSWR({ url: '/info' }, fetchTenantInfo) - const { mutate } = useSWRConfig() - if (!userInfo) - return null - - const getTenantInfo = () => { - mutate({ url: '/info' }) - } - // In this case, the edit button is displayed - const targetProvider = userInfo?.providers?.some(({ token_is_set, is_valid }) => token_is_set && is_valid) - return ( - !targetProvider - ?
- OpenAI API key invalid - } - trigger='click' - position='br' - btnElement='Edit' - btnClassName='text-primary-600 !text-xs px-3 py-1.5' - className='!p-0 !w-[464px] h-[200px]' - /> -
- : null) -} - -export default WelcomeBanner diff --git a/web/app/components/app/configuration/config/index.tsx b/web/app/components/app/configuration/config/index.tsx index 46b3eb43f5..82e2314309 100644 --- a/web/app/components/app/configuration/config/index.tsx +++ b/web/app/components/app/configuration/config/index.tsx @@ -38,7 +38,7 @@ const Config: FC = () => { setSpeechToTextConfig, } = useContext(ConfigContext) const isChatApp = mode === AppType.chat - const { currentProvider } = useProviderContext() + const { speech2textDefaultModel } = useProviderContext() const promptTemplate = modelConfig.configs.prompt_template const promptVariables = modelConfig.configs.prompt_variables @@ -90,7 +90,7 @@ const Config: FC = () => { }, }) - const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && currentProvider?.provider_name === 'openai')) + const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && !!speech2textDefaultModel)) const hasToolbox = false const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) @@ -120,7 +120,7 @@ const Config: FC = () => { isChatApp={isChatApp} config={featureConfig} onChange={handleFeatureChange} - showSpeechToTextItem={currentProvider?.provider_name === 'openai'} + showSpeechToTextItem={!!speech2textDefaultModel} /> )} {showAutomatic && ( @@ -160,7 +160,7 @@ const Config: FC = () => { } } isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer} - isShowSpeechText={featureConfig.speechToText && currentProvider?.provider_name === 'openai'} + isShowSpeechText={featureConfig.speechToText && !!speech2textDefaultModel} /> ) } diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index 1abb55eef4..5d0fb9ad1f 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -52,7 +52,7 @@ const Debug: FC = ({ modelConfig, completionParams, } = useContext(ConfigContext) - const { currentProvider } = useProviderContext() + const { speech2textDefaultModel } = useProviderContext() const [chatList, setChatList, getChatList] = useGetState([]) const chatListDomRef = useRef(null) useEffect(() => { @@ -390,7 +390,7 @@ const Debug: FC = ({ }} isShowSuggestion={doShowSuggestion} suggestionList={suggestQuestions} - isShowSpeechToText={speechToTextConfig.enabled && currentProvider?.provider_name === 'openai'} + isShowSpeechToText={speechToTextConfig.enabled && !!speech2textDefaultModel} />
diff --git a/web/app/components/datasets/create/index.tsx b/web/app/components/datasets/create/index.tsx index 8a4ae5e8fe..34ee3ef2ad 100644 --- a/web/app/components/datasets/create/index.tsx +++ b/web/app/components/datasets/create/index.tsx @@ -9,9 +9,10 @@ import StepTwo from './step-two' import StepThree from './step-three' import { DataSourceType } from '@/models/datasets' import type { DataSet, FileItem, createDocumentResponse } from '@/models/datasets' -import { fetchDataSource, fetchTenantInfo } from '@/service/common' +import { fetchDataSource } from '@/service/common' import { fetchDataDetail } from '@/service/datasets' import type { DataSourceNotionPage } from '@/models/common' +import { useProviderContext } from '@/context/provider-context' import AccountSetting from '@/app/components/header/account-setting' @@ -23,7 +24,6 @@ type DatasetUpdateFormProps = { const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => { const { t } = useTranslation() - const [hasSetAPIKEY, setHasSetAPIKEY] = useState(true) const [isShowSetAPIKey, { setTrue: showSetAPIKey, setFalse: hideSetAPIkey }] = useBoolean() const [hasConnection, setHasConnection] = useState(true) const [isShowDataSourceSetting, { setTrue: showDataSourceSetting, setFalse: hideDataSourceSetting }] = useBoolean() @@ -33,6 +33,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => { const [fileList, setFiles] = useState([]) const [result, setResult] = useState() const [hasError, setHasError] = useState(false) + const { embeddingsDefaultModel } = useProviderContext() const [notionPages, setNotionPages] = useState([]) const updateNotionPages = (value: Page[]) => { @@ -77,11 +78,6 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => { setStep(step + delta) }, [step, setStep]) - const checkAPIKey = async () => { - const data = await fetchTenantInfo({ url: '/info' }) - const hasSetKey = data.providers.some(({ is_valid }) => is_valid) - setHasSetAPIKEY(hasSetKey) - } const checkNotionConnection = async () => { const { data } = await fetchDataSource({ url: '/data-source/integrates' }) const hasConnection = data.filter(item => item.provider === 'notion') || [] @@ -89,7 +85,6 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => { } useEffect(() => { - checkAPIKey() checkNotionConnection() }, []) @@ -132,7 +127,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => { onStepChange={nextStep} />} {(step === 2 && (!datasetId || (datasetId && !!detail))) && { />} {isShowSetAPIKey && { - await checkAPIKey() hideSetAPIkey() }} />} {isShowDataSourceSetting && } diff --git a/web/app/components/datasets/documents/detail/settings/index.tsx b/web/app/components/datasets/documents/detail/settings/index.tsx index 5bc4e8e517..a1b0eaf3a1 100644 --- a/web/app/components/datasets/documents/detail/settings/index.tsx +++ b/web/app/components/datasets/documents/detail/settings/index.tsx @@ -6,7 +6,6 @@ import { useContext } from 'use-context-selector' import { useRouter } from 'next/navigation' import DatasetDetailContext from '@/context/dataset-detail' import type { FullDocumentDetail } from '@/models/datasets' -import { fetchTenantInfo } from '@/service/common' import type { MetadataType } from '@/service/datasets' import { fetchDocumentDetail } from '@/service/datasets' @@ -14,6 +13,7 @@ import Loading from '@/app/components/base/loading' import StepTwo from '@/app/components/datasets/create/step-two' import AccountSetting from '@/app/components/header/account-setting' import AppUnavailable from '@/app/components/base/app-unavailable' +import { useProviderContext } from '@/context/provider-context' type DocumentSettingsProps = { datasetId: string @@ -23,25 +23,15 @@ type DocumentSettingsProps = { const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => { const { t } = useTranslation() const router = useRouter() - const [hasSetAPIKEY, setHasSetAPIKEY] = useState(true) const [isShowSetAPIKey, { setTrue: showSetAPIKey, setFalse: hideSetAPIkey }] = useBoolean() const [hasError, setHasError] = useState(false) const { indexingTechnique, dataset } = useContext(DatasetDetailContext) + const { embeddingsDefaultModel } = useProviderContext() const saveHandler = () => router.push(`/datasets/${datasetId}/documents/${documentId}`) const cancelHandler = () => router.back() - const checkAPIKey = async () => { - const data = await fetchTenantInfo({ url: '/info' }) - const hasSetKey = data.providers.some(({ is_valid }) => is_valid) - setHasSetAPIKEY(hasSetKey) - } - - useEffect(() => { - checkAPIKey() - }, []) - const [documentDetail, setDocumentDetail] = useState(null) const currentPage = useMemo(() => { return { @@ -77,7 +67,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => { {!documentDetail && } {dataset && documentDetail && ( { )} {isShowSetAPIKey && { - await checkAPIKey() hideSetAPIkey() }} />} 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 a45938fe66..af3cdb0ec7 100644 --- a/web/app/components/header/account-setting/model-page/index.tsx +++ b/web/app/components/header/account-setting/model-page/index.tsx @@ -61,11 +61,15 @@ type DeleteModel = { const ModelPage = () => { const { t } = useTranslation() - const { updateModelList } = useProviderContext() + const { + updateModelList, + embeddingsDefaultModel, + mutateEmbeddingsDefaultModel, + speech2textDefaultModel, + mutateSpeech2textDefaultModel, + } = useProviderContext() const { data: providers, mutate: mutateProviders } = useSWR('/workspaces/current/model-providers', fetchModelProviders) const { data: textGenerationDefaultModel, mutate: mutateTextGenerationDefaultModel } = useSWR('/workspaces/current/default-model?model_type=text-generation', fetchDefaultModal) - const { data: embeddingsDefaultModel, mutate: mutateEmbeddingsDefaultModel } = useSWR('/workspaces/current/default-model?model_type=embeddings', fetchDefaultModal) - const { data: speech2textDefaultModel, mutate: mutateSpeech2textDefaultModel } = useSWR('/workspaces/current/default-model?model_type=speech2text', fetchDefaultModal) const [showMoreModel, setShowMoreModel] = useState(false) const [showModal, setShowModal] = useState(false) const { notify } = useToastContext() diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index 618c309554..46161fe671 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -2,29 +2,29 @@ import { createContext, useContext } from 'use-context-selector' import useSWR from 'swr' -import { fetchModelList, fetchTenantInfo } from '@/service/common' +import { fetchDefaultModal, fetchModelList } from '@/service/common' import { ModelFeature, ModelType } from '@/app/components/header/account-setting/model-page/declarations' import type { BackendModel } from '@/app/components/header/account-setting/model-page/declarations' const ProviderContext = createContext<{ - currentProvider: { - provider: string - provider_name: string - token_is_set: boolean - is_valid: boolean - token_is_valid: boolean - } | null | undefined textGenerationModelList: BackendModel[] embeddingsModelList: BackendModel[] speech2textModelList: BackendModel[] agentThoughtModelList: BackendModel[] updateModelList: (type: ModelType) => void + embeddingsDefaultModel?: BackendModel + mutateEmbeddingsDefaultModel: () => void + speech2textDefaultModel?: BackendModel + mutateSpeech2textDefaultModel: () => void }>({ - currentProvider: null, textGenerationModelList: [], embeddingsModelList: [], speech2textModelList: [], agentThoughtModelList: [], updateModelList: () => {}, + speech2textDefaultModel: undefined, + mutateSpeech2textDefaultModel: () => {}, + embeddingsDefaultModel: undefined, + mutateEmbeddingsDefaultModel: () => {}, }) export const useProviderContext = () => useContext(ProviderContext) @@ -35,8 +35,8 @@ type ProviderContextProviderProps = { export const ProviderContextProvider = ({ children, }: ProviderContextProviderProps) => { - const { data: userInfo } = useSWR({ url: '/info' }, fetchTenantInfo) - const currentProvider = userInfo?.providers?.find(({ token_is_set, is_valid, provider_name }) => token_is_set && is_valid && (provider_name === 'openai' || provider_name === 'azure_openai')) + const { data: embeddingsDefaultModel, mutate: mutateEmbeddingsDefaultModel } = useSWR('/workspaces/current/default-model?model_type=embeddings', fetchDefaultModal) + const { data: speech2textDefaultModel, mutate: mutateSpeech2textDefaultModel } = useSWR('/workspaces/current/default-model?model_type=speech2text', fetchDefaultModal) const fetchModelListUrlPrefix = '/workspaces/current/models/model-type/' const { data: textGenerationModelList, mutate: mutateTextGenerationModelList } = useSWR(`${fetchModelListUrlPrefix}${ModelType.textGeneration}`, fetchModelList) const { data: embeddingsModelList, mutate: mutateEmbeddingsModelList } = useSWR(`${fetchModelListUrlPrefix}${ModelType.embeddings}`, fetchModelList) @@ -54,12 +54,15 @@ export const ProviderContextProvider = ({ return ( {children}