Browse Source

frontend for model runtime (#1861)

Co-authored-by: Joel <iamjoel007@gmail.com>
tags/0.4.0
zxhlyh 1 year ago
parent
commit
d70d61b1cb
No account linked to committer's email address
29 changed files with 1448 additions and 923 deletions
  1. 1
    1
      web/app/components/app/chat/answer/index.tsx
  2. 0
    22
      web/app/components/app/configuration/debug/index.tsx
  3. 1
    1
      web/app/components/app/configuration/index.tsx
  4. 1
    1
      web/app/components/datasets/create/step-two/index.tsx
  5. 4
    4
      web/app/components/header/account-setting/model-provider-page/declarations.ts
  6. 5
    3
      web/app/components/header/account-setting/model-provider-page/index.tsx
  7. 0
    0
      web/app/components/header/account-setting/model-provider-page/model-display/index.tsx
  8. 12
    0
      web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
  9. 6
    0
      web/app/components/header/account-setting/model-provider-page/model-modal/Input.tsx
  10. 2
    1
      web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx
  11. 2
    2
      web/app/components/header/account-setting/model-provider-page/model-name/index.tsx
  12. 74
    28
      web/app/components/header/account-setting/model-provider-page/model-parameter-modal/index.tsx
  13. 40
    24
      web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx
  14. 46
    0
      web/app/components/header/account-setting/model-provider-page/model-selector/deprecated-model-trigger.tsx
  15. 36
    36
      web/app/components/header/account-setting/model-provider-page/model-selector/feature-icon.tsx
  16. 11
    1
      web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx
  17. 23
    4
      web/app/components/header/account-setting/model-provider-page/model-selector/model-trigger.tsx
  18. 1
    1
      web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx
  19. 2
    1
      web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx
  20. 1
    1
      web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx
  21. 9
    1
      web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx
  22. 3
    2
      web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx
  23. 26
    24
      web/app/components/share/chat/index.tsx
  24. 3
    0
      web/i18n/lang/common.en.ts
  25. 3
    0
      web/i18n/lang/common.zh.ts
  26. 4
    8
      web/service/base.ts
  27. 3
    4
      web/service/debug.ts
  28. 3
    4
      web/service/share.ts
  29. 1126
    749
      web/yarn.lock

+ 1
- 1
web/app/components/app/chat/answer/index.tsx View File

className={cn(s.copyBtn, 'mr-1')} className={cn(s.copyBtn, 'mr-1')}
/> />
)} )}
{supportAnnotation && (
{(supportAnnotation && !item.isOpeningStatement) && (
<AnnotationCtrlBtn <AnnotationCtrlBtn
appId={appId!} appId={appId!}
messageId={id} messageId={id}

+ 0
- 22
web/app/components/app/configuration/debug/index.tsx View File

} }
}, },
onMessageEnd: (messageEnd) => { onMessageEnd: (messageEnd) => {
// TODO
if (messageEnd.metadata?.annotation_reply) { if (messageEnd.metadata?.annotation_reply) {
responseItem.id = messageEnd.id responseItem.id = messageEnd.id
responseItem.annotation = ({ responseItem.annotation = ({
onMessageReplace: (messageReplace) => { onMessageReplace: (messageReplace) => {
responseItem.content = messageReplace.answer responseItem.content = messageReplace.answer
}, },
onAnnotationReply: (annotationReply) => {
// TODO: temp debug
responseItem.id = annotationReply.id
responseItem.content = annotationReply.answer
responseItem.annotation = ({
id: annotationReply.annotation_id,
authorName: annotationReply.annotation_author_name,
} as AnnotationType)
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })

draft.push({
...responseItem,
id: annotationReply.id,
})
})
setChatList(newListWithAnswer)
},
onError() { onError() {
setResponsingFalse() setResponsingFalse()
// role back placeholder answer // role back placeholder answer

+ 1
- 1
web/app/components/app/configuration/index.tsx View File

...visionConfig, ...visionConfig,
enabled: supportVision, enabled: supportVision,
}, true) }, true)
setCompletionParams({})
} }


const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision) const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
onCompletionParamsChange={(newParams: FormValue) => { onCompletionParamsChange={(newParams: FormValue) => {
setCompletionParams(newParams) setCompletionParams(newParams)
}} }}
disabled={!hasSettedApiKey}
/> />
<div className='w-[1px] h-[14px] bg-gray-200'></div> <div className='w-[1px] h-[14px] bg-gray-200'></div>
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button> <Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>

+ 1
- 1
web/app/components/datasets/create/step-two/index.tsx View File

</div> </div>
</div> </div>
<div className={s.formFooter}> <div className={s.formFooter}>
<Button type="primary" className={cn(s.button, '!h-8 text-primary-600')} onClick={confirmChangeCustomConfig}>{t('datasetCreation.stepTwo.preview')}</Button>
<Button type="primary" className={cn(s.button, '!h-8')} onClick={confirmChangeCustomConfig}>{t('datasetCreation.stepTwo.preview')}</Button>
<Button className={cn(s.button, 'ml-2 !h-8')} onClick={resetRules}>{t('datasetCreation.stepTwo.reset')}</Button> <Button className={cn(s.button, 'ml-2 !h-8')} onClick={resetRules}>{t('datasetCreation.stepTwo.reset')}</Button>
</div> </div>
</div> </div>

+ 4
- 4
web/app/components/header/account-setting/model-provider-page/declarations.ts View File

noPermission = 'no-permission', noPermission = 'no-permission',
} }


export const MODEL_STATUS_TEXT = {
[ModelStatusEnum.noConfigure]: {
export const MODEL_STATUS_TEXT: { [k: string]: TypeWithI18N } = {
'no-configure': {
en_US: 'No Configure', en_US: 'No Configure',
zh_Hans: '未配置凭据', zh_Hans: '未配置凭据',
}, },
[ModelStatusEnum.quotaExceeded]: {
'quota-exceeded': {
en_US: 'Quota Exceeded', en_US: 'Quota Exceeded',
zh_Hans: '额度不足', zh_Hans: '额度不足',
}, },
[ModelStatusEnum.noPermission]: {
'no-permission': {
en_US: 'No Permission', en_US: 'No Permission',
zh_Hans: '无使用权限', zh_Hans: '无使用权限',
}, },

+ 5
- 3
web/app/components/header/account-setting/model-provider-page/index.tsx View File

import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card' import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card'
import ProviderCard from './provider-card' import ProviderCard from './provider-card'
import type { import type {
ConfigurateMethodEnum,
CustomConfigrationModelFixedFields, CustomConfigrationModelFixedFields,
ModelProvider, ModelProvider,
} from './declarations' } from './declarations'
import { CustomConfigurationStatusEnum } from './declarations'
import {
ConfigurateMethodEnum,
CustomConfigurationStatusEnum,
} from './declarations'
import { import {
useDefaultModel, useDefaultModel,
useUpdateModelProvidersAndModelList, useUpdateModelProvidersAndModelList,
onSaveCallback: () => { onSaveCallback: () => {
updateModelProvidersAndModelList() updateModelProvidersAndModelList()


if (customConfigrationModelFixedFields && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) {
if (configurateMethod === ConfigurateMethodEnum.customizableModel && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) {
eventEmitter?.emit({ eventEmitter?.emit({
type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST, type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST,
payload: provider.provider, payload: provider.provider,

+ 0
- 0
web/app/components/header/account-setting/model-provider-page/model-display/index.tsx View File


+ 12
- 0
web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx View File

validating: boolean validating: boolean
validatedSuccess?: boolean validatedSuccess?: boolean
showOnVariableMap: Record<string, string[]> showOnVariableMap: Record<string, string[]>
isEditMode: boolean
} }


const Form: FC<FormProps> = ({ const Form: FC<FormProps> = ({
validating, validating,
validatedSuccess, validatedSuccess,
showOnVariableMap, showOnVariableMap,
isEditMode,
}) => { }) => {
const language = useLanguage() const language = useLanguage()
const [changeKey, setChangeKey] = useState('') const [changeKey, setChangeKey] = useState('')


const handleFormChange = (key: string, val: string) => { const handleFormChange = (key: string, val: string) => {
if (isEditMode && (key === '__model_type' || key === '__model_name'))
return

setChangeKey(key) setChangeKey(key)
const shouldClearVariable: Record<string, string | undefined> = {} const shouldClearVariable: Record<string, string | undefined> = {}
if (showOnVariableMap[key]?.length) { if (showOnVariableMap[key]?.length) {
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return null return null


const disabed = isEditMode && (variable === '__model_type' || variable === '__model_name')

return ( return (
<div key={variable} className='py-3'> <div key={variable} className='py-3'>
<div className='py-2 text-sm text-gray-900'> <div className='py-2 text-sm text-gray-900'>
} }
</div> </div>
<Input <Input
className={`${disabed && 'cursor-not-allowed opacity-60'}`}
value={value[variable] as string} value={value[variable] as string}
onChange={val => handleFormChange(variable, val)} onChange={val => handleFormChange(variable, val)}
validated={validatedSuccess} validated={validatedSuccess}
placeholder={placeholder?.[language]} placeholder={placeholder?.[language]}
disabled={disabed}
/> />
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
</div> </div>
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return null return null


const disabed = isEditMode && (variable === '__model_type' || variable === '__model_name')

return ( return (
<div key={variable} className='py-3'> <div key={variable} className='py-3'>
<div className='py-2 text-sm text-gray-900'> <div className='py-2 text-sm text-gray-900'>
className={` className={`
flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
${value[variable] === option.value && 'bg-white border-[1.5px] border-primary-400 shadow-sm'} ${value[variable] === option.value && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
${disabed && '!cursor-not-allowed opacity-60'}
`} `}
onClick={() => handleFormChange(variable, option.value)} onClick={() => handleFormChange(variable, option.value)}
key={`${variable}-${option.value}`} key={`${variable}-${option.value}`}

+ 6
- 0
web/app/components/header/account-setting/model-provider-page/model-modal/Input.tsx View File

onFocus?: () => void onFocus?: () => void
placeholder?: string placeholder?: string
validated?: boolean validated?: boolean
className?: string
disabled?: boolean
} }
const Input: FC<InputProps> = ({ const Input: FC<InputProps> = ({
value, value,
onFocus, onFocus,
placeholder, placeholder,
validated, validated,
className,
disabled,
}) => { }) => {
return ( return (
<div className='relative'> <div className='relative'>
focus:bg-white focus:border-gray-300 focus:shadow-xs focus:bg-white focus:border-gray-300 focus:shadow-xs
placeholder:text-sm placeholder:text-gray-400 placeholder:text-sm placeholder:text-gray-400
${validated && 'pr-[30px]'} ${validated && 'pr-[30px]'}
${className}
`} `}
placeholder={placeholder || ''} placeholder={placeholder || ''}
onChange={e => onChange(e.target.value)} onChange={e => onChange(e.target.value)}
onFocus={onFocus} onFocus={onFocus}
value={value || ''} value={value || ''}
disabled={disabled}
/> />
{ {
validated && ( validated && (

+ 2
- 1
web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx View File

validating={validating} validating={validating}
validatedSuccess={validatedStatusState.status === ValidatedStatus.Success} validatedSuccess={validatedStatusState.status === ValidatedStatus.Success}
showOnVariableMap={showOnVariableMap} showOnVariableMap={showOnVariableMap}
isEditMode={isEditMode}
/> />
<div className='sticky bottom-0 flex justify-between items-center py-6 flex-wrap gap-y-2 bg-white'> <div className='sticky bottom-0 flex justify-between items-center py-6 flex-wrap gap-y-2 bg-white'>
{ {
{ {
showConfirm && ( showConfirm && (
<ConfirmCommon <ConfirmCommon
title='Are you sure?'
title={t('common.modelProvider.confirmDelete')}
isShow={showConfirm} isShow={showConfirm}
onCancel={() => setShowConfirm(false)} onCancel={() => setShowConfirm(false)}
onConfirm={handleRemove} onConfirm={handleRemove}

+ 2
- 2
web/app/components/header/account-setting/model-provider-page/model-name/index.tsx View File

`} `}
> >
<div <div
className='mr-2 truncate'
className='mr-1 truncate'
title={modelItem.label[language]} title={modelItem.label[language]}
> >
{modelItem.label[language]} {modelItem.label[language]}
</div> </div>
{ {
showModelType && (
showModelType && modelItem.model_type && (
<ModelBadge className={`mr-0.5 ${modelTypeClassName}`}> <ModelBadge className={`mr-0.5 ${modelTypeClassName}`}>
{modelTypeFormat(modelItem.model_type)} {modelTypeFormat(modelItem.model_type)}
</ModelBadge> </ModelBadge>

+ 74
- 28
web/app/components/header/account-setting/model-provider-page/model-parameter-modal/index.tsx View File

import type { FC } from 'react' import type { FC } from 'react'
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { import type {
FormValue, FormValue,
ModelParameterRule, ModelParameterRule,
} from '../declarations' } from '../declarations'
import {
MODEL_STATUS_TEXT,
ModelStatusEnum,
} from '../declarations'
import ModelIcon from '../model-icon' import ModelIcon from '../model-icon'
import ModelName from '../model-name' import ModelName from '../model-name'
import ModelSelector from '../model-selector' import ModelSelector from '../model-selector'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '../hooks'
import {
useLanguage,
useTextGenerationCurrentProviderAndModelAndModelList,
} from '../hooks'
import ParameterItem from './parameter-item' import ParameterItem from './parameter-item'
import type { ParameterValue } from './parameter-item' import type { ParameterValue } from './parameter-item'
import { import {
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes' import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import { fetchModelParameterRules } from '@/service/common' import { fetchModelParameterRules } from '@/service/common'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { useProviderContext } from '@/context/provider-context'
import TooltipPlus from '@/app/components/base/tooltip-plus'


type ModelParameterModalProps = { type ModelParameterModalProps = {
isAdvancedMode: boolean isAdvancedMode: boolean
setModel: (model: { modelId: string; provider: string; mode?: string; features: string[] }) => void setModel: (model: { modelId: string; provider: string; mode?: string; features: string[] }) => void
completionParams: FormValue completionParams: FormValue
onCompletionParamsChange: (newParams: FormValue) => void onCompletionParamsChange: (newParams: FormValue) => void
disabled: boolean
} }
const stopParameerRule: ModelParameterRule = { const stopParameerRule: ModelParameterRule = {
default: [], default: [],
}, },
label: { label: {
en_US: 'Stop sequences', en_US: 'Stop sequences',
zh_Hans: '停止序列 stop_sequences',
zh_Hans: '停止序列',
}, },
name: 'stop', name: 'stop',
required: false, required: false,
setModel, setModel,
completionParams, completionParams,
onCompletionParamsChange, onCompletionParamsChange,
disabled,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const language = useLanguage()
const { hasSettedApiKey, modelProviders } = useProviderContext()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { data: parameterRulesData, isLoading } = useSWR(`/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}`, fetchModelParameterRules) const { data: parameterRulesData, isLoading } = useSWR(`/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}`, fetchModelParameterRules)
const { const {
{ provider, model: modelId }, { provider, model: modelId },
) )


const parameterRules = parameterRulesData?.data || []
const hasDeprecated = !currentProvider || !currentModel
const modelDisabled = currentModel?.status !== ModelStatusEnum.active
const disabled = !hasSettedApiKey || hasDeprecated || modelDisabled

const parameterRules = useMemo(() => {
return parameterRulesData?.data || []
}, [parameterRulesData])


const handleParamChange = (key: string, value: ParameterValue) => { const handleParamChange = (key: string, value: ParameterValue) => {
onCompletionParamsChange({ onCompletionParamsChange({
}) })
} }


const handleChangeParams = () => {
const newCompletionParams = parameterRules.reduce((acc, parameter) => {
if (parameter.default !== undefined)
acc[parameter.name] = parameter.default

return acc
}, {} as Record<string, any>)

onCompletionParamsChange(newCompletionParams)
}

useEffect(() => {
handleChangeParams()
}, [parameterRules])

const handleSwitch = (key: string, value: boolean, assignValue: ParameterValue) => { const handleSwitch = (key: string, value: boolean, assignValue: ParameterValue) => {
if (!value) { if (!value) {
const newCompletionParams = { ...completionParams } const newCompletionParams = { ...completionParams }
} }
} }


const handleInitialParams = () => {
if (parameterRules.length) {
const newCompletionParams = { ...completionParams }
Object.keys(newCompletionParams).forEach((key) => {
if (!parameterRules.find(item => item.name === key))
delete newCompletionParams[key]
})

onCompletionParamsChange(newCompletionParams)
}
}

useEffect(() => {
handleInitialParams()
}, [parameterRules])

return ( return (
<PortalToFollowElem <PortalToFollowElem
open={open} open={open}
/> />
) )
} }
{
!currentProvider && (
<ModelIcon
className='mr-1.5 !w-5 !h-5'
provider={modelProviders.find(item => item.provider === provider)}
modelName={modelId}
/>
)
}
{ {
currentModel && ( currentModel && (
<ModelName <ModelName
className='mr-1.5 text-gray-900' className='mr-1.5 text-gray-900'
modelItem={currentModel} modelItem={currentModel}
showMode={isAdvancedMode}
showMode
modeClassName='!text-[#444CE7] !border-[#A4BCFD]' modeClassName='!text-[#444CE7] !border-[#A4BCFD]'
showFeatures={isAdvancedMode}
showFeatures
featuresClassName='!text-[#444CE7] !border-[#A4BCFD]' featuresClassName='!text-[#444CE7] !border-[#A4BCFD]'
/> />
) )
} }
{
!currentModel && (
<div className='mr-1 text-[13px] font-medium text-gray-900 truncate'>
{modelId}
</div>
)
}
{ {
disabled disabled
? ( ? (
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
<TooltipPlus
popupContent={
hasDeprecated
? t('common.modelProvider.deprecated')
: (modelDisabled && currentModel)
? MODEL_STATUS_TEXT[currentModel.status as string][language]
: ''
}
>
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
</TooltipPlus>
) )
: ( : (
<SlidersH className='w-4 h-4 text-indigo-600' /> <SlidersH className='w-4 h-4 text-indigo-600' />
<CubeOutline className='mr-2 w-4 h-4 text-primary-600' /> <CubeOutline className='mr-2 w-4 h-4 text-primary-600' />
{t('common.modelProvider.modelAndParameters')} {t('common.modelProvider.modelAndParameters')}
</div> </div>
<div className='px-10 pt-4 pb-8'>
<div className='max-h-[480px] px-10 pt-4 pb-8 overflow-y-auto'>
<div className='flex items-center justify-between h-8'> <div className='flex items-center justify-between h-8'>
<div className='text-sm font-medium text-gray-900'> <div className='text-sm font-medium text-gray-900'>
{t('common.modelProvider.model')} {t('common.modelProvider.model')}
onSelect={handleChangeModel} onSelect={handleChangeModel}
/> />
</div> </div>
<div className='my-5 h-[1px] bg-gray-100' />
{
!!parameterRules.length && (
<div className='my-5 h-[1px] bg-gray-100' />
)
}
{ {
isLoading && ( isLoading && (
<Loading />
<div className='mt-5'><Loading /></div>
) )
} }
{ {
!isLoading && (
!isLoading && !!parameterRules.length && (
[ [
...parameterRules, ...parameterRules,
...(isAdvancedMode ? [stopParameerRule] : []), ...(isAdvancedMode ? [stopParameerRule] : []),

+ 40
- 24
web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx View File

const language = useLanguage() const language = useLanguage()
const [localValue, setLocalValue] = useState(value) const [localValue, setLocalValue] = useState(value)
const mergedValue = isNullOrUndefined(value) ? localValue : value const mergedValue = isNullOrUndefined(value) ? localValue : value
const renderValue = mergedValue === undefined ? parameterRule.default : mergedValue

const getDefaultValue = () => {
let defaultValue: ParameterValue

if (parameterRule.type === 'int' || parameterRule.type === 'float') {
if (isNullOrUndefined(parameterRule.default)) {
if (parameterRule.min)
defaultValue = parameterRule.min
else
defaultValue = 0
}
else {
defaultValue = parameterRule.default
}
}

if (parameterRule.type === 'string' && !parameterRule.options?.length)
defaultValue = parameterRule.default || ''

if (parameterRule.type === 'string' && parameterRule.options?.length)
defaultValue = parameterRule.default || ''

if (parameterRule.type === 'boolean')
defaultValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : false

if (parameterRule.type === 'tag')
defaultValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : []

return defaultValue
}
const renderValue = isNullOrUndefined(mergedValue) ? getDefaultValue() : mergedValue


const handleChange = (v: ParameterValue) => { const handleChange = (v: ParameterValue) => {
setLocalValue(v) setLocalValue(v)
if (onSwitch) { if (onSwitch) {
let assignValue: ParameterValue = localValue let assignValue: ParameterValue = localValue


if (isNullOrUndefined(localValue)) {
if (parameterRule.type === 'int' || parameterRule.type === 'float')
assignValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : 0

if (parameterRule.type === 'string' && !parameterRule.options?.length)
assignValue = parameterRule.default || ''

if (parameterRule.type === 'string' && parameterRule.options?.length)
assignValue = parameterRule.options[0]

if (parameterRule.type === 'boolean')
assignValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : false

if (parameterRule.type === 'tag')
assignValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : []
}
if (isNullOrUndefined(localValue))
assignValue = getDefaultValue()


onSwitch(checked, assignValue) onSwitch(checked, assignValue)
} }
<div className='flex items-center'> <div className='flex items-center'>
<Slider <Slider
className='w-[120px]' className='w-[120px]'
value={isNullOrUndefined(renderValue) ? 0 : +renderValue!}
value={renderValue as number}
min={parameterRule.min} min={parameterRule.min}
max={parameterRule.max} max={parameterRule.max}
step={+`0.${parameterRule.precision || 0}`} step={+`0.${parameterRule.precision || 0}`}
max={parameterRule.max} max={parameterRule.max}
min={parameterRule.min} min={parameterRule.min}
step={+`0.${parameterRule.precision || 0}`} step={+`0.${parameterRule.precision || 0}`}
value={isNullOrUndefined(renderValue) ? 0 : +renderValue!}
value={renderValue as string}
onChange={handleNumberInputChange} onChange={handleNumberInputChange}
/> />
</div> </div>
parameterRule.type === 'boolean' && ( parameterRule.type === 'boolean' && (
<Radio.Group <Radio.Group
className='w-[200px] flex items-center' className='w-[200px] flex items-center'
value={isNullOrUndefined(renderValue) ? 1 : 0}
value={renderValue ? 1 : 0}
onChange={handleRadioChange} onChange={handleRadioChange}
> >
<Radio value={1} className='!mr-1 w-[94px]'>True</Radio> <Radio value={1} className='!mr-1 w-[94px]'>True</Radio>
<input <input
type='number' type='number'
className='flex items-center px-3 w-[200px] h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900' className='flex items-center px-3 w-[200px] h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900'
value={(isNullOrUndefined(renderValue) ? '' : renderValue) as string}
value={renderValue as string}
onChange={handleNumberInputChange} onChange={handleNumberInputChange}
/> />
) )
parameterRule.type === 'string' && !parameterRule.options?.length && ( parameterRule.type === 'string' && !parameterRule.options?.length && (
<input <input
className='flex items-center px-3 w-[200px] h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900' className='flex items-center px-3 w-[200px] h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900'
value={(isNullOrUndefined(renderValue) ? '' : renderValue) as string}
value={renderValue as string}
onChange={handleStringInputChange} onChange={handleStringInputChange}
/> />
) )
} }
{ {
parameterRule.type === 'string' && parameterRule?.options?.length && (
parameterRule.type === 'string' && !!parameterRule?.options?.length && (
<SimpleSelect <SimpleSelect
className='!py-0' className='!py-0'
wrapperClassName='!w-[200px] !h-8' wrapperClassName='!w-[200px] !h-8'
parameterRule.type === 'tag' && ( parameterRule.type === 'tag' && (
<div className='w-[200px]'> <div className='w-[200px]'>
<TagInput <TagInput
items={isNullOrUndefined(renderValue) ? [] : (renderValue as string[])}
items={renderValue as string[]}
onChange={handleTagChange} onChange={handleTagChange}
customizedConfirmKey='Tab' customizedConfirmKey='Tab'
/> />

+ 46
- 0
web/app/components/header/account-setting/model-provider-page/model-selector/deprecated-model-trigger.tsx View File

import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import ModelIcon from '../model-icon'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import { useProviderContext } from '@/context/provider-context'
import TooltipPlus from '@/app/components/base/tooltip-plus'

type ModelTriggerProps = {
modelName: string
providerName: string
className?: string
}
const ModelTrigger: FC<ModelTriggerProps> = ({
modelName,
providerName,
className,
}) => {
const { t } = useTranslation()
const { modelProviders } = useProviderContext()
const currentProvider = modelProviders.find(provider => provider.provider === providerName)

return (
<div
className={`
group flex items-center px-2 h-8 rounded-lg bg-[#FFFAEB] cursor-pointer
${className}
`}
>
<ModelIcon
className='shrink-0 mr-1.5'
provider={currentProvider}
modelName={modelName}
/>
<div className='mr-1 text-[13px] font-medium text-gray-800 truncate'>
{modelName}
</div>
<div className='shrink-0 flex items-center justify-center w-4 h-4'>
<TooltipPlus popupContent={t('common.modelProvider.deprecated')}>
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
</TooltipPlus>
</div>
</div>
)
}

export default ModelTrigger

+ 36
- 36
web/app/components/header/account-setting/model-provider-page/model-selector/feature-icon.tsx View File

ModelFeatureTextEnum, ModelFeatureTextEnum,
} from '../declarations' } from '../declarations'
import { import {
MagicBox,
// MagicBox,
MagicEyes, MagicEyes,
MagicWand,
Robot,
// MagicWand,
// Robot,
} from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import TooltipPlus from '@/app/components/base/tooltip-plus' import TooltipPlus from '@/app/components/base/tooltip-plus'


}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()


if (feature === ModelFeatureEnum.agentThought) {
return (
<TooltipPlus
popupContent={t('common.modelProvider.featureSupported', { feature: ModelFeatureTextEnum.agentThought })}
>
<ModelBadge className={`mr-0.5 !px-0 w-[18px] justify-center text-gray-500 ${className}`}>
<Robot className='w-3 h-3' />
</ModelBadge>
</TooltipPlus>
)
}
// if (feature === ModelFeatureEnum.agentThought) {
// return (
// <TooltipPlus
// popupContent={t('common.modelProvider.featureSupported', { feature: ModelFeatureTextEnum.agentThought })}
// >
// <ModelBadge className={`mr-0.5 !px-0 w-[18px] justify-center text-gray-500 ${className}`}>
// <Robot className='w-3 h-3' />
// </ModelBadge>
// </TooltipPlus>
// )
// }


if (feature === ModelFeatureEnum.toolCall) {
return (
<TooltipPlus
popupContent={t('common.modelProvider.featureSupported', { feature: ModelFeatureTextEnum.toolCall })}
>
<ModelBadge className={`mr-0.5 !px-0 w-[18px] justify-center text-gray-500 ${className}`}>
<MagicWand className='w-3 h-3' />
</ModelBadge>
</TooltipPlus>
)
}
// if (feature === ModelFeatureEnum.toolCall) {
// return (
// <TooltipPlus
// popupContent={t('common.modelProvider.featureSupported', { feature: ModelFeatureTextEnum.toolCall })}
// >
// <ModelBadge className={`mr-0.5 !px-0 w-[18px] justify-center text-gray-500 ${className}`}>
// <MagicWand className='w-3 h-3' />
// </ModelBadge>
// </TooltipPlus>
// )
// }


if (feature === ModelFeatureEnum.multiToolCall) {
return (
<TooltipPlus
popupContent={t('common.modelProvider.featureSupported', { feature: ModelFeatureTextEnum.multiToolCall })}
>
<ModelBadge className={`mr-0.5 !px-0 w-[18px] justify-center text-gray-500 ${className}`}>
<MagicBox className='w-3 h-3' />
</ModelBadge>
</TooltipPlus>
)
}
// if (feature === ModelFeatureEnum.multiToolCall) {
// return (
// <TooltipPlus
// popupContent={t('common.modelProvider.featureSupported', { feature: ModelFeatureTextEnum.multiToolCall })}
// >
// <ModelBadge className={`mr-0.5 !px-0 w-[18px] justify-center text-gray-500 ${className}`}>
// <MagicBox className='w-3 h-3' />
// </ModelBadge>
// </TooltipPlus>
// )
// }


if (feature === ModelFeatureEnum.vision) { if (feature === ModelFeatureEnum.vision) {
return ( return (

+ 11
- 1
web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx View File

import { useCurrentProviderAndModel } from '../hooks' import { useCurrentProviderAndModel } from '../hooks'
import ModelTrigger from './model-trigger' import ModelTrigger from './model-trigger'
import EmptyTrigger from './empty-trigger' import EmptyTrigger from './empty-trigger'
import DeprecatedModelTrigger from './deprecated-model-trigger'
import Popup from './popup' import Popup from './popup'
import { import {
PortalToFollowElem, PortalToFollowElem,
) )
} }
{ {
!currentModel && (
!currentModel && defaultModel && (
<DeprecatedModelTrigger
modelName={defaultModel?.model || ''}
providerName={defaultModel?.provider || ''}
className={triggerClassName}
/>
)
}
{
!defaultModel && (
<EmptyTrigger <EmptyTrigger
open={open} open={open}
className={triggerClassName} className={triggerClassName}

+ 23
- 4
web/app/components/header/account-setting/model-provider-page/model-selector/model-trigger.tsx View File

Model, Model,
ModelItem, ModelItem,
} from '../declarations' } from '../declarations'
import {
MODEL_STATUS_TEXT,
ModelStatusEnum,
} from '../declarations'
import { useLanguage } from '../hooks'
import ModelIcon from '../model-icon' import ModelIcon from '../model-icon'
import ModelName from '../model-name' import ModelName from '../model-name'
// import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows' import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import TooltipPlus from '@/app/components/base/tooltip-plus'


type ModelTriggerProps = { type ModelTriggerProps = {
open: boolean open: boolean
model, model,
className, className,
}) => { }) => {
const language = useLanguage()

return ( return (
<div <div
className={` className={`
group flex items-center px-2 h-8 rounded-lg bg-gray-100 hover:bg-gray-200 cursor-pointer group flex items-center px-2 h-8 rounded-lg bg-gray-100 hover:bg-gray-200 cursor-pointer
${className} ${className}
${open && '!bg-gray-200'} ${open && '!bg-gray-200'}
${model.status !== ModelStatusEnum.active && '!bg-[#FFFAEB]'}
`} `}
> >
<ModelIcon <ModelIcon
showFeatures showFeatures
/> />
<div className='shrink-0 flex items-center justify-center w-4 h-4'> <div className='shrink-0 flex items-center justify-center w-4 h-4'>
<ChevronDown
className='w-3.5 h-3.5 text-gray-500'
/>
{
model.status !== ModelStatusEnum.active
? (
<TooltipPlus popupContent={MODEL_STATUS_TEXT[model.status][language]}>
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
</TooltipPlus>
)
: (
<ChevronDown
className='w-3.5 h-3.5 text-gray-500'
/>
)
}
</div> </div>
</div> </div>
) )

+ 1
- 1
web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx View File

showFeatures showFeatures
/> />
{ {
defaultModel?.model === modelItem.model && (
defaultModel?.model === modelItem.model && defaultModel.provider === currentProvider.provider && (
<Check className='shrink-0 w-4 h-4 text-primary-600' /> <Check className='shrink-0 w-4 h-4 text-primary-600' />
) )
} }

+ 2
- 1
web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx View File

import { Loading02 } from '@/app/components/base/icons/src/vender/line/general' import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
import { fetchModelProviderModelList } from '@/service/common' import { fetchModelProviderModelList } from '@/service/common'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { IS_CE_EDITION } from '@/config'


export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST' export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
type ProviderAddedCardProps = { type ProviderAddedCardProps = {
const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurateMethodEnum.fetchFromRemote) const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurateMethodEnum.fetchFromRemote)
const systemConfig = provider.system_configuration const systemConfig = provider.system_configuration
const hasModelList = fetched && !!modelList.length const hasModelList = fetched && !!modelList.length
const showQuota = systemConfig.enabled || ['minimax', 'spark', 'zhipuai', 'anthropic'].includes(provider.provider)
const showQuota = systemConfig.enabled && ['minimax', 'spark', 'zhipuai', 'anthropic', 'openai'].includes(provider.provider) && !IS_CE_EDITION


const getModelList = async (providerName: string) => { const getModelList = async (providerName: string) => {
if (loading) if (loading)

+ 1
- 1
web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx View File

className={` className={`
group flex items-center pl-2 pr-2.5 h-8 rounded-lg group flex items-center pl-2 pr-2.5 h-8 rounded-lg
${canCustomConfig && 'hover:bg-gray-50'} ${canCustomConfig && 'hover:bg-gray-50'}
${model.deprecated && 'opacity-60'}
`} `}
> >
<div className='shrink-0 mr-2' style={{ background: provider.icon_small[language] }} />
<ModelIcon <ModelIcon
className='shrink-0 mr-2' className='shrink-0 mr-2'
provider={provider} provider={provider}

+ 9
- 1
web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx View File

import PriorityUseTip from './priority-use-tip' import PriorityUseTip from './priority-use-tip'
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general' import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import TooltipPlus from '@/app/components/base/tooltip-plus'


type QuotaPanelProps = { type QuotaPanelProps = {
provider: ModelProvider provider: ModelProvider
const priorityUseType = provider.preferred_provider_type const priorityUseType = provider.preferred_provider_type
const systemConfig = provider.system_configuration const systemConfig = provider.system_configuration
const currentQuota = systemConfig.enabled && systemConfig.quota_configurations.find(item => item.quota_type === systemConfig.current_quota_type) const currentQuota = systemConfig.enabled && systemConfig.quota_configurations.find(item => item.quota_type === systemConfig.current_quota_type)
const openaiOrAnthropic = ['openai', 'anthropic'].includes(provider.provider)


return ( return (
<div className='group relative shrink-0 min-w-[112px] px-3 py-2 rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'> <div className='group relative shrink-0 min-w-[112px] px-3 py-2 rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'>
<div className='flex items-center mb-2 h-4 text-xs font-medium text-gray-500'> <div className='flex items-center mb-2 h-4 text-xs font-medium text-gray-500'>
{t('common.modelProvider.quota')} {t('common.modelProvider.quota')}
<InfoCircle className='ml-0.5 w-3 h-3 text-gray-400' />
<TooltipPlus popupContent={
openaiOrAnthropic
? t('common.modelProvider.card.tip')
: t('common.modelProvider.quotaTip')
}>
<InfoCircle className='ml-0.5 w-3 h-3 text-gray-400' />
</TooltipPlus>
</div> </div>
{ {
currentQuota && ( currentQuota && (

+ 3
- 2
web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx View File

import { Plus, Settings01 } from '@/app/components/base/icons/src/vender/line/general' import { Plus, Settings01 } from '@/app/components/base/icons/src/vender/line/general'
import { CoinsStacked01 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' import { CoinsStacked01 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { IS_CE_EDITION } from '@/config'


type ProviderCardProps = { type ProviderCardProps = {
provider: ModelProvider provider: ModelProvider
} }
const handleFreeQuota = useFreeQuota(handleFreeQuotaSuccess) const handleFreeQuota = useFreeQuota(handleFreeQuotaSuccess)
const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurateMethodEnum.fetchFromRemote) const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurateMethodEnum.fetchFromRemote)
const canGetFreeQuota = ['mininmax', 'spark', 'zhipuai'].includes(provider.provider)
const canGetFreeQuota = ['mininmax', 'spark', 'zhipuai'].includes(provider.provider) && !IS_CE_EDITION


return ( return (
<div <div
}) })
} }
{ {
provider.provider === 'anthropic' && (
provider.provider === 'anthropic' && !IS_CE_EDITION && (
<Button <Button
className='h-7 text-xs text-gray-700' className='h-7 text-xs text-gray-700'
onClick={handlePay} onClick={handlePay}

+ 26
- 24
web/app/components/share/chat/index.tsx View File

import type { VisionFile, VisionSettings } from '@/types/app' import type { VisionFile, VisionSettings } from '@/types/app'
import { Resolution, TransferMethod } from '@/types/app' import { Resolution, TransferMethod } from '@/types/app'
import { fetchFileUploadConfig } from '@/service/common' import { fetchFileUploadConfig } from '@/service/common'
import type { Annotation as AnnotationType } from '@/models/log'


export type IMainProps = { export type IMainProps = {
isInstalledApp?: boolean isInstalledApp?: boolean
} }
setResponsingFalse() setResponsingFalse()
}, },
onMessageEnd: isInstalledApp
? (messageEnd) => {
if (!isInstalledApp)
return
responseItem.citation = messageEnd.retriever_resources

onMessageEnd: (messageEnd) => {
if (messageEnd.metadata?.annotation_reply) {
responseItem.id = messageEnd.id
responseItem.annotation = ({
id: messageEnd.metadata.annotation_reply.id,
authorName: messageEnd.metadata.annotation_reply.account.name,
} as AnnotationType)
const newListWithAnswer = produce( const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => { (draft) => {
if (!draft.find(item => item.id === questionId)) if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem }) draft.push({ ...questionItem })


draft.push({ ...responseItem })
draft.push({
...responseItem,
})
}) })
setChatList(newListWithAnswer) setChatList(newListWithAnswer)
return
} }
: undefined,
// not support show citation
// responseItem.citation = messageEnd.retriever_resources
if (!isInstalledApp)
return
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })

draft.push({ ...responseItem })
})
setChatList(newListWithAnswer)
},
onMessageReplace: (messageReplace) => { onMessageReplace: (messageReplace) => {
if (isInstalledApp) { if (isInstalledApp) {
responseItem.content = messageReplace.answer responseItem.content = messageReplace.answer
)) ))
} }
}, },
onAnnotationReply: (annotationReply) => {
responseItem.content = annotationReply.answer
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })

draft.push({
...responseItem,
id: annotationReply.id,
})
})
setChatList(newListWithAnswer)
tempNewConversationId = annotationReply.conversation_id
},
onError() { onError() {
setResponsingFalse() setResponsingFalse()
// role back placeholder answer // role back placeholder answer

+ 3
- 0
web/i18n/lang/common.en.ts View File

buyQuota: 'Buy Quota', buyQuota: 'Buy Quota',
getFreeTokens: 'Get free Tokens', getFreeTokens: 'Get free Tokens',
priorityUsing: 'Prioritize using', priorityUsing: 'Prioritize using',
deprecated: 'Deprecated',
confirmDelete: 'confirm deletion?',
quotaTip: 'Remaining available free tokens',
}, },
dataSource: { dataSource: {
add: 'Add a data source', add: 'Add a data source',

+ 3
- 0
web/i18n/lang/common.zh.ts View File

buyQuota: '购买额度', buyQuota: '购买额度',
getFreeTokens: '获得免费 Tokens', getFreeTokens: '获得免费 Tokens',
priorityUsing: '优先使用', priorityUsing: '优先使用',
deprecated: '已弃用',
confirmDelete: '确认删除?',
quotaTip: '剩余免费额度',
}, },
dataSource: { dataSource: {
add: '添加数据源', add: '添加数据源',

+ 4
- 8
web/service/base.ts View File

onThought?: IOnThought onThought?: IOnThought
onMessageEnd?: IOnMessageEnd onMessageEnd?: IOnMessageEnd
onMessageReplace?: IOnMessageReplace onMessageReplace?: IOnMessageReplace
onAnnotationReply?: IOnAnnotationReply
onError?: IOnError onError?: IOnError
onCompleted?: IOnCompleted // for stream onCompleted?: IOnCompleted // for stream
getAbortController?: (abortController: AbortController) => void getAbortController?: (abortController: AbortController) => void
return res.replaceAll('\n', '<br/>').replaceAll('```', '') return res.replaceAll('\n', '<br/>').replaceAll('```', '')
} }


const handleStream = (response: Response, onData: IOnData, onCompleted?: IOnCompleted, onThought?: IOnThought, onMessageEnd?: IOnMessageEnd, onMessageReplace?: IOnMessageReplace, onAnnotationReply?: IOnAnnotationReply) => {
const handleStream = (response: Response, onData: IOnData, onCompleted?: IOnCompleted, onThought?: IOnThought, onMessageEnd?: IOnMessageEnd, onMessageReplace?: IOnMessageReplace) => {
if (!response.ok) if (!response.ok)
throw new Error('Network response was not ok') throw new Error('Network response was not ok')


onThought?.(bufferObj as ThoughtItem) onThought?.(bufferObj as ThoughtItem)
} }
else if (bufferObj.event === 'message_end') { else if (bufferObj.event === 'message_end') {
console.log(bufferObj)
onMessageEnd?.(bufferObj as MessageEnd) onMessageEnd?.(bufferObj as MessageEnd)
} }
else if (bufferObj.event === 'message_replace') { else if (bufferObj.event === 'message_replace') {
onMessageReplace?.(bufferObj as MessageReplace) onMessageReplace?.(bufferObj as MessageReplace)
} }
else if (bufferObj.event === 'annotation') {
onAnnotationReply?.(bufferObj as AnnotationReply)
}
} }
}) })
buffer = lines[lines.length - 1] buffer = lines[lines.length - 1]
}) })
} }


export const ssePost = (url: string, fetchOptions: FetchOptionType, { isPublicAPI = false, onData, onCompleted, onThought, onMessageEnd, onMessageReplace, onAnnotationReply, onError, getAbortController }: IOtherOptions) => {
export const ssePost = (url: string, fetchOptions: FetchOptionType, { isPublicAPI = false, onData, onCompleted, onThought, onMessageEnd, onMessageReplace, onError, getAbortController }: IOtherOptions) => {
const abortController = new AbortController() const abortController = new AbortController()


const options = Object.assign({}, baseOptions, { const options = Object.assign({}, baseOptions, {
} }
return handleStream(res, (str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => { return handleStream(res, (str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => {
if (moreInfo.errorMessage) { if (moreInfo.errorMessage) {
// debugger
onError?.(moreInfo.errorMessage, moreInfo.errorCode) onError?.(moreInfo.errorMessage, moreInfo.errorCode)
if (moreInfo.errorMessage !== 'AbortError: The user aborted a request.') if (moreInfo.errorMessage !== 'AbortError: The user aborted a request.')
Toast.notify({ type: 'error', message: moreInfo.errorMessage }) Toast.notify({ type: 'error', message: moreInfo.errorMessage })
return return
} }
onData?.(str, isFirstMessage, moreInfo) onData?.(str, isFirstMessage, moreInfo)
}, onCompleted, onThought, onMessageEnd, onMessageReplace, onAnnotationReply)
}, onCompleted, onThought, onMessageEnd, onMessageReplace)
}).catch((e) => { }).catch((e) => {
if (e.toString() !== 'AbortError: The user aborted a request.') if (e.toString() !== 'AbortError: The user aborted a request.')
Toast.notify({ type: 'error', message: e }) Toast.notify({ type: 'error', message: e })

+ 3
- 4
web/service/debug.ts View File

import type { IOnAnnotationReply, IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
import type { IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
import { get, post, ssePost } from './base' import { get, post, ssePost } from './base'
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug' import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
import type { ModelModeType } from '@/types/app' import type { ModelModeType } from '@/types/app'
opening_statement: string opening_statement: string
} }


export const sendChatMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply }: {
export const sendChatMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace }: {
onData: IOnData onData: IOnData
onCompleted: IOnCompleted onCompleted: IOnCompleted
onMessageEnd: IOnMessageEnd onMessageEnd: IOnMessageEnd
onMessageReplace: IOnMessageReplace onMessageReplace: IOnMessageReplace
onAnnotationReply: IOnAnnotationReply
onError: IOnError onError: IOnError
getAbortController?: (abortController: AbortController) => void getAbortController?: (abortController: AbortController) => void
}) => { }) => {
...body, ...body,
response_mode: 'streaming', response_mode: 'streaming',
}, },
}, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply })
}, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace })
} }


export const stopChatMessageResponding = async (appId: string, taskId: string) => { export const stopChatMessageResponding = async (appId: string, taskId: string) => {

+ 3
- 4
web/service/share.ts View File

import type { IOnAnnotationReply, IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
import type { IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnMessageReplace } from './base'
import { import {
del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost, del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost,
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost, delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
return isInstalledApp ? `installed-apps/${installedAppId}/${url.startsWith('/') ? url.slice(1) : url}` : url return isInstalledApp ? `installed-apps/${installedAppId}/${url.startsWith('/') ? url.slice(1) : url}` : url
} }


export const sendChatMessage = async (body: Record<string, any>, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply }: {
export const sendChatMessage = async (body: Record<string, any>, { onData, onCompleted, onError, getAbortController, onMessageEnd, onMessageReplace }: {
onData: IOnData onData: IOnData
onCompleted: IOnCompleted onCompleted: IOnCompleted
onError: IOnError onError: IOnError
onMessageEnd?: IOnMessageEnd onMessageEnd?: IOnMessageEnd
onMessageReplace?: IOnMessageReplace onMessageReplace?: IOnMessageReplace
onAnnotationReply: IOnAnnotationReply
getAbortController?: (abortController: AbortController) => void getAbortController?: (abortController: AbortController) => void
}, isInstalledApp: boolean, installedAppId = '') => { }, isInstalledApp: boolean, installedAppId = '') => {
return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), { return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), {
...body, ...body,
response_mode: 'streaming', response_mode: 'streaming',
}, },
}, { onData, onCompleted, isPublicAPI: !isInstalledApp, onError, getAbortController, onMessageEnd, onMessageReplace, onAnnotationReply })
}, { onData, onCompleted, isPublicAPI: !isInstalledApp, onError, getAbortController, onMessageEnd, onMessageReplace })
} }


export const stopChatMessageResponding = async (appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => { export const stopChatMessageResponding = async (appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => {

+ 1126
- 749
web/yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save