mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-18 17:35:55 +08:00
feat: frontend support claude (#573)
Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
This commit is contained in:
parent
7599f79a17
commit
8e11200306
File diff suppressed because one or more lines are too long
@ -372,7 +372,7 @@ const Debug: FC<IDebug> = ({
|
|||||||
{/* Chat */}
|
{/* Chat */}
|
||||||
{mode === AppType.chat && (
|
{mode === AppType.chat && (
|
||||||
<div className="mt-[34px] h-full flex flex-col">
|
<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}>
|
<div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}>
|
||||||
<Chat
|
<Chat
|
||||||
chatList={chatList}
|
chatList={chatList}
|
||||||
|
@ -16,6 +16,7 @@ import ConfigModel from '@/app/components/app/configuration/config-model'
|
|||||||
import Config from '@/app/components/app/configuration/config'
|
import Config from '@/app/components/app/configuration/config'
|
||||||
import Debug from '@/app/components/app/configuration/debug'
|
import Debug from '@/app/components/app/configuration/debug'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
import Confirm from '@/app/components/base/confirm'
|
||||||
|
import { ProviderType } from '@/types/app'
|
||||||
import type { AppDetailResponse } from '@/models/app'
|
import type { AppDetailResponse } from '@/models/app'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
import { fetchTenantInfo } from '@/service/common'
|
import { fetchTenantInfo } from '@/service/common'
|
||||||
@ -67,7 +68,7 @@ const Configuration: FC = () => {
|
|||||||
frequency_penalty: 1, // -2-2
|
frequency_penalty: 1, // -2-2
|
||||||
})
|
})
|
||||||
const [modelConfig, doSetModelConfig] = useState<ModelConfig>({
|
const [modelConfig, doSetModelConfig] = useState<ModelConfig>({
|
||||||
provider: 'openai',
|
provider: ProviderType.openai,
|
||||||
model_id: 'gpt-3.5-turbo',
|
model_id: 'gpt-3.5-turbo',
|
||||||
configs: {
|
configs: {
|
||||||
prompt_template: '',
|
prompt_template: '',
|
||||||
@ -84,8 +85,9 @@ const Configuration: FC = () => {
|
|||||||
doSetModelConfig(newModelConfig)
|
doSetModelConfig(newModelConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setModelId = (modelId: string) => {
|
const setModelId = (modelId: string, provider: ProviderType) => {
|
||||||
const newModelConfig = produce(modelConfig, (draft: any) => {
|
const newModelConfig = produce(modelConfig, (draft: any) => {
|
||||||
|
draft.provider = provider
|
||||||
draft.model_id = modelId
|
draft.model_id = modelId
|
||||||
})
|
})
|
||||||
setModelConfig(newModelConfig)
|
setModelConfig(newModelConfig)
|
||||||
|
@ -19,6 +19,7 @@ const AutoHeightTextarea = forwardRef(
|
|||||||
{ value, onChange, placeholder, className, minHeight = 36, maxHeight = 96, autoFocus, controlFocus, onKeyDown, onKeyUp }: IProps,
|
{ value, onChange, placeholder, className, minHeight = 36, maxHeight = 96, autoFocus, controlFocus, onKeyDown, onKeyUp }: IProps,
|
||||||
outerRef: any,
|
outerRef: any,
|
||||||
) => {
|
) => {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const ref = outerRef || useRef<HTMLTextAreaElement>(null)
|
const ref = outerRef || useRef<HTMLTextAreaElement>(null)
|
||||||
|
|
||||||
const doFocus = () => {
|
const doFocus = () => {
|
||||||
@ -54,13 +55,20 @@ const AutoHeightTextarea = forwardRef(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<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 ')}
|
{!value ? placeholder : value.replace(/\n$/, '\n ')}
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
ref={ref}
|
ref={ref}
|
||||||
autoFocus={autoFocus}
|
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}
|
placeholder={placeholder}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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 +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
|
@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import ProviderItem from './provider-item'
|
import ProviderItem from './provider-item'
|
||||||
import OpenaiHostedProvider from './openai-hosted-provider'
|
import OpenaiHostedProvider from './openai-hosted-provider'
|
||||||
|
import AnthropicHostedProvider from './anthropic-hosted-provider'
|
||||||
import type { ProviderHosted } from '@/models/common'
|
import type { ProviderHosted } from '@/models/common'
|
||||||
import { fetchProviders } from '@/service/common'
|
import { fetchProviders } from '@/service/common'
|
||||||
import { IS_CE_EDITION } from '@/config'
|
import { IS_CE_EDITION } from '@/config'
|
||||||
@ -18,6 +19,10 @@ const providersMap: { [k: string]: any } = {
|
|||||||
icon: 'azure',
|
icon: 'azure',
|
||||||
name: 'Azure OpenAI Service',
|
name: 'Azure OpenAI Service',
|
||||||
},
|
},
|
||||||
|
'anthropic-custom': {
|
||||||
|
icon: 'anthropic',
|
||||||
|
name: 'Anthropic',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// const providersList = [
|
// const providersList = [
|
||||||
@ -65,6 +70,8 @@ const ProviderPage = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const providerHosted = data?.filter(provider => provider.provider_name === 'openai' && provider.provider_type === 'system')?.[0]
|
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 (
|
return (
|
||||||
<div className='pb-7'>
|
<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>
|
<div>
|
||||||
{
|
{
|
||||||
providers?.map(providerItem => (
|
providers?.map(providerItem => (
|
||||||
@ -89,6 +106,7 @@ const ProviderPage = () => {
|
|||||||
activeId={activeProviderId}
|
activeId={activeProviderId}
|
||||||
onActive={aid => setActiveProviderId(aid)}
|
onActive={aid => setActiveProviderId(aid)}
|
||||||
onSave={() => mutate()}
|
onSave={() => mutate()}
|
||||||
|
providedOpenaiProvider={providedOpenaiProvider}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,20 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import Indicator from '../../../indicator'
|
import Indicator from '../../../indicator'
|
||||||
import OpenaiProvider from '../openai-provider'
|
import OpenaiProvider from '../openai-provider'
|
||||||
import AzureProvider from '../azure-provider'
|
import AzureProvider from '../azure-provider'
|
||||||
|
import AnthropicProvider from '../anthropic-provider'
|
||||||
import type { ValidatedStatusState } from '../provider-input/useValidateToken'
|
import type { ValidatedStatusState } from '../provider-input/useValidateToken'
|
||||||
import { ValidatedStatus } from '../provider-input/useValidateToken'
|
import { ValidatedStatus } from '../provider-input/useValidateToken'
|
||||||
import s from './index.module.css'
|
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 { ProviderName } from '@/models/common'
|
||||||
import { updateProviderAIKey } from '@/service/common'
|
import { updateProviderAIKey } from '@/service/common'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
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 = {
|
type IProviderItemProps = {
|
||||||
icon: string
|
icon: string
|
||||||
name: string
|
name: string
|
||||||
@ -20,6 +26,7 @@ type IProviderItemProps = {
|
|||||||
activeId: string
|
activeId: string
|
||||||
onActive: (v: string) => void
|
onActive: (v: string) => void
|
||||||
onSave: () => void
|
onSave: () => void
|
||||||
|
providedOpenaiProvider?: Provider
|
||||||
}
|
}
|
||||||
const ProviderItem = ({
|
const ProviderItem = ({
|
||||||
activeId,
|
activeId,
|
||||||
@ -28,14 +35,17 @@ const ProviderItem = ({
|
|||||||
provider,
|
provider,
|
||||||
onActive,
|
onActive,
|
||||||
onSave,
|
onSave,
|
||||||
|
providedOpenaiProvider,
|
||||||
}: IProviderItemProps) => {
|
}: IProviderItemProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>()
|
const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const [token, setToken] = useState<ProviderAzureToken | string>(
|
const [token, setToken] = useState<ProviderAzureToken | string | ProviderAnthropicToken>(
|
||||||
provider.provider_name === 'azure_openai'
|
provider.provider_name === 'azure_openai'
|
||||||
? { openai_api_base: '', openai_api_key: '' }
|
? { openai_api_base: '', openai_api_key: '' }
|
||||||
|
: provider.provider_name === 'anthropic'
|
||||||
|
? { anthropic_api_key: '' }
|
||||||
: '',
|
: '',
|
||||||
)
|
)
|
||||||
const id = `${provider.provider_name}-${provider.provider_type}`
|
const id = `${provider.provider_name}-${provider.provider_type}`
|
||||||
@ -54,6 +64,8 @@ const ProviderItem = ({
|
|||||||
}
|
}
|
||||||
if (provider.provider_name === ProviderName.OPENAI)
|
if (provider.provider_name === ProviderName.OPENAI)
|
||||||
return provider.token
|
return provider.token
|
||||||
|
if (provider.provider_name === ProviderName.ANTHROPIC)
|
||||||
|
return provider.token?.anthropic_api_key
|
||||||
}
|
}
|
||||||
const handleUpdateToken = async () => {
|
const handleUpdateToken = async () => {
|
||||||
if (loading)
|
if (loading)
|
||||||
@ -81,7 +93,7 @@ const ProviderItem = ({
|
|||||||
<div className={cn(s[`icon-${icon}`], 'mr-3 w-6 h-6 rounded-md')} />
|
<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>
|
<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'>
|
<div className='flex items-center mr-4'>
|
||||||
{!isValid && <div className='text-xs text-[#D92D20]'>{t('common.provider.invalidApiKey')}</div>}
|
{!isValid && <div className='text-xs text-[#D92D20]'>{t('common.provider.invalidApiKey')}</div>}
|
||||||
<Indicator color={!isValid ? 'red' : 'green'} className='ml-2' />
|
<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='
|
<div className='
|
||||||
px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
|
px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
|
||||||
text-xs font-medium text-gray-700 flex items-center
|
text-xs font-medium text-gray-700 flex items-center
|
||||||
@ -98,6 +130,34 @@ const ProviderItem = ({
|
|||||||
</div>
|
</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 && (
|
comingSoon && !isOpen && (
|
||||||
<div className='
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -620,7 +620,7 @@ const Main: FC<IMainProps> = ({
|
|||||||
|
|
||||||
{
|
{
|
||||||
hasSetInputs && (
|
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}>
|
<div className='h-full overflow-y-auto' ref={chatListDomRef}>
|
||||||
<Chat
|
<Chat
|
||||||
chatList={chatList}
|
chatList={chatList}
|
||||||
|
@ -609,7 +609,7 @@ const Main: FC<IMainProps> = ({
|
|||||||
|
|
||||||
{
|
{
|
||||||
hasSetInputs && (
|
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}>
|
<div className='h-full overflow-y-auto' ref={chatListDomRef}>
|
||||||
<Chat
|
<Chat
|
||||||
chatList={chatList}
|
chatList={chatList}
|
||||||
|
@ -54,7 +54,7 @@ const translation = {
|
|||||||
maxTokenTip:
|
maxTokenTip:
|
||||||
'Max tokens depending on the model. Prompt and completion share this limit. One token is roughly 1 English character.',
|
'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.',
|
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: {
|
tone: {
|
||||||
Creative: 'Creative',
|
Creative: 'Creative',
|
||||||
@ -180,6 +180,22 @@ const translation = {
|
|||||||
useYourModel: 'Currently using own Model Provider.',
|
useYourModel: 'Currently using own Model Provider.',
|
||||||
close: 'Close',
|
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: {
|
encrypted: {
|
||||||
front: 'Your API KEY will be encrypted and stored using',
|
front: 'Your API KEY will be encrypted and stored using',
|
||||||
back: ' technology.',
|
back: ' technology.',
|
||||||
|
@ -54,7 +54,7 @@ const translation = {
|
|||||||
maxTokenTip:
|
maxTokenTip:
|
||||||
'生成的最大令牌数取决于模型。提示和完成共享令牌数限制。一个令牌约等于 1 个英文或 半个中文字符。',
|
'生成的最大令牌数取决于模型。提示和完成共享令牌数限制。一个令牌约等于 1 个英文或 半个中文字符。',
|
||||||
maxTokenSettingTip: '您设置的最大 tokens 数较大,可能会导致 prompt、用户问题、数据集内容没有 token 空间进行处理,建议设置到 2/3 以下。',
|
maxTokenSettingTip: '您设置的最大 tokens 数较大,可能会导致 prompt、用户问题、数据集内容没有 token 空间进行处理,建议设置到 2/3 以下。',
|
||||||
setToCurrentModelMaxTokenTip: '最大令牌数更新为当前模型最大的令牌数 4,000。',
|
setToCurrentModelMaxTokenTip: '最大令牌数更新为当前模型最大的令牌数 {{maxToken}}。',
|
||||||
},
|
},
|
||||||
tone: {
|
tone: {
|
||||||
Creative: '创意',
|
Creative: '创意',
|
||||||
@ -180,6 +180,22 @@ const translation = {
|
|||||||
useYourModel: '当前正在使用你自己的模型供应商。',
|
useYourModel: '当前正在使用你自己的模型供应商。',
|
||||||
close: '关闭',
|
close: '关闭',
|
||||||
},
|
},
|
||||||
|
anthropicHosted: {
|
||||||
|
anthropicHosted: 'Anthropic Claude',
|
||||||
|
onTrial: '体验',
|
||||||
|
exhausted: '超出限额',
|
||||||
|
desc: '功能强大的模型,擅长执行从复杂对话和创意内容生成到详细指导的各种任务。',
|
||||||
|
callTimes: '调用次数',
|
||||||
|
usedUp: '试用额度已用完,请在下方添加自己的模型供应商',
|
||||||
|
useYourModel: '当前正在使用你自己的模型供应商。',
|
||||||
|
close: '关闭',
|
||||||
|
},
|
||||||
|
anthropic: {
|
||||||
|
using: '嵌入能力正在使用',
|
||||||
|
enableTip: '要启用 Anthropic 模型,您需要先绑定 OpenAI 或 Azure OpenAI 服务。',
|
||||||
|
notEnabled: '未启用',
|
||||||
|
keyFrom: '从 Anthropic 获取您的 API 密钥',
|
||||||
|
},
|
||||||
encrypted: {
|
encrypted: {
|
||||||
front: '密钥将使用 ',
|
front: '密钥将使用 ',
|
||||||
back: ' 技术进行加密和存储。',
|
back: ' 技术进行加密和存储。',
|
||||||
|
@ -59,14 +59,19 @@ export type Member = Pick<UserProfileResponse, 'id' | 'name' | 'email' | 'last_l
|
|||||||
export enum ProviderName {
|
export enum ProviderName {
|
||||||
OPENAI = 'openai',
|
OPENAI = 'openai',
|
||||||
AZURE_OPENAI = 'azure_openai',
|
AZURE_OPENAI = 'azure_openai',
|
||||||
|
ANTHROPIC = 'anthropic',
|
||||||
}
|
}
|
||||||
export type ProviderAzureToken = {
|
export type ProviderAzureToken = {
|
||||||
openai_api_base?: string
|
openai_api_base?: string
|
||||||
openai_api_key?: string
|
openai_api_key?: string
|
||||||
}
|
}
|
||||||
|
export type ProviderAnthropicToken = {
|
||||||
|
anthropic_api_key?: string
|
||||||
|
}
|
||||||
export type ProviderTokenType = {
|
export type ProviderTokenType = {
|
||||||
[ProviderName.OPENAI]: string
|
[ProviderName.OPENAI]: string
|
||||||
[ProviderName.AZURE_OPENAI]: ProviderAzureToken
|
[ProviderName.AZURE_OPENAI]: ProviderAzureToken
|
||||||
|
[ProviderName.ANTHROPIC]: ProviderAnthropicToken
|
||||||
}
|
}
|
||||||
export type Provider = {
|
export type Provider = {
|
||||||
[Name in ProviderName]: {
|
[Name in ProviderName]: {
|
||||||
|
@ -3,7 +3,7 @@ import { del, get, patch, post, put } from './base'
|
|||||||
import type {
|
import type {
|
||||||
AccountIntegrate, CommonResponse, DataSourceNotion,
|
AccountIntegrate, CommonResponse, DataSourceNotion,
|
||||||
IWorkspace, LangGeniusVersionResponse, Member,
|
IWorkspace, LangGeniusVersionResponse, Member,
|
||||||
OauthResponse, Provider, ProviderAzureToken, TenantInfoResponse,
|
OauthResponse, Provider, ProviderAnthropicToken, ProviderAzureToken, TenantInfoResponse,
|
||||||
UserProfileOriginResponse,
|
UserProfileOriginResponse,
|
||||||
} from '@/models/common'
|
} from '@/models/common'
|
||||||
import type {
|
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 }) => {
|
export const validateProviderKey: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: { token: string } }> = ({ url, body }) => {
|
||||||
return post(url, { body }) as Promise<ValidateOpenAIKeyResponse>
|
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>
|
return post(url, { body }) as Promise<UpdateOpenAIKeyResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
export enum ProviderType {
|
||||||
|
openai = 'openai',
|
||||||
|
anthropic = 'anthropic',
|
||||||
|
}
|
||||||
|
|
||||||
export enum AppType {
|
export enum AppType {
|
||||||
'chat' = 'chat',
|
'chat' = 'chat',
|
||||||
'completion' = 'completion',
|
'completion' = 'completion',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user