Procházet zdrojové kódy

feat: frontend support claude (#573)

Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
Joel před 1 rokem
rodič
revize
8e11200306

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 7 - 5
web/app/components/app/configuration/config-model/index.tsx


+ 1 - 1
web/app/components/app/configuration/debug/index.tsx

@@ -372,7 +372,7 @@ const Debug: FC<IDebug> = ({
         {/* Chat */}
         {mode === AppType.chat && (
           <div className="mt-[34px] h-full flex flex-col">
-            <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[66px]'), 'relative mt-1.5 grow h-[200px] overflow-hidden')}>
+            <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative mt-1.5 grow h-[200px] overflow-hidden')}>
               <div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}>
                 <Chat
                   chatList={chatList}

+ 4 - 2
web/app/components/app/configuration/index.tsx

@@ -16,6 +16,7 @@ import ConfigModel from '@/app/components/app/configuration/config-model'
 import Config from '@/app/components/app/configuration/config'
 import Debug from '@/app/components/app/configuration/debug'
 import Confirm from '@/app/components/base/confirm'
+import { ProviderType } from '@/types/app'
 import type { AppDetailResponse } from '@/models/app'
 import { ToastContext } from '@/app/components/base/toast'
 import { fetchTenantInfo } from '@/service/common'
@@ -67,7 +68,7 @@ const Configuration: FC = () => {
     frequency_penalty: 1, // -2-2
   })
   const [modelConfig, doSetModelConfig] = useState<ModelConfig>({
-    provider: 'openai',
+    provider: ProviderType.openai,
     model_id: 'gpt-3.5-turbo',
     configs: {
       prompt_template: '',
@@ -84,8 +85,9 @@ const Configuration: FC = () => {
     doSetModelConfig(newModelConfig)
   }
 
-  const setModelId = (modelId: string) => {
+  const setModelId = (modelId: string, provider: ProviderType) => {
     const newModelConfig = produce(modelConfig, (draft: any) => {
+      draft.provider = provider
       draft.model_id = modelId
     })
     setModelConfig(newModelConfig)

+ 10 - 2
web/app/components/base/auto-height-textarea/index.tsx

@@ -19,6 +19,7 @@ const AutoHeightTextarea = forwardRef(
     { value, onChange, placeholder, className, minHeight = 36, maxHeight = 96, autoFocus, controlFocus, onKeyDown, onKeyUp }: IProps,
     outerRef: any,
   ) => {
+    // eslint-disable-next-line react-hooks/rules-of-hooks
     const ref = outerRef || useRef<HTMLTextAreaElement>(null)
 
     const doFocus = () => {
@@ -54,13 +55,20 @@ const AutoHeightTextarea = forwardRef(
 
     return (
       <div className='relative'>
-        <div className={cn(className, 'invisible whitespace-pre-wrap break-all  overflow-y-auto')} style={{ minHeight, maxHeight }}>
+        <div className={cn(className, 'invisible whitespace-pre-wrap break-all  overflow-y-auto')} style={{
+          minHeight,
+          maxHeight,
+          paddingRight: (value && value.trim().length > 10000) ? 140 : 130,
+        }}>
           {!value ? placeholder : value.replace(/\n$/, '\n ')}
         </div>
         <textarea
           ref={ref}
           autoFocus={autoFocus}
-          className={cn(className, 'absolute inset-0 resize-none overflow-hidden')}
+          className={cn(className, 'absolute inset-0 resize-none overflow-auto')}
+          style={{
+            paddingRight: (value && value.trim().length > 10000) ? 140 : 130,
+          }}
           placeholder={placeholder}
           onChange={onChange}
           onKeyDown={onKeyDown}

+ 24 - 0
web/app/components/header/account-setting/provider-page/anthropic-hosted-provider/index.module.css

@@ -0,0 +1,24 @@
+.icon {
+  width: 24px;
+  height: 24px;
+  margin-right: 12px;
+  background: url(../../../assets/anthropic.svg) center center no-repeat;
+  background-size: contain;
+}
+
+.bar {
+  background: linear-gradient(90deg, rgba(41, 112, 255, 0.9) 0%, rgba(21, 94, 239, 0.9) 100%);
+}
+
+.bar-error {
+  background: linear-gradient(90deg, rgba(240, 68, 56, 0.72) 0%, rgba(217, 45, 32, 0.9) 100%);
+}
+
+.bar-item {
+  width: 10%;
+  border-right: 1px solid rgba(255, 255, 255, 0.5);
+}
+
+.bar-item:last-of-type {
+  border-right: 0;
+}

+ 65 - 0
web/app/components/header/account-setting/provider-page/anthropic-hosted-provider/index.tsx

@@ -0,0 +1,65 @@
+import { useTranslation } from 'react-i18next'
+import cn from 'classnames'
+import s from './index.module.css'
+import type { ProviderHosted } from '@/models/common'
+
+type AnthropicHostedProviderProps = {
+  provider: ProviderHosted
+}
+const AnthropicHostedProvider = ({
+  provider,
+}: AnthropicHostedProviderProps) => {
+  const { t } = useTranslation()
+  const exhausted = provider.quota_used > provider.quota_limit
+
+  return (
+    <div className={`
+      border-[0.5px] border-gray-200 rounded-xl
+      ${exhausted ? 'bg-[#FFFBFA]' : 'bg-gray-50'}
+    `}>
+      <div className='pt-4 px-4 pb-3'>
+        <div className='flex items-center mb-3'>
+          <div className={s.icon} />
+          <div className='grow text-sm font-medium text-gray-800'>
+            {t('common.provider.anthropicHosted.anthropicHosted')}
+          </div>
+          <div className={`
+            px-2 h-[22px] flex items-center rounded-md border 
+            text-xs font-semibold 
+            ${exhausted ? 'border-[#D92D20] text-[#D92D20]' : 'border-primary-600 text-primary-600'}
+          `}>
+            {exhausted ? t('common.provider.anthropicHosted.exhausted') : t('common.provider.anthropicHosted.onTrial')}
+          </div>
+        </div>
+        <div className='text-[13px] text-gray-500'>{t('common.provider.anthropicHosted.desc')}</div>
+      </div>
+      <div className='flex items-center h-[42px] px-4 border-t-[0.5px] border-t-[rgba(0, 0, 0, 0.05)]'>
+        <div className='text-[13px] text-gray-700'>{t('common.provider.anthropicHosted.callTimes')}</div>
+        <div className='relative grow h-2 flex bg-gray-200 rounded-md mx-2 overflow-hidden'>
+          <div
+            className={cn(s.bar, exhausted && s['bar-error'], 'absolute top-0 left-0 right-0 bottom-0')}
+            style={{ width: `${(provider.quota_used / provider.quota_limit * 100).toFixed(2)}%` }}
+          />
+          {Array(10).fill(0).map((i, k) => (
+            <div key={k} className={s['bar-item']} />
+          ))}
+        </div>
+        <div className={`
+          text-[13px] font-medium ${exhausted ? 'text-[#D92D20]' : 'text-gray-700'}
+        `}>{provider.quota_used}/{provider.quota_limit}</div>
+      </div>
+      {
+        exhausted && (
+          <div className='
+            px-4 py-3 leading-[18px] flex items-center text-[13px] text-gray-700 font-medium
+            bg-[#FFFAEB] border-t border-t-[rgba(0, 0, 0, 0.05)] rounded-b-xl
+          '>
+            {t('common.provider.anthropicHosted.usedUp')}
+          </div>
+        )
+      }
+    </div>
+  )
+}
+
+export default AnthropicHostedProvider

+ 0 - 0
web/app/components/header/account-setting/provider-page/anthropic-provider/index.module.css


+ 90 - 0
web/app/components/header/account-setting/provider-page/anthropic-provider/index.tsx

@@ -0,0 +1,90 @@
+import { useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import Link from 'next/link'
+import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
+import ProviderInput from '../provider-input'
+import type { ValidatedStatusState } from '../provider-input/useValidateToken'
+import useValidateToken, { ValidatedStatus } from '../provider-input/useValidateToken'
+import {
+  ValidatedErrorIcon,
+  ValidatedErrorOnOpenaiTip,
+  ValidatedSuccessIcon,
+  ValidatingTip,
+} from '../provider-input/Validate'
+import type { Provider, ProviderAnthropicToken } from '@/models/common'
+
+type AnthropicProviderProps = {
+  provider: Provider
+  onValidatedStatus: (status?: ValidatedStatusState) => void
+  onTokenChange: (token: ProviderAnthropicToken) => void
+}
+
+const AnthropicProvider = ({
+  provider,
+  onValidatedStatus,
+  onTokenChange,
+}: AnthropicProviderProps) => {
+  const { t } = useTranslation()
+  const [token, setToken] = useState<ProviderAnthropicToken>((provider.token as ProviderAnthropicToken) || { anthropic_api_key: '' })
+  const [validating, validatedStatus, setValidatedStatus, validate] = useValidateToken(provider.provider_name)
+  const handleFocus = () => {
+    if (token.anthropic_api_key === (provider.token as ProviderAnthropicToken).anthropic_api_key) {
+      setToken({ anthropic_api_key: '' })
+      onTokenChange({ anthropic_api_key: '' })
+      setValidatedStatus({})
+    }
+  }
+  const handleChange = (v: string) => {
+    const apiKey = { anthropic_api_key: v }
+    setToken(apiKey)
+    onTokenChange(apiKey)
+    validate(apiKey, {
+      beforeValidating: () => {
+        if (!v) {
+          setValidatedStatus({})
+          return false
+        }
+        return true
+      },
+    })
+  }
+  useEffect(() => {
+    if (typeof onValidatedStatus === 'function')
+      onValidatedStatus(validatedStatus)
+  }, [validatedStatus])
+
+  const getValidatedIcon = () => {
+    if (validatedStatus?.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed)
+      return <ValidatedErrorIcon />
+
+    if (validatedStatus.status === ValidatedStatus.Success)
+      return <ValidatedSuccessIcon />
+  }
+  const getValidatedTip = () => {
+    if (validating)
+      return <ValidatingTip />
+
+    if (validatedStatus?.status === ValidatedStatus.Error)
+      return <ValidatedErrorOnOpenaiTip errorMessage={validatedStatus.message ?? ''} />
+  }
+
+  return (
+    <div className='px-4 pt-3 pb-4'>
+      <ProviderInput
+        value={token.anthropic_api_key}
+        name={t('common.provider.apiKey')}
+        placeholder={t('common.provider.enterYourKey')}
+        onChange={handleChange}
+        onFocus={handleFocus}
+        validatedIcon={getValidatedIcon()}
+        validatedTip={getValidatedTip()}
+      />
+      <Link className="inline-flex items-center mt-3 text-xs font-normal cursor-pointer text-primary-600 w-fit" href="https://docs.anthropic.com/claude/reference/getting-started-with-the-api" target={'_blank'}>
+        {t('common.provider.anthropic.keyFrom')}
+        <ArrowTopRightOnSquareIcon className='w-3 h-3 ml-1 text-primary-600' aria-hidden="true" />
+      </Link>
+    </div>
+  )
+}
+
+export default AnthropicProvider

+ 18 - 0
web/app/components/header/account-setting/provider-page/index.tsx

@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
 import Link from 'next/link'
 import ProviderItem from './provider-item'
 import OpenaiHostedProvider from './openai-hosted-provider'
+import AnthropicHostedProvider from './anthropic-hosted-provider'
 import type { ProviderHosted } from '@/models/common'
 import { fetchProviders } from '@/service/common'
 import { IS_CE_EDITION } from '@/config'
@@ -18,6 +19,10 @@ const providersMap: { [k: string]: any } = {
     icon: 'azure',
     name: 'Azure OpenAI Service',
   },
+  'anthropic-custom': {
+    icon: 'anthropic',
+    name: 'Anthropic',
+  },
 }
 
 // const providersList = [
@@ -65,6 +70,8 @@ const ProviderPage = () => {
     }
   })
   const providerHosted = data?.filter(provider => provider.provider_name === 'openai' && provider.provider_type === 'system')?.[0]
+  const anthropicHosted = data?.filter(provider => provider.provider_name === 'anthropic' && provider.provider_type === 'system')?.[0]
+  const providedOpenaiProvider = data?.find(provider => provider.is_enabled && (provider.provider_name === 'openai' || provider.provider_name === 'azure_openai'))
 
   return (
     <div className='pb-7'>
@@ -78,6 +85,16 @@ const ProviderPage = () => {
           </>
         )
       }
+      {
+        anthropicHosted && !IS_CE_EDITION && (
+          <>
+            <div>
+              <AnthropicHostedProvider provider={anthropicHosted as ProviderHosted} />
+            </div>
+            <div className='my-5 w-full h-0 border-[0.5px] border-gray-100' />
+          </>
+        )
+      }
       <div>
         {
           providers?.map(providerItem => (
@@ -89,6 +106,7 @@ const ProviderPage = () => {
               activeId={activeProviderId}
               onActive={aid => setActiveProviderId(aid)}
               onSave={() => mutate()}
+              providedOpenaiProvider={providedOpenaiProvider}
             />
           ))
         }

+ 88 - 5
web/app/components/header/account-setting/provider-page/provider-item/index.tsx

@@ -5,14 +5,20 @@ import { useTranslation } from 'react-i18next'
 import Indicator from '../../../indicator'
 import OpenaiProvider from '../openai-provider'
 import AzureProvider from '../azure-provider'
+import AnthropicProvider from '../anthropic-provider'
 import type { ValidatedStatusState } from '../provider-input/useValidateToken'
 import { ValidatedStatus } from '../provider-input/useValidateToken'
 import s from './index.module.css'
-import type { Provider, ProviderAzureToken } from '@/models/common'
+import type { Provider, ProviderAnthropicToken, ProviderAzureToken } from '@/models/common'
 import { ProviderName } from '@/models/common'
 import { updateProviderAIKey } from '@/service/common'
 import { ToastContext } from '@/app/components/base/toast'
+import Tooltip from '@/app/components/base/tooltip'
 
+const providerNameMap: Record<string, string> = {
+  openai: 'OpenAI',
+  azure_openai: 'Azure OpenAI Service',
+}
 type IProviderItemProps = {
   icon: string
   name: string
@@ -20,6 +26,7 @@ type IProviderItemProps = {
   activeId: string
   onActive: (v: string) => void
   onSave: () => void
+  providedOpenaiProvider?: Provider
 }
 const ProviderItem = ({
   activeId,
@@ -28,15 +35,18 @@ const ProviderItem = ({
   provider,
   onActive,
   onSave,
+  providedOpenaiProvider,
 }: IProviderItemProps) => {
   const { t } = useTranslation()
   const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>()
   const [loading, setLoading] = useState(false)
   const { notify } = useContext(ToastContext)
-  const [token, setToken] = useState<ProviderAzureToken | string>(
+  const [token, setToken] = useState<ProviderAzureToken | string | ProviderAnthropicToken>(
     provider.provider_name === 'azure_openai'
       ? { openai_api_base: '', openai_api_key: '' }
-      : '',
+      : provider.provider_name === 'anthropic'
+        ? { anthropic_api_key: '' }
+        : '',
   )
   const id = `${provider.provider_name}-${provider.provider_type}`
   const isOpen = id === activeId
@@ -54,6 +64,8 @@ const ProviderItem = ({
     }
     if (provider.provider_name === ProviderName.OPENAI)
       return provider.token
+    if (provider.provider_name === ProviderName.ANTHROPIC)
+      return provider.token?.anthropic_api_key
   }
   const handleUpdateToken = async () => {
     if (loading)
@@ -81,7 +93,7 @@ const ProviderItem = ({
         <div className={cn(s[`icon-${icon}`], 'mr-3 w-6 h-6 rounded-md')} />
         <div className='grow text-sm font-medium text-gray-800'>{name}</div>
         {
-          providerTokenHasSetted() && !comingSoon && !isOpen && (
+          providerTokenHasSetted() && !comingSoon && !isOpen && provider.provider_name !== ProviderName.ANTHROPIC && (
             <div className='flex items-center mr-4'>
               {!isValid && <div className='text-xs text-[#D92D20]'>{t('common.provider.invalidApiKey')}</div>}
               <Indicator color={!isValid ? 'red' : 'green'} className='ml-2' />
@@ -89,7 +101,27 @@ const ProviderItem = ({
           )
         }
         {
-          !comingSoon && !isOpen && (
+          (providerTokenHasSetted() && !comingSoon && !isOpen && provider.provider_name === ProviderName.ANTHROPIC) && (
+            <div className='flex items-center mr-4'>
+              {
+                providedOpenaiProvider?.is_valid
+                  ? !isValid
+                    ? <div className='text-xs text-[#D92D20]'>{t('common.provider.invalidApiKey')}</div>
+                    : null
+                  : <div className='text-xs text-[#DC6803]'>{t('common.provider.anthropic.notEnabled')}</div>
+              }
+              <Indicator color={
+                providedOpenaiProvider?.is_valid
+                  ? isValid
+                    ? 'green'
+                    : 'red'
+                  : 'yellow'
+              } className='ml-2' />
+            </div>
+          )
+        }
+        {
+          !comingSoon && !isOpen && provider.provider_name !== ProviderName.ANTHROPIC && (
             <div className='
               px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
               text-xs font-medium text-gray-700 flex items-center
@@ -98,6 +130,34 @@ const ProviderItem = ({
             </div>
           )
         }
+        {
+          (!comingSoon && !isOpen && provider.provider_name === ProviderName.ANTHROPIC)
+            ? providedOpenaiProvider?.is_enabled
+              ? (
+                <div className='
+                  px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
+                  text-xs font-medium text-gray-700 flex items-center
+                ' onClick={() => providedOpenaiProvider.is_valid && onActive(id)}>
+                  {providerTokenHasSetted() ? t('common.provider.editKey') : t('common.provider.addKey')}
+                </div>
+              )
+              : (
+                <Tooltip
+                  htmlContent={<div className='w-[320px]'>
+                    {t('common.provider.anthropic.enableTip')}
+                  </div>}
+                  position='bottom'
+                  selector='anthropic-provider-enable-top-tooltip'>
+                  <div className='
+                    px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-not-allowed
+                    text-xs font-medium text-gray-700 flex items-center opacity-50
+                  '>
+                    {t('common.provider.addKey')}
+                  </div>
+                </Tooltip>
+              )
+            : null
+        }
         {
           comingSoon && !isOpen && (
             <div className='
@@ -147,6 +207,29 @@ const ProviderItem = ({
           />
         )
       }
+      {
+        provider.provider_name === ProviderName.ANTHROPIC && isOpen && (
+          <AnthropicProvider
+            provider={provider}
+            onValidatedStatus={v => setValidatedStatus(v)}
+            onTokenChange={v => setToken(v)}
+          />
+        )
+      }
+      {
+        provider.provider_name === ProviderName.ANTHROPIC && !isOpen && providerTokenHasSetted() && providedOpenaiProvider?.is_valid && (
+          <div className='px-4 py-3 text-[13px] font-medium text-gray-700'>
+            {t('common.provider.anthropic.using')} {providerNameMap[providedOpenaiProvider.provider_name as string]}
+          </div>
+        )
+      }
+      {
+        provider.provider_name === ProviderName.ANTHROPIC && !isOpen && providerTokenHasSetted() && !providedOpenaiProvider?.is_valid && (
+          <div className='px-4 py-3 bg-[#FFFAEB] text-[13px] font-medium text-gray-700'>
+            {t('common.provider.anthropic.enableTip')}
+          </div>
+        )
+      }
     </div>
   )
 }

+ 1 - 1
web/app/components/share/chat/index.tsx

@@ -620,7 +620,7 @@ const Main: FC<IMainProps> = ({
 
           {
             hasSetInputs && (
-              <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[66px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
+              <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
                 <div className='h-full overflow-y-auto' ref={chatListDomRef}>
                   <Chat
                     chatList={chatList}

+ 1 - 1
web/app/components/share/chatbot/index.tsx

@@ -609,7 +609,7 @@ const Main: FC<IMainProps> = ({
 
           {
             hasSetInputs && (
-              <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[66px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
+              <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
                 <div className='h-full overflow-y-auto' ref={chatListDomRef}>
                   <Chat
                     chatList={chatList}

+ 17 - 1
web/i18n/lang/common.en.ts

@@ -54,7 +54,7 @@ const translation = {
       maxTokenTip:
         'Max tokens depending on the model. Prompt and completion share this limit. One token is roughly 1 English character.',
       maxTokenSettingTip: 'Your max token setting is high, potentially limiting space for prompts, queries, and data. Consider setting it below 2/3.',
-      setToCurrentModelMaxTokenTip: 'Max token is updated to the maximum token of the current model 4,000.',
+      setToCurrentModelMaxTokenTip: 'Max token is updated to the maximum token of the current model {{maxToken}}.',
     },
     tone: {
       Creative: 'Creative',
@@ -180,6 +180,22 @@ const translation = {
       useYourModel: 'Currently using own Model Provider.',
       close: 'Close',
     },
+    anthropicHosted: {
+      anthropicHosted: 'Anthropic Claude',
+      onTrial: 'ON TRIAL',
+      exhausted: 'QUOTA EXHAUSTED',
+      desc: 'Powerful model, which excels at a wide range of tasks from sophisticated dialogue and creative content generation to detailed instruction.',
+      callTimes: 'Call times',
+      usedUp: 'Trial quota used up. Add own Model Provider.',
+      useYourModel: 'Currently using own Model Provider.',
+      close: 'Close',
+    },
+    anthropic: {
+      using: 'The embedding capability is using',
+      enableTip: 'To enable the Anthropic model, you need to bind to OpenAI or Azure OpenAI Service first.',
+      notEnabled: 'Not enabled',
+      keyFrom: 'Get your API key from Anthropic',
+    },
     encrypted: {
       front: 'Your API KEY will be encrypted and stored using',
       back: ' technology.',

+ 17 - 1
web/i18n/lang/common.zh.ts

@@ -54,7 +54,7 @@ const translation = {
       maxTokenTip:
         '生成的最大令牌数取决于模型。提示和完成共享令牌数限制。一个令牌约等于 1 个英文或 半个中文字符。',
       maxTokenSettingTip: '您设置的最大 tokens 数较大,可能会导致 prompt、用户问题、数据集内容没有 token 空间进行处理,建议设置到 2/3 以下。',
-      setToCurrentModelMaxTokenTip: '最大令牌数更新为当前模型最大的令牌数 4,000。',
+      setToCurrentModelMaxTokenTip: '最大令牌数更新为当前模型最大的令牌数 {{maxToken}}。',
     },
     tone: {
       Creative: '创意',
@@ -180,6 +180,22 @@ const translation = {
       useYourModel: '当前正在使用你自己的模型供应商。',
       close: '关闭',
     },
+    anthropicHosted: {
+      anthropicHosted: 'Anthropic Claude',
+      onTrial: '体验',
+      exhausted: '超出限额',
+      desc: '功能强大的模型,擅长执行从复杂对话和创意内容生成到详细指导的各种任务。',
+      callTimes: '调用次数',
+      usedUp: '试用额度已用完,请在下方添加自己的模型供应商',
+      useYourModel: '当前正在使用你自己的模型供应商。',
+      close: '关闭',
+    },
+    anthropic: {
+      using: '嵌入能力正在使用',
+      enableTip: '要启用 Anthropic 模型,您需要先绑定 OpenAI 或 Azure OpenAI 服务。',
+      notEnabled: '未启用',
+      keyFrom: '从 Anthropic 获取您的 API 密钥',
+    },
     encrypted: {
       front: '密钥将使用 ',
       back: ' 技术进行加密和存储。',

+ 5 - 0
web/models/common.ts

@@ -59,14 +59,19 @@ export type Member = Pick<UserProfileResponse, 'id' | 'name' | 'email' | 'last_l
 export enum ProviderName {
   OPENAI = 'openai',
   AZURE_OPENAI = 'azure_openai',
+  ANTHROPIC = 'anthropic',
 }
 export type ProviderAzureToken = {
   openai_api_base?: string
   openai_api_key?: string
 }
+export type ProviderAnthropicToken = {
+  anthropic_api_key?: string
+}
 export type ProviderTokenType = {
   [ProviderName.OPENAI]: string
   [ProviderName.AZURE_OPENAI]: ProviderAzureToken
+  [ProviderName.ANTHROPIC]: ProviderAnthropicToken
 }
 export type Provider = {
   [Name in ProviderName]: {

+ 2 - 2
web/service/common.ts

@@ -3,7 +3,7 @@ import { del, get, patch, post, put } from './base'
 import type {
   AccountIntegrate, CommonResponse, DataSourceNotion,
   IWorkspace, LangGeniusVersionResponse, Member,
-  OauthResponse, Provider, ProviderAzureToken, TenantInfoResponse,
+  OauthResponse, Provider, ProviderAnthropicToken, ProviderAzureToken, TenantInfoResponse,
   UserProfileOriginResponse,
 } from '@/models/common'
 import type {
@@ -58,7 +58,7 @@ export const fetchProviders: Fetcher<Provider[] | null, { url: string; params: R
 export const validateProviderKey: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: { token: string } }> = ({ url, body }) => {
   return post(url, { body }) as Promise<ValidateOpenAIKeyResponse>
 }
-export const updateProviderAIKey: Fetcher<UpdateOpenAIKeyResponse, { url: string; body: { token: string | ProviderAzureToken } }> = ({ url, body }) => {
+export const updateProviderAIKey: Fetcher<UpdateOpenAIKeyResponse, { url: string; body: { token: string | ProviderAzureToken | ProviderAnthropicToken } }> = ({ url, body }) => {
   return post(url, { body }) as Promise<UpdateOpenAIKeyResponse>
 }
 

+ 5 - 0
web/types/app.ts

@@ -1,3 +1,8 @@
+export enum ProviderType {
+  openai = 'openai',
+  anthropic = 'anthropic',
+}
+
 export enum AppType {
   'chat' = 'chat',
   'completion' = 'completion',

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů