mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 00:09:02 +08:00
chore: remove universal chat code (#2194)
This commit is contained in:
parent
77636945fb
commit
bec998ab94
@ -1,13 +0,0 @@
|
|||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import UniversalChat from '@/app/components/explore/universal-chat'
|
|
||||||
|
|
||||||
const Chat: FC = () => {
|
|
||||||
return (
|
|
||||||
<div className='h-full p-2'>
|
|
||||||
<UniversalChat />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(Chat)
|
|
@ -16,7 +16,6 @@ export type ICardItemProps = {
|
|||||||
onRemove: (id: string) => void
|
onRemove: (id: string) => void
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
}
|
}
|
||||||
// used in universal-chat
|
|
||||||
const CardItem: FC<ICardItemProps> = ({
|
const CardItem: FC<ICardItemProps> = ({
|
||||||
className,
|
className,
|
||||||
config,
|
config,
|
||||||
|
@ -33,7 +33,7 @@ const AudioBtn = ({
|
|||||||
if (value !== '') {
|
if (value !== '') {
|
||||||
formData.append('text', removeCodeBlocks(value))
|
formData.append('text', removeCodeBlocks(value))
|
||||||
|
|
||||||
let url = '/universal-chat/text-to-audio'
|
let url = ''
|
||||||
let isPublic = false
|
let isPublic = false
|
||||||
|
|
||||||
if (params.token) {
|
if (params.token) {
|
||||||
|
@ -86,7 +86,7 @@ const VoiceInput = ({
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', mp3File)
|
formData.append('file', mp3File)
|
||||||
|
|
||||||
let url = '/universal-chat/audio-to-text'
|
let url = ''
|
||||||
let isPublic = false
|
let isPublic = false
|
||||||
|
|
||||||
if (params.token) {
|
if (params.token) {
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import cn from 'classnames'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import s from './style.module.css'
|
|
||||||
import Config from '@/app/components/explore/universal-chat/config'
|
|
||||||
import type { DataSet } from '@/models/datasets'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
modelId: string
|
|
||||||
providerName: string
|
|
||||||
plugins: Record<string, boolean>
|
|
||||||
dataSets: DataSet[]
|
|
||||||
}
|
|
||||||
const ConfigViewPanel: FC<Props> = ({
|
|
||||||
modelId,
|
|
||||||
providerName,
|
|
||||||
plugins,
|
|
||||||
dataSets,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
return (
|
|
||||||
<div className={cn('absolute top-9 right-0 z-20 p-4 bg-white rounded-2xl shadow-md', s.panelBorder)}>
|
|
||||||
<div className='w-[368px]'>
|
|
||||||
<Config
|
|
||||||
readonly
|
|
||||||
modelId={modelId}
|
|
||||||
providerName={providerName}
|
|
||||||
plugins={plugins}
|
|
||||||
dataSets={dataSets}
|
|
||||||
/>
|
|
||||||
<div className='mt-3 text-xs leading-[18px] text-500 font-normal'>{t('explore.universalChat.viewConfigDetailTip')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(ConfigViewPanel)
|
|
@ -1,9 +0,0 @@
|
|||||||
.btn {
|
|
||||||
background: url(~@/assets/action.svg) center center no-repeat transparent;
|
|
||||||
background-size: 16px 16px;
|
|
||||||
/* mask-image: ; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.panelBorder {
|
|
||||||
border: 0.5px solid rgba(0, 0, 0, .05);
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import cn from 'classnames'
|
|
||||||
import { useBoolean, useClickAway } from 'ahooks'
|
|
||||||
import s from './style.module.css'
|
|
||||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
|
||||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
|
||||||
import { Google, WebReader, Wikipedia } from '@/app/components/base/icons/src/public/plugins'
|
|
||||||
import ConfigDetail from '@/app/components/explore/universal-chat/config-view/detail'
|
|
||||||
import type { DataSet } from '@/models/datasets'
|
|
||||||
import { useAgentThoughtCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
|
||||||
|
|
||||||
export type ISummaryProps = {
|
|
||||||
modelId: string
|
|
||||||
providerName: string
|
|
||||||
plugins: Record<string, boolean>
|
|
||||||
dataSets: DataSet[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getColorInfo = (modelId: string) => {
|
|
||||||
if (modelId === 'gpt-4')
|
|
||||||
return s.gpt4
|
|
||||||
|
|
||||||
if (modelId === 'claude-2')
|
|
||||||
return s.claude
|
|
||||||
|
|
||||||
return s.gpt3
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPlugIcon = (pluginId: string) => {
|
|
||||||
const className = 'w-4 h-4'
|
|
||||||
switch (pluginId) {
|
|
||||||
case 'google_search':
|
|
||||||
return <Google className={className} />
|
|
||||||
case 'web_reader':
|
|
||||||
return <WebReader className={className} />
|
|
||||||
case 'wikipedia':
|
|
||||||
return <Wikipedia className={className} />
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Summary: FC<ISummaryProps> = ({
|
|
||||||
modelId,
|
|
||||||
providerName,
|
|
||||||
plugins,
|
|
||||||
dataSets,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
currentModel: currModel,
|
|
||||||
currentProvider,
|
|
||||||
} = useAgentThoughtCurrentProviderAndModelAndModelList(
|
|
||||||
{ provider: providerName, model: modelId },
|
|
||||||
)
|
|
||||||
// current_datetime is not configable and do not have icon
|
|
||||||
const pluginIds = Object.keys(plugins).filter(key => plugins[key] && key !== 'current_datetime')
|
|
||||||
const [isShowConfig, { setFalse: hideConfig, toggle: toggleShowConfig }] = useBoolean(false)
|
|
||||||
const configContentRef = React.useRef(null)
|
|
||||||
|
|
||||||
useClickAway(() => {
|
|
||||||
hideConfig()
|
|
||||||
}, configContentRef)
|
|
||||||
return (
|
|
||||||
<div ref={configContentRef} className='relative'>
|
|
||||||
<div onClick={toggleShowConfig} className={cn(getColorInfo(modelId), 'flex items-center px-1 h-8 rounded-lg border cursor-pointer')}>
|
|
||||||
<ModelIcon
|
|
||||||
provider={currentProvider}
|
|
||||||
modelName={currModel?.model}
|
|
||||||
className='!w-6 !h-6'
|
|
||||||
/>
|
|
||||||
<div className='ml-2 text-[13px] font-medium text-gray-900'>
|
|
||||||
<ModelName
|
|
||||||
modelItem={currModel!}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
pluginIds.length > 0 && (
|
|
||||||
<div className='ml-1.5 flex items-center'>
|
|
||||||
<div className='mr-1 h-3 w-[1px] bg-[#000] opacity-[0.05]'></div>
|
|
||||||
<div className='flex space-x-1'>
|
|
||||||
{pluginIds.map(pluginId => (
|
|
||||||
<div
|
|
||||||
key={pluginId}
|
|
||||||
className={`flex items-center justify-center w-6 h-6 rounded-md ${s.border} bg-white`}
|
|
||||||
>
|
|
||||||
{getPlugIcon(pluginId)}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
{isShowConfig && (
|
|
||||||
<ConfigDetail
|
|
||||||
modelId={modelId}
|
|
||||||
providerName={providerName}
|
|
||||||
plugins={plugins}
|
|
||||||
dataSets={dataSets}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Summary)
|
|
@ -1,21 +0,0 @@
|
|||||||
.border {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gpt3 {
|
|
||||||
background: linear-gradient(0deg, #D3F8DF, #D3F8DF),
|
|
||||||
linear-gradient(0deg, #EDFCF2, #EDFCF2);
|
|
||||||
border: 1px solid rgba(211, 248, 223, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
.gpt4 {
|
|
||||||
background: linear-gradient(0deg, #EBE9FE, #EBE9FE),
|
|
||||||
linear-gradient(0deg, #F4F3FF, #F4F3FF);
|
|
||||||
border: 1px solid rgba(235, 233, 254, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
.claude {
|
|
||||||
background: linear-gradient(0deg, #F9EBDF, #F9EBDF),
|
|
||||||
linear-gradient(0deg, #FCF3EB, #FCF3EB);
|
|
||||||
border: 1px solid rgba(249, 235, 223, 1)
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { useBoolean } from 'ahooks'
|
|
||||||
import { isEqual } from 'lodash-es'
|
|
||||||
import produce from 'immer'
|
|
||||||
import FeaturePanel from '@/app/components/app/configuration/base/feature-panel'
|
|
||||||
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
|
|
||||||
import CardItem from '@/app/components/app/configuration/dataset-config/card-item'
|
|
||||||
import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
|
|
||||||
import type { DataSet } from '@/models/datasets'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
readonly?: boolean
|
|
||||||
dataSets: DataSet[]
|
|
||||||
onChange?: (data: DataSet[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const DatasetConfig: FC<Props> = ({
|
|
||||||
readonly,
|
|
||||||
dataSets,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const selectedIds = dataSets.map(item => item.id)
|
|
||||||
|
|
||||||
const hasData = dataSets.length > 0
|
|
||||||
const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false)
|
|
||||||
const handleSelect = (data: DataSet[]) => {
|
|
||||||
if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) {
|
|
||||||
hideSelectDataSet()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.find(item => !item.name)) { // has not loaded selected dataset
|
|
||||||
const newSelected = produce(data, (draft) => {
|
|
||||||
data.forEach((item, index) => {
|
|
||||||
if (!item.name) { // not fetched database
|
|
||||||
const newItem = dataSets.find(i => i.id === item.id)
|
|
||||||
if (newItem)
|
|
||||||
draft[index] = newItem
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
onChange?.(newSelected)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
onChange?.(data)
|
|
||||||
}
|
|
||||||
hideSelectDataSet()
|
|
||||||
}
|
|
||||||
const onRemove = (id: string) => {
|
|
||||||
onChange?.(dataSets.filter(item => item.id !== id))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FeaturePanel
|
|
||||||
className='mt-3'
|
|
||||||
title={t('appDebug.feature.dataSet.title')}
|
|
||||||
headerRight={!readonly && <OperationBtn type="add" onClick={showSelectDataSet} />}
|
|
||||||
hasHeaderBottomBorder={!hasData}
|
|
||||||
>
|
|
||||||
{hasData
|
|
||||||
? (
|
|
||||||
<div className='max-h-[220px] overflow-y-auto'>
|
|
||||||
{dataSets.map(item => (
|
|
||||||
<CardItem
|
|
||||||
className="mb-2 !w-full"
|
|
||||||
key={item.id}
|
|
||||||
config={item}
|
|
||||||
onRemove={onRemove}
|
|
||||||
readonly={readonly}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.feature.dataSet.noData')}</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isShowSelectDataSet && (
|
|
||||||
<SelectDataSet
|
|
||||||
isShow={isShowSelectDataSet}
|
|
||||||
onClose={hideSelectDataSet}
|
|
||||||
selectedIds={selectedIds}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</FeaturePanel>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(DatasetConfig)
|
|
@ -1,55 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import ModelConfig from './model-config'
|
|
||||||
import DataConfig from './data-config'
|
|
||||||
import PluginConfig from './plugins-config'
|
|
||||||
import type { DataSet } from '@/models/datasets'
|
|
||||||
|
|
||||||
export type IConfigProps = {
|
|
||||||
className?: string
|
|
||||||
readonly?: boolean
|
|
||||||
modelId: string
|
|
||||||
providerName: string
|
|
||||||
onModelChange?: (modelId: string, providerName: string) => void
|
|
||||||
plugins: Record<string, boolean>
|
|
||||||
onPluginChange?: (key: string, value: boolean) => void
|
|
||||||
dataSets: DataSet[]
|
|
||||||
onDataSetsChange?: (contexts: DataSet[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const Config: FC<IConfigProps> = ({
|
|
||||||
className,
|
|
||||||
readonly,
|
|
||||||
modelId,
|
|
||||||
providerName,
|
|
||||||
onModelChange,
|
|
||||||
plugins,
|
|
||||||
onPluginChange,
|
|
||||||
dataSets,
|
|
||||||
onDataSetsChange,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<ModelConfig
|
|
||||||
readonly={readonly}
|
|
||||||
modelId={modelId}
|
|
||||||
providerName={providerName}
|
|
||||||
onChange={onModelChange}
|
|
||||||
/>
|
|
||||||
<PluginConfig
|
|
||||||
readonly={readonly}
|
|
||||||
config={plugins}
|
|
||||||
onChange={onPluginChange}
|
|
||||||
/>
|
|
||||||
{(!readonly || (readonly && dataSets.length > 0)) && (
|
|
||||||
<DataConfig
|
|
||||||
readonly={readonly}
|
|
||||||
dataSets={dataSets}
|
|
||||||
onChange={onDataSetsChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Config)
|
|
@ -1,39 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
|
||||||
|
|
||||||
export type IModelConfigProps = {
|
|
||||||
modelId: string
|
|
||||||
providerName: string
|
|
||||||
onChange?: (modelId: string, providerName: string) => void
|
|
||||||
readonly?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModelConfig: FC<IModelConfigProps> = ({
|
|
||||||
modelId,
|
|
||||||
providerName,
|
|
||||||
onChange,
|
|
||||||
readonly,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { agentThoughtModelList } = useProviderContext()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex items-center justify-between h-[52px] px-3 rounded-xl bg-gray-50'>
|
|
||||||
<div className='text-sm font-semibold text-gray-800'>{t('explore.universalChat.model')}</div>
|
|
||||||
<ModelSelector
|
|
||||||
triggerClassName={`${readonly && '!cursor-not-allowed !opacity-60'}`}
|
|
||||||
defaultModel={{ provider: providerName, model: modelId }}
|
|
||||||
modelList={agentThoughtModelList}
|
|
||||||
onSelect={(model) => {
|
|
||||||
onChange?.(model.model, model.provider)
|
|
||||||
}}
|
|
||||||
readonly={readonly}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(ModelConfig)
|
|
@ -1,103 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React, { useEffect } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import Item from './item'
|
|
||||||
import FeaturePanel from '@/app/components/app/configuration/base/feature-panel'
|
|
||||||
import { Google, WebReader, Wikipedia } from '@/app/components/base/icons/src/public/plugins'
|
|
||||||
import { getToolProviders } from '@/service/explore'
|
|
||||||
import Loading from '@/app/components/base/loading'
|
|
||||||
import { useModalContext } from '@/context/modal-context'
|
|
||||||
|
|
||||||
export type IPluginsProps = {
|
|
||||||
readonly?: boolean
|
|
||||||
config: Record<string, boolean>
|
|
||||||
onChange?: (key: string, value: boolean) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const plugins = [
|
|
||||||
{ key: 'google_search', icon: <Google /> },
|
|
||||||
{ key: 'web_reader', icon: <WebReader /> },
|
|
||||||
{ key: 'wikipedia', icon: <Wikipedia /> },
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const Plugins: FC<IPluginsProps> = ({
|
|
||||||
readonly,
|
|
||||||
config,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { setShowAccountSettingModal } = useModalContext()
|
|
||||||
const [isLoading, setIsLoading] = React.useState(!readonly)
|
|
||||||
const [isSerpApiValid, setIsSerpApiValid] = React.useState(false)
|
|
||||||
const checkSerpApiKey = async () => {
|
|
||||||
if (readonly)
|
|
||||||
return
|
|
||||||
|
|
||||||
const provides: any = await getToolProviders()
|
|
||||||
const isSerpApiValid = !!provides.find((v: any) => v.tool_name === 'serpapi' && v.is_enabled)
|
|
||||||
setIsSerpApiValid(isSerpApiValid)
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
checkSerpApiKey()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const itemConfigs = plugins.map((plugin) => {
|
|
||||||
const res: Record<string, any> = { ...plugin }
|
|
||||||
const { key } = plugin
|
|
||||||
res.name = t(`explore.universalChat.plugins.${key}.name`)
|
|
||||||
if (key === 'web_reader')
|
|
||||||
res.description = t(`explore.universalChat.plugins.${key}.description`)
|
|
||||||
|
|
||||||
if (key === 'google_search' && !isSerpApiValid && !readonly) {
|
|
||||||
res.readonly = true
|
|
||||||
res.more = (
|
|
||||||
<div className='border-t border-[#FEF0C7] flex items-center h-[34px] pl-2 bg-[#FFFAEB] text-gray-700 text-xs '>
|
|
||||||
<span className='whitespace-pre'>{t('explore.universalChat.plugins.google_search.more.left')}</span>
|
|
||||||
<span className='cursor-pointer text-[#155EEF]' onClick={() => setShowAccountSettingModal({ payload: 'plugin', onCancelCallback: async () => await checkSerpApiKey() })}>{t('explore.universalChat.plugins.google_search.more.link')}</span>
|
|
||||||
<span className='whitespace-pre'>{t('explore.universalChat.plugins.google_search.more.right')}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
|
|
||||||
const enabledPluginNum = Object.values(config).filter(v => v).length
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FeaturePanel
|
|
||||||
className='mt-3'
|
|
||||||
title={
|
|
||||||
<div className='flex space-x-1'>
|
|
||||||
<div>{t('explore.universalChat.plugins.name')}</div>
|
|
||||||
<div className='text-[13px] font-normal text-gray-500'>({enabledPluginNum}/{plugins.length})</div>
|
|
||||||
</div>}
|
|
||||||
hasHeaderBottomBorder={false}
|
|
||||||
>
|
|
||||||
{isLoading
|
|
||||||
? (
|
|
||||||
<div className='flex items-center h-[166px]'>
|
|
||||||
<Loading type='area' />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: (<div className='space-y-2'>
|
|
||||||
{itemConfigs.map(item => (
|
|
||||||
<Item
|
|
||||||
key={item.key}
|
|
||||||
icon={item.icon}
|
|
||||||
name={item.name}
|
|
||||||
description={item.description}
|
|
||||||
more={item.more}
|
|
||||||
enabled={config[item.key]}
|
|
||||||
onChange={enabled => onChange?.(item.key, enabled)}
|
|
||||||
readonly={readonly || item.readonly}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>)}
|
|
||||||
</FeaturePanel>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Plugins)
|
|
@ -1,3 +0,0 @@
|
|||||||
.shadow {
|
|
||||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import cn from 'classnames'
|
|
||||||
import s from './item.module.css'
|
|
||||||
import Switch from '@/app/components/base/switch'
|
|
||||||
|
|
||||||
export type IItemProps = {
|
|
||||||
icon: React.ReactNode
|
|
||||||
name: string
|
|
||||||
description?: string
|
|
||||||
more?: React.ReactNode
|
|
||||||
enabled: boolean
|
|
||||||
onChange: (enabled: boolean) => void
|
|
||||||
readonly?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const Item: FC<IItemProps> = ({
|
|
||||||
icon,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
more,
|
|
||||||
enabled,
|
|
||||||
onChange,
|
|
||||||
readonly,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className={cn('bg-white rounded-xl border border-gray-200 overflow-hidden', s.shadow)}>
|
|
||||||
<div className='flex justify-between items-center min-h-[48px] px-2'>
|
|
||||||
<div className='flex items-center space-x-2'>
|
|
||||||
{icon}
|
|
||||||
<div className='leading-[18px]'>
|
|
||||||
<div className='text-[13px] font-medium text-gray-800'>{name}</div>
|
|
||||||
{description && <div className='text-xs leading-[18px] text-gray-500'>{description}</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Switch size='md' defaultValue={enabled} onChange={onChange} disabled={readonly} />
|
|
||||||
</div>
|
|
||||||
{more}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Item)
|
|
@ -1,73 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import produce from 'immer'
|
|
||||||
import { useGetState } from 'ahooks'
|
|
||||||
import type { ConversationItem } from '@/models/share'
|
|
||||||
|
|
||||||
const storageConversationIdKey = 'conversationIdInfo'
|
|
||||||
|
|
||||||
type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>
|
|
||||||
function useConversation() {
|
|
||||||
const [conversationList, setConversationList] = useState<ConversationItem[]>([])
|
|
||||||
const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([])
|
|
||||||
const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>('-1')
|
|
||||||
// when set conversation id, we do not have set appId
|
|
||||||
const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
|
|
||||||
doSetCurrConversationId(id)
|
|
||||||
if (isSetToLocalStroge && id !== '-1') {
|
|
||||||
// conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2}
|
|
||||||
const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
|
|
||||||
conversationIdInfo[appId] = id
|
|
||||||
globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getConversationIdFromStorage = (appId: string) => {
|
|
||||||
const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
|
|
||||||
const id = conversationIdInfo[appId]
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNewConversation = currConversationId === '-1'
|
|
||||||
// input can be updated by user
|
|
||||||
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null)
|
|
||||||
const resetNewConversationInputs = () => {
|
|
||||||
if (!newConversationInputs)
|
|
||||||
return
|
|
||||||
setNewConversationInputs(produce(newConversationInputs, (draft) => {
|
|
||||||
Object.keys(draft).forEach((key) => {
|
|
||||||
draft[key] = ''
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
const [existConversationInputs, setExistConversationInputs] = useState<Record<string, any> | null>(null)
|
|
||||||
const currInputs = isNewConversation ? newConversationInputs : existConversationInputs
|
|
||||||
const setCurrInputs = isNewConversation ? setNewConversationInputs : setExistConversationInputs
|
|
||||||
|
|
||||||
// info is muted
|
|
||||||
const [newConversationInfo, setNewConversationInfo] = useState<ConversationInfoType | null>(null)
|
|
||||||
const [existConversationInfo, setExistConversationInfo] = useState<ConversationInfoType | null>(null)
|
|
||||||
const currConversationInfo = isNewConversation ? newConversationInfo : existConversationInfo
|
|
||||||
|
|
||||||
return {
|
|
||||||
conversationList,
|
|
||||||
setConversationList,
|
|
||||||
pinnedConversationList,
|
|
||||||
setPinnedConversationList,
|
|
||||||
currConversationId,
|
|
||||||
getCurrConversationId,
|
|
||||||
setCurrConversationId,
|
|
||||||
getConversationIdFromStorage,
|
|
||||||
isNewConversation,
|
|
||||||
currInputs,
|
|
||||||
newConversationInputs,
|
|
||||||
existConversationInputs,
|
|
||||||
resetNewConversationInputs,
|
|
||||||
setCurrInputs,
|
|
||||||
currConversationInfo,
|
|
||||||
setNewConversationInfo,
|
|
||||||
existConversationInfo,
|
|
||||||
setExistConversationInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useConversation
|
|
@ -1,831 +0,0 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
|
||||||
import cn from 'classnames'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { useContext } from 'use-context-selector'
|
|
||||||
import produce from 'immer'
|
|
||||||
import { useBoolean, useGetState } from 'ahooks'
|
|
||||||
import AppUnavailable from '../../base/app-unavailable'
|
|
||||||
import useConversation from './hooks/use-conversation'
|
|
||||||
import Init from './init'
|
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
|
||||||
import Sidebar from '@/app/components/share/chat/sidebar'
|
|
||||||
import {
|
|
||||||
delConversation,
|
|
||||||
fetchAppParams,
|
|
||||||
fetchChatList,
|
|
||||||
fetchConversations,
|
|
||||||
fetchSuggestedQuestions,
|
|
||||||
generationConversationName,
|
|
||||||
pinConversation,
|
|
||||||
sendChatMessage,
|
|
||||||
stopChatMessageResponding,
|
|
||||||
unpinConversation,
|
|
||||||
updateFeedback,
|
|
||||||
} from '@/service/universal-chat'
|
|
||||||
import type { ConversationItem, SiteInfo } from '@/models/share'
|
|
||||||
import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
|
|
||||||
import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type'
|
|
||||||
import Chat from '@/app/components/app/chat'
|
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
|
||||||
import Loading from '@/app/components/base/loading'
|
|
||||||
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
|
|
||||||
import { userInputsFormToPromptVariables } from '@/utils/model-config'
|
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import type { DataSet } from '@/models/datasets'
|
|
||||||
import ConfigSummary from '@/app/components/explore/universal-chat/config-view/summary'
|
|
||||||
import { fetchDatasets } from '@/service/datasets'
|
|
||||||
import ItemOperation from '@/app/components/explore/item-operation'
|
|
||||||
import { useCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
|
||||||
|
|
||||||
const APP_ID = 'universal-chat'
|
|
||||||
const DEFAULT_PLUGIN = {
|
|
||||||
google_search: false,
|
|
||||||
web_reader: true,
|
|
||||||
wikipedia: true,
|
|
||||||
}
|
|
||||||
// Old configuration structure is not compatible with the current configuration
|
|
||||||
localStorage.removeItem('universal-chat-config')
|
|
||||||
const CONFIG_KEY = 'universal-chat-config-2'
|
|
||||||
type CONFIG = {
|
|
||||||
providerName: string
|
|
||||||
modelId: string
|
|
||||||
plugin: {
|
|
||||||
google_search: boolean
|
|
||||||
web_reader: boolean
|
|
||||||
wikipedia: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let prevConfig: null | CONFIG = localStorage.getItem(CONFIG_KEY) ? JSON.parse(localStorage.getItem(CONFIG_KEY) as string) as CONFIG : null
|
|
||||||
const setPrevConfig = (config: CONFIG) => {
|
|
||||||
prevConfig = config
|
|
||||||
localStorage.setItem(CONFIG_KEY, JSON.stringify(prevConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IMainProps = {}
|
|
||||||
|
|
||||||
const Main: FC<IMainProps> = () => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const media = useBreakpoints()
|
|
||||||
const isMobile = media === MediaType.mobile
|
|
||||||
const { agentThoughtModelList } = useProviderContext()
|
|
||||||
const getInitConfig = (type: 'model' | 'plugin') => {
|
|
||||||
if (type === 'model') {
|
|
||||||
return {
|
|
||||||
providerName: prevConfig?.providerName || agentThoughtModelList[0]?.provider,
|
|
||||||
modelId: prevConfig?.modelId || agentThoughtModelList[0]?.models[0]?.model,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'plugin')
|
|
||||||
return prevConfig?.plugin || DEFAULT_PLUGIN
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.title = `${t('explore.sidebar.chat')} - Dify`
|
|
||||||
}, [])
|
|
||||||
/*
|
|
||||||
* app info
|
|
||||||
*/
|
|
||||||
const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
|
|
||||||
const [isUnknwonReason, setIsUnknwonReason] = useState<boolean>(false)
|
|
||||||
const siteInfo: SiteInfo = (
|
|
||||||
{
|
|
||||||
title: 'universal Chatbot',
|
|
||||||
icon: '',
|
|
||||||
icon_background: '',
|
|
||||||
description: '',
|
|
||||||
default_language: 'en', // TODO
|
|
||||||
prompt_public: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null)
|
|
||||||
const [inited, setInited] = useState<boolean>(false)
|
|
||||||
// in mobile, show sidebar by click button
|
|
||||||
const [isShowSidebar, { setTrue: showSidebar, setFalse: hideSidebar }] = useBoolean(false)
|
|
||||||
/*
|
|
||||||
* conversation info
|
|
||||||
*/
|
|
||||||
const [allConversationList, setAllConversationList] = useState<ConversationItem[]>([])
|
|
||||||
const [isClearConversationList, { setTrue: clearConversationListTrue, setFalse: clearConversationListFalse }] = useBoolean(false)
|
|
||||||
const [isClearPinnedConversationList, { setTrue: clearPinnedConversationListTrue, setFalse: clearPinnedConversationListFalse }] = useBoolean(false)
|
|
||||||
const {
|
|
||||||
conversationList,
|
|
||||||
setConversationList,
|
|
||||||
pinnedConversationList,
|
|
||||||
setPinnedConversationList,
|
|
||||||
currConversationId,
|
|
||||||
getCurrConversationId,
|
|
||||||
setCurrConversationId,
|
|
||||||
getConversationIdFromStorage,
|
|
||||||
isNewConversation,
|
|
||||||
currConversationInfo,
|
|
||||||
currInputs,
|
|
||||||
newConversationInputs,
|
|
||||||
// existConversationInputs,
|
|
||||||
resetNewConversationInputs,
|
|
||||||
setCurrInputs,
|
|
||||||
setNewConversationInfo,
|
|
||||||
existConversationInfo,
|
|
||||||
setExistConversationInfo,
|
|
||||||
} = useConversation()
|
|
||||||
const [hasMore, setHasMore] = useState<boolean>(true)
|
|
||||||
const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true)
|
|
||||||
const onMoreLoaded = ({ data: conversations, has_more }: any) => {
|
|
||||||
setHasMore(has_more)
|
|
||||||
if (isClearConversationList) {
|
|
||||||
setConversationList(conversations)
|
|
||||||
clearConversationListFalse()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setConversationList([...conversationList, ...conversations])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => {
|
|
||||||
setHasPinnedMore(has_more)
|
|
||||||
if (isClearPinnedConversationList) {
|
|
||||||
setPinnedConversationList(conversations)
|
|
||||||
clearPinnedConversationListFalse()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setPinnedConversationList([...pinnedConversationList, ...conversations])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0)
|
|
||||||
const noticeUpdateList = () => {
|
|
||||||
setHasMore(true)
|
|
||||||
clearConversationListTrue()
|
|
||||||
|
|
||||||
setHasPinnedMore(true)
|
|
||||||
clearPinnedConversationListTrue()
|
|
||||||
|
|
||||||
setControlUpdateConversationList(Date.now())
|
|
||||||
}
|
|
||||||
const handlePin = async (id: string) => {
|
|
||||||
await pinConversation(id)
|
|
||||||
setControlItemOpHide(Date.now())
|
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
|
||||||
noticeUpdateList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUnpin = async (id: string) => {
|
|
||||||
await unpinConversation(id)
|
|
||||||
setControlItemOpHide(Date.now())
|
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
|
||||||
noticeUpdateList()
|
|
||||||
}
|
|
||||||
const [isShowConfirm, { setTrue: showConfirm, setFalse: hideConfirm }] = useBoolean(false)
|
|
||||||
const [toDeleteConversationId, setToDeleteConversationId] = useState('')
|
|
||||||
const handleDelete = (id: string) => {
|
|
||||||
setToDeleteConversationId(id)
|
|
||||||
hideSidebar() // mobile
|
|
||||||
showConfirm()
|
|
||||||
}
|
|
||||||
|
|
||||||
const didDelete = async () => {
|
|
||||||
await delConversation(toDeleteConversationId)
|
|
||||||
setControlItemOpHide(Date.now())
|
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
|
||||||
hideConfirm()
|
|
||||||
if (currConversationId === toDeleteConversationId)
|
|
||||||
handleConversationIdChange('-1')
|
|
||||||
|
|
||||||
noticeUpdateList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
|
|
||||||
const [speechToTextConfig, setSpeechToTextConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
|
|
||||||
const [citationConfig, setCitationConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
|
|
||||||
|
|
||||||
const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false)
|
|
||||||
|
|
||||||
const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string
|
|
||||||
const conversationIntroduction = currConversationInfo?.introduction || ''
|
|
||||||
|
|
||||||
const handleConversationSwitch = async () => {
|
|
||||||
if (!inited)
|
|
||||||
return
|
|
||||||
|
|
||||||
// update inputs of current conversation
|
|
||||||
let notSyncToStateIntroduction = ''
|
|
||||||
let notSyncToStateInputs: Record<string, any> | undefined | null = {}
|
|
||||||
// debugger
|
|
||||||
if (!isNewConversation) {
|
|
||||||
const item = allConversationList.find(item => item.id === currConversationId) as any
|
|
||||||
notSyncToStateInputs = item?.inputs || {}
|
|
||||||
// setCurrInputs(notSyncToStateInputs)
|
|
||||||
notSyncToStateIntroduction = item?.introduction || ''
|
|
||||||
setExistConversationInfo({
|
|
||||||
name: item?.name || '',
|
|
||||||
introduction: notSyncToStateIntroduction,
|
|
||||||
})
|
|
||||||
const modelConfig = item?.model_config
|
|
||||||
if (modelConfig) {
|
|
||||||
setModeId(modelConfig.model_id)
|
|
||||||
const pluginConfig: Record<string, boolean> = {}
|
|
||||||
const datasetIds: string[] = []
|
|
||||||
modelConfig.agent_mode.tools.forEach((item: any) => {
|
|
||||||
const pluginName = Object.keys(item)[0]
|
|
||||||
if (pluginName === 'dataset')
|
|
||||||
datasetIds.push(item.dataset.id)
|
|
||||||
else
|
|
||||||
pluginConfig[pluginName] = item[pluginName].enabled
|
|
||||||
})
|
|
||||||
setPlugins(pluginConfig)
|
|
||||||
if (datasetIds.length > 0) {
|
|
||||||
const { data } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasetIds } })
|
|
||||||
setDateSets(data)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setDateSets([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
configSetDefaultValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
configSetDefaultValue()
|
|
||||||
notSyncToStateInputs = newConversationInputs
|
|
||||||
setCurrInputs(notSyncToStateInputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update chat list of current conversation
|
|
||||||
if (!isNewConversation && !conversationIdChangeBecauseOfNew) {
|
|
||||||
fetchChatList(currConversationId).then((res: any) => {
|
|
||||||
const { data } = res
|
|
||||||
const newChatList: IChatItem[] = generateNewChatListWithOpenstatement(notSyncToStateIntroduction, notSyncToStateInputs)
|
|
||||||
|
|
||||||
data.forEach((item: any) => {
|
|
||||||
newChatList.push({
|
|
||||||
id: `question-${item.id}`,
|
|
||||||
content: item.query,
|
|
||||||
isAnswer: false,
|
|
||||||
})
|
|
||||||
newChatList.push({
|
|
||||||
...item,
|
|
||||||
id: item.id,
|
|
||||||
content: item.answer,
|
|
||||||
feedback: item.feedback,
|
|
||||||
isAnswer: true,
|
|
||||||
citation: item.retriever_resources,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
setChatList(newChatList)
|
|
||||||
setErrorHappened(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNewConversation) {
|
|
||||||
setChatList(generateNewChatListWithOpenstatement())
|
|
||||||
setErrorHappened(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
setControlFocus(Date.now())
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handleConversationSwitch()
|
|
||||||
}, [currConversationId, inited])
|
|
||||||
|
|
||||||
const handleConversationIdChange = (id: string) => {
|
|
||||||
if (id === '-1') {
|
|
||||||
createNewChat()
|
|
||||||
setConversationIdChangeBecauseOfNew(true)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setConversationIdChangeBecauseOfNew(false)
|
|
||||||
}
|
|
||||||
// trigger handleConversationSwitch
|
|
||||||
setCurrConversationId(id, APP_ID)
|
|
||||||
setIsShowSuggestion(false)
|
|
||||||
hideSidebar()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* chat info. chat is under conversation.
|
|
||||||
*/
|
|
||||||
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
|
|
||||||
const chatListDomRef = useRef<HTMLDivElement>(null)
|
|
||||||
useEffect(() => {
|
|
||||||
// scroll to bottom
|
|
||||||
if (chatListDomRef.current)
|
|
||||||
chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
|
|
||||||
}, [chatList, currConversationId])
|
|
||||||
|
|
||||||
// user can not edit inputs if user had send message
|
|
||||||
const createNewChat = async () => {
|
|
||||||
// if new chat is already exist, do not create new chat
|
|
||||||
abortController?.abort()
|
|
||||||
setResponsingFalse()
|
|
||||||
if (conversationList.some(item => item.id === '-1'))
|
|
||||||
return
|
|
||||||
|
|
||||||
setConversationList(produce(conversationList, (draft) => {
|
|
||||||
draft.unshift({
|
|
||||||
id: '-1',
|
|
||||||
name: t('share.chat.newChatDefaultName'),
|
|
||||||
inputs: newConversationInputs,
|
|
||||||
introduction: conversationIntroduction,
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
configSetDefaultValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sometime introduction is not applied to state
|
|
||||||
const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record<string, any> | null) => {
|
|
||||||
let caculatedIntroduction = introduction || conversationIntroduction || ''
|
|
||||||
const caculatedPromptVariables = inputs || currInputs || null
|
|
||||||
if (caculatedIntroduction && caculatedPromptVariables)
|
|
||||||
caculatedIntroduction = replaceStringWithValues(caculatedIntroduction, promptConfig?.prompt_variables || [], caculatedPromptVariables)
|
|
||||||
|
|
||||||
const openstatement = {
|
|
||||||
id: `${Date.now()}`,
|
|
||||||
content: caculatedIntroduction,
|
|
||||||
isAnswer: true,
|
|
||||||
feedbackDisabled: true,
|
|
||||||
isOpeningStatement: true,
|
|
||||||
}
|
|
||||||
if (caculatedIntroduction)
|
|
||||||
return [openstatement]
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchAllConversations = () => {
|
|
||||||
return fetchConversations(undefined, undefined, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchInitData = async () => {
|
|
||||||
return Promise.all([fetchAllConversations(), fetchAppParams()])
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const [conversationData, appParams]: any = await fetchInitData()
|
|
||||||
const prompt_template = ''
|
|
||||||
// handle current conversation id
|
|
||||||
const { data: allConversations } = conversationData as { data: ConversationItem[]; has_more: boolean }
|
|
||||||
const _conversationId = getConversationIdFromStorage(APP_ID)
|
|
||||||
const isNotNewConversation = allConversations.some(item => item.id === _conversationId)
|
|
||||||
setAllConversationList(allConversations)
|
|
||||||
// fetch new conversation info
|
|
||||||
const { user_input_form, opening_statement: introduction, suggested_questions_after_answer, speech_to_text, retriever_resource }: any = appParams
|
|
||||||
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
|
|
||||||
|
|
||||||
setNewConversationInfo({
|
|
||||||
name: t('share.chat.newChatDefaultName'),
|
|
||||||
introduction,
|
|
||||||
})
|
|
||||||
setPromptConfig({
|
|
||||||
prompt_template,
|
|
||||||
prompt_variables,
|
|
||||||
} as PromptConfig)
|
|
||||||
setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer)
|
|
||||||
setSpeechToTextConfig(speech_to_text)
|
|
||||||
setCitationConfig(retriever_resource)
|
|
||||||
|
|
||||||
if (isNotNewConversation)
|
|
||||||
setCurrConversationId(_conversationId, APP_ID, false)
|
|
||||||
|
|
||||||
setInited(true)
|
|
||||||
}
|
|
||||||
catch (e: any) {
|
|
||||||
if (e.status === 404) {
|
|
||||||
setAppUnavailable(true)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setIsUnknwonReason(true)
|
|
||||||
setAppUnavailable(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
|
|
||||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
|
||||||
const { notify } = useContext(ToastContext)
|
|
||||||
const logError = (message: string) => {
|
|
||||||
notify({ type: 'error', message })
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkCanSend = () => {
|
|
||||||
if (currConversationId !== '-1')
|
|
||||||
return true
|
|
||||||
|
|
||||||
const prompt_variables = promptConfig?.prompt_variables
|
|
||||||
const inputs = currInputs
|
|
||||||
if (!inputs || !prompt_variables || prompt_variables?.length === 0)
|
|
||||||
return true
|
|
||||||
|
|
||||||
let hasEmptyInput = false
|
|
||||||
const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
|
|
||||||
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
|
|
||||||
return res
|
|
||||||
}) || [] // compatible with old version
|
|
||||||
requiredVars.forEach(({ key }) => {
|
|
||||||
if (hasEmptyInput)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (!inputs?.[key])
|
|
||||||
hasEmptyInput = true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (hasEmptyInput) {
|
|
||||||
logError(t('appDebug.errorMessage.valueOfVarRequired'))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !hasEmptyInput
|
|
||||||
}
|
|
||||||
|
|
||||||
const [controlFocus, setControlFocus] = useState(0)
|
|
||||||
const [isShowSuggestion, setIsShowSuggestion] = useState(false)
|
|
||||||
const doShowSuggestion = isShowSuggestion && !isResponsing
|
|
||||||
const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
|
|
||||||
const [messageTaskId, setMessageTaskId] = useState('')
|
|
||||||
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
|
|
||||||
const [errorHappened, setErrorHappened] = useState(false)
|
|
||||||
const [isResponsingConIsCurrCon, setIsResponsingConCurrCon, getIsResponsingConIsCurrCon] = useGetState(true)
|
|
||||||
const initConfig = getInitConfig('model')
|
|
||||||
const [modelId, setModeId] = useState<string>((initConfig as any)?.modelId as string)
|
|
||||||
const [providerName, setProviderName] = useState<string>((initConfig as any)?.providerName)
|
|
||||||
const { currentModel } = useCurrentProviderAndModel(
|
|
||||||
agentThoughtModelList,
|
|
||||||
{ provider: providerName, model: modelId },
|
|
||||||
)
|
|
||||||
const handleSend = async (message: string) => {
|
|
||||||
if (isNewConversation) {
|
|
||||||
const isModelSelected = modelId && !!currentModel
|
|
||||||
if (!isModelSelected) {
|
|
||||||
notify({ type: 'error', message: t('appDebug.errorMessage.notSelectModel') })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setPrevConfig({
|
|
||||||
modelId,
|
|
||||||
providerName,
|
|
||||||
plugin: plugins as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isResponsing) {
|
|
||||||
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const formattedPlugins = Object.keys(plugins).map(key => ({
|
|
||||||
[key]: {
|
|
||||||
enabled: plugins[key],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
const formattedDataSets = dataSets.map(({ id }) => {
|
|
||||||
return {
|
|
||||||
dataset: {
|
|
||||||
enabled: true,
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const data = {
|
|
||||||
query: message,
|
|
||||||
conversation_id: isNewConversation ? null : currConversationId,
|
|
||||||
model: modelId,
|
|
||||||
provider: providerName,
|
|
||||||
tools: [...formattedPlugins, ...formattedDataSets],
|
|
||||||
}
|
|
||||||
|
|
||||||
// qustion
|
|
||||||
const questionId = `question-${Date.now()}`
|
|
||||||
const questionItem = {
|
|
||||||
id: questionId,
|
|
||||||
content: message,
|
|
||||||
agent_thoughts: [],
|
|
||||||
isAnswer: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const placeholderAnswerId = `answer-placeholder-${Date.now()}`
|
|
||||||
const placeholderAnswerItem = {
|
|
||||||
id: placeholderAnswerId,
|
|
||||||
content: '',
|
|
||||||
isAnswer: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const newList = [...getChatList(), questionItem, placeholderAnswerItem]
|
|
||||||
setChatList(newList)
|
|
||||||
|
|
||||||
// answer
|
|
||||||
const responseItem: IChatItem = {
|
|
||||||
id: `${Date.now()}`,
|
|
||||||
content: '',
|
|
||||||
agent_thoughts: [],
|
|
||||||
isAnswer: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevTempNewConversationId = getCurrConversationId() || '-1'
|
|
||||||
let tempNewConversationId = prevTempNewConversationId
|
|
||||||
|
|
||||||
setHasStopResponded(false)
|
|
||||||
setResponsingTrue()
|
|
||||||
setErrorHappened(false)
|
|
||||||
setIsShowSuggestion(false)
|
|
||||||
setIsResponsingConCurrCon(true)
|
|
||||||
|
|
||||||
sendChatMessage(data, {
|
|
||||||
getAbortController: (abortController) => {
|
|
||||||
setAbortController(abortController)
|
|
||||||
},
|
|
||||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
|
||||||
responseItem.content = responseItem.content + message
|
|
||||||
responseItem.id = messageId
|
|
||||||
if (isFirstMessage && newConversationId)
|
|
||||||
tempNewConversationId = newConversationId
|
|
||||||
|
|
||||||
setMessageTaskId(taskId)
|
|
||||||
// has switched to other conversation
|
|
||||||
if (prevTempNewConversationId !== getCurrConversationId()) {
|
|
||||||
setIsResponsingConCurrCon(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// closesure new list is outdated.
|
|
||||||
const newListWithAnswer = produce(
|
|
||||||
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
|
||||||
(draft) => {
|
|
||||||
if (!draft.find(item => item.id === questionId))
|
|
||||||
draft.push({ ...questionItem } as any)
|
|
||||||
|
|
||||||
draft.push({ ...responseItem })
|
|
||||||
})
|
|
||||||
|
|
||||||
setChatList(newListWithAnswer)
|
|
||||||
},
|
|
||||||
async onCompleted(hasError?: boolean) {
|
|
||||||
if (hasError) {
|
|
||||||
setResponsingFalse()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getConversationIdChangeBecauseOfNew()) {
|
|
||||||
const { data: allConversations }: any = await fetchAllConversations()
|
|
||||||
const newItem: any = await generationConversationName(allConversations[0].id)
|
|
||||||
const newAllConversations = produce(allConversations, (draft: any) => {
|
|
||||||
draft[0].name = newItem.name
|
|
||||||
})
|
|
||||||
setAllConversationList(newAllConversations as any)
|
|
||||||
noticeUpdateList()
|
|
||||||
}
|
|
||||||
setConversationIdChangeBecauseOfNew(false)
|
|
||||||
resetNewConversationInputs()
|
|
||||||
setCurrConversationId(tempNewConversationId, APP_ID, true)
|
|
||||||
if (getIsResponsingConIsCurrCon() && suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) {
|
|
||||||
const { data }: any = await fetchSuggestedQuestions(responseItem.id)
|
|
||||||
setSuggestQuestions(data)
|
|
||||||
setIsShowSuggestion(true)
|
|
||||||
}
|
|
||||||
setResponsingFalse()
|
|
||||||
},
|
|
||||||
onThought(thought) {
|
|
||||||
// thought finished then start to return message. Warning: use push agent_thoughts.push would caused problem when the thought is more then 2
|
|
||||||
responseItem.id = thought.message_id;
|
|
||||||
(responseItem as any).agent_thoughts = [...(responseItem as any).agent_thoughts, thought] // .push(thought)
|
|
||||||
// has switched to other conversation
|
|
||||||
|
|
||||||
if (prevTempNewConversationId !== getCurrConversationId()) {
|
|
||||||
setIsResponsingConCurrCon(false)
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
onMessageEnd: (messageEnd) => {
|
|
||||||
responseItem.citation = messageEnd.metadata?.retriever_resources
|
|
||||||
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
onError() {
|
|
||||||
setErrorHappened(true)
|
|
||||||
// role back placeholder answer
|
|
||||||
setChatList(produce(getChatList(), (draft) => {
|
|
||||||
draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
|
|
||||||
}))
|
|
||||||
setResponsingFalse()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFeedback = async (messageId: string, feedback: Feedbacktype) => {
|
|
||||||
await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } })
|
|
||||||
const newChatList = chatList.map((item) => {
|
|
||||||
if (item.id === messageId) {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
feedback,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
setChatList(newChatList)
|
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
|
||||||
}
|
|
||||||
|
|
||||||
const [controlChatUpdateAllConversation, setControlChatUpdateAllConversation] = useState(0)
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
if (controlChatUpdateAllConversation && !isNewConversation) {
|
|
||||||
const { data: allConversations } = await fetchAllConversations() as { data: ConversationItem[]; has_more: boolean }
|
|
||||||
const item = allConversations.find(item => item.id === currConversationId)
|
|
||||||
setAllConversationList(allConversations)
|
|
||||||
if (item) {
|
|
||||||
setExistConversationInfo({
|
|
||||||
...existConversationInfo,
|
|
||||||
name: item?.name || '',
|
|
||||||
} as any)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}, [controlChatUpdateAllConversation])
|
|
||||||
const renderSidebar = () => {
|
|
||||||
if (!APP_ID || !promptConfig)
|
|
||||||
return null
|
|
||||||
return (
|
|
||||||
<Sidebar
|
|
||||||
list={conversationList}
|
|
||||||
onListChanged={(list) => {
|
|
||||||
setConversationList(list)
|
|
||||||
setControlChatUpdateAllConversation(Date.now())
|
|
||||||
}}
|
|
||||||
isClearConversationList={isClearConversationList}
|
|
||||||
pinnedList={pinnedConversationList}
|
|
||||||
onPinnedListChanged={(list) => {
|
|
||||||
setPinnedConversationList(list)
|
|
||||||
setControlChatUpdateAllConversation(Date.now())
|
|
||||||
}}
|
|
||||||
isClearPinnedConversationList={isClearPinnedConversationList}
|
|
||||||
onMoreLoaded={onMoreLoaded}
|
|
||||||
onPinnedMoreLoaded={onPinnedMoreLoaded}
|
|
||||||
isNoMore={!hasMore}
|
|
||||||
isPinnedNoMore={!hasPinnedMore}
|
|
||||||
onCurrentIdChange={handleConversationIdChange}
|
|
||||||
currentId={currConversationId}
|
|
||||||
copyRight={''}
|
|
||||||
isInstalledApp={false}
|
|
||||||
isUniversalChat
|
|
||||||
installedAppId={''}
|
|
||||||
siteInfo={siteInfo}
|
|
||||||
onPin={handlePin}
|
|
||||||
onUnpin={handleUnpin}
|
|
||||||
controlUpdateList={controlUpdateConversationList}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
onStartChat={() => handleConversationIdChange('-1')}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// const currModel = MODEL_LIST.find(item => item.id === modelId)
|
|
||||||
|
|
||||||
const [plugins, setPlugins] = useState<Record<string, boolean>>(getInitConfig('plugin') as Record<string, boolean>)
|
|
||||||
const handlePluginsChange = (key: string, value: boolean) => {
|
|
||||||
setPlugins({
|
|
||||||
...plugins,
|
|
||||||
[key]: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const [dataSets, setDateSets] = useState<DataSet[]>([])
|
|
||||||
const configSetDefaultValue = () => {
|
|
||||||
const initConfig = getInitConfig('model')
|
|
||||||
setModeId((initConfig as any)?.modelId as string)
|
|
||||||
setProviderName((initConfig as any)?.providerName)
|
|
||||||
setPlugins(getInitConfig('plugin') as any)
|
|
||||||
setDateSets([])
|
|
||||||
}
|
|
||||||
const isCurrConversationPinned = !!pinnedConversationList.find(item => item.id === currConversationId)
|
|
||||||
const [controlItemOpHide, setControlItemOpHide] = useState(0)
|
|
||||||
if (appUnavailable)
|
|
||||||
return <AppUnavailable isUnknwonReason={isUnknwonReason} />
|
|
||||||
|
|
||||||
if (!promptConfig)
|
|
||||||
return <Loading type='app' />
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='bg-gray-100 h-full'>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'flex rounded-t-2xl bg-white overflow-hidden rounded-b-2xl h-full',
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* sidebar */}
|
|
||||||
{!isMobile && renderSidebar()}
|
|
||||||
{isMobile && isShowSidebar && (
|
|
||||||
<div className='fixed inset-0 z-50'
|
|
||||||
style={{ backgroundColor: 'rgba(35, 56, 118, 0.2)' }}
|
|
||||||
onClick={hideSidebar}
|
|
||||||
>
|
|
||||||
<div className='inline-block' onClick={e => e.stopPropagation()}>
|
|
||||||
{renderSidebar()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* main */}
|
|
||||||
<div className={cn(
|
|
||||||
'h-full flex-grow flex flex-col overflow-y-auto',
|
|
||||||
)
|
|
||||||
}>
|
|
||||||
{(!isNewConversation || isResponsing || errorHappened) && (
|
|
||||||
<div className='mb-5 antialiased font-sans shrink-0 relative mobile:min-h-[48px] tablet:min-h-[64px]'>
|
|
||||||
<div className='absolute z-10 top-0 left-0 right-0 flex items-center justify-between border-b border-gray-100 mobile:h-12 tablet:h-16 px-8 bg-white'>
|
|
||||||
<div className='text-gray-900'>{conversationName}</div>
|
|
||||||
<div className='flex items-center shrink-0 ml-2 space-x-2'>
|
|
||||||
<ConfigSummary
|
|
||||||
modelId={modelId}
|
|
||||||
providerName={providerName}
|
|
||||||
plugins={plugins}
|
|
||||||
dataSets={dataSets}
|
|
||||||
/>
|
|
||||||
<div className={cn('flex w-8 h-8 justify-center items-center shrink-0 rounded-lg border border-gray-200')} onClick={e => e.stopPropagation()}>
|
|
||||||
<ItemOperation
|
|
||||||
key={controlItemOpHide}
|
|
||||||
className='!w-8 !h-8'
|
|
||||||
isPinned={isCurrConversationPinned}
|
|
||||||
togglePin={() => isCurrConversationPinned ? handleUnpin(currConversationId) : handlePin(currConversationId)}
|
|
||||||
isShowDelete
|
|
||||||
onDelete={() => handleDelete(currConversationId)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<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={cn('pc:w-[794px] max-w-full mobile:w-full mx-auto h-full overflow-y-auto')} ref={chatListDomRef}>
|
|
||||||
<Chat
|
|
||||||
isShowConfigElem={isNewConversation && chatList.length === 0}
|
|
||||||
configElem={<Init
|
|
||||||
modelId={modelId}
|
|
||||||
providerName={providerName}
|
|
||||||
onModelChange={(modelId, providerName) => {
|
|
||||||
setModeId(modelId)
|
|
||||||
setProviderName(providerName)
|
|
||||||
}}
|
|
||||||
plugins={plugins}
|
|
||||||
onPluginChange={handlePluginsChange}
|
|
||||||
dataSets={dataSets}
|
|
||||||
onDataSetsChange={setDateSets}
|
|
||||||
/>}
|
|
||||||
chatList={chatList}
|
|
||||||
onSend={handleSend}
|
|
||||||
isHideFeedbackEdit
|
|
||||||
onFeedback={handleFeedback}
|
|
||||||
isResponsing={isResponsing}
|
|
||||||
canStopResponsing={!!messageTaskId && isResponsingConIsCurrCon}
|
|
||||||
abortResponsing={async () => {
|
|
||||||
await stopChatMessageResponding(messageTaskId)
|
|
||||||
setHasStopResponded(true)
|
|
||||||
setResponsingFalse()
|
|
||||||
}}
|
|
||||||
checkCanSend={checkCanSend}
|
|
||||||
controlFocus={controlFocus}
|
|
||||||
isShowSuggestion={doShowSuggestion}
|
|
||||||
suggestionList={suggestQuestions}
|
|
||||||
isShowSpeechToText={speechToTextConfig?.enabled}
|
|
||||||
isShowCitation={citationConfig?.enabled}
|
|
||||||
dataSets={dataSets}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isShowConfirm && (
|
|
||||||
<Confirm
|
|
||||||
title={t('share.chat.deleteConversation.title')}
|
|
||||||
content={t('share.chat.deleteConversation.content')}
|
|
||||||
isShow={isShowConfirm}
|
|
||||||
onClose={hideConfirm}
|
|
||||||
onConfirm={didDelete}
|
|
||||||
onCancel={hideConfirm}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Main)
|
|
@ -1,43 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import cn from 'classnames'
|
|
||||||
import type { IConfigProps } from '../config'
|
|
||||||
import Config from '../config'
|
|
||||||
import s from './style.module.css'
|
|
||||||
|
|
||||||
const Line = (
|
|
||||||
<svg width="100%" height="1" viewBox="0 0 720 1" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<line y1="0.5" x2="720" y2="0.5" stroke="url(#paint0_linear_6845_53470)"/>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="paint0_linear_6845_53470" x1="0" y1="1" x2="720" y2="1" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stopColor="#F2F4F7" stopOpacity="0"/>
|
|
||||||
<stop offset="0.491667" stopColor="#F2F4F7"/>
|
|
||||||
<stop offset="1" stopColor="#F2F4F7" stopOpacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const Init: FC<IConfigProps> = ({
|
|
||||||
...configProps
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='h-full flex items-center justify-center'>
|
|
||||||
<div>
|
|
||||||
<div className='text-center'>
|
|
||||||
<div className={cn(s.textGradient, 'mb-2 leading-[32px] font-semibold text-[24px]')}>{t('explore.universalChat.welcome')}</div>
|
|
||||||
<div className='mb-2 font-normal text-sm text-gray-500'>{t('explore.universalChat.welcomeDescribe')}</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex mb-2 h-8 items-center'>
|
|
||||||
{Line}
|
|
||||||
</div>
|
|
||||||
<Config {...configProps} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(Init)
|
|
@ -1,9 +0,0 @@
|
|||||||
.textGradient {
|
|
||||||
background: linear-gradient(to right, rgba(16, 74, 225, 1) 0, rgba(0, 152, 238, 1) 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -58,7 +58,6 @@ export type IMainProps = {
|
|||||||
isInstalledApp?: boolean
|
isInstalledApp?: boolean
|
||||||
installedAppInfo?: InstalledApp
|
installedAppInfo?: InstalledApp
|
||||||
isSupportPlugin?: boolean
|
isSupportPlugin?: boolean
|
||||||
isUniversalChat?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Main: FC<IMainProps> = ({
|
const Main: FC<IMainProps> = ({
|
||||||
|
@ -11,7 +11,6 @@ import AppInfo from '@/app/components/share/chat/sidebar/app-info'
|
|||||||
// import Card from './card'
|
// import Card from './card'
|
||||||
import type { ConversationItem, SiteInfo } from '@/models/share'
|
import type { ConversationItem, SiteInfo } from '@/models/share'
|
||||||
import { fetchConversations } from '@/service/share'
|
import { fetchConversations } from '@/service/share'
|
||||||
import { fetchConversations as fetchUniversalConversations } from '@/service/universal-chat'
|
|
||||||
|
|
||||||
export type ISidebarProps = {
|
export type ISidebarProps = {
|
||||||
copyRight: string
|
copyRight: string
|
||||||
@ -25,7 +24,6 @@ export type ISidebarProps = {
|
|||||||
isClearPinnedConversationList: boolean
|
isClearPinnedConversationList: boolean
|
||||||
isInstalledApp: boolean
|
isInstalledApp: boolean
|
||||||
installedAppId?: string
|
installedAppId?: string
|
||||||
isUniversalChat?: boolean
|
|
||||||
siteInfo: SiteInfo
|
siteInfo: SiteInfo
|
||||||
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||||
onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||||
@ -50,7 +48,6 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
isClearPinnedConversationList,
|
isClearPinnedConversationList,
|
||||||
isInstalledApp,
|
isInstalledApp,
|
||||||
installedAppId,
|
installedAppId,
|
||||||
isUniversalChat,
|
|
||||||
siteInfo,
|
siteInfo,
|
||||||
onMoreLoaded,
|
onMoreLoaded,
|
||||||
onPinnedMoreLoaded,
|
onPinnedMoreLoaded,
|
||||||
@ -66,13 +63,7 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
const [hasPinned, setHasPinned] = useState(false)
|
const [hasPinned, setHasPinned] = useState(false)
|
||||||
|
|
||||||
const checkHasPinned = async () => {
|
const checkHasPinned = async () => {
|
||||||
let res: any
|
const res = await fetchConversations(isInstalledApp, installedAppId, undefined, true) as any
|
||||||
if (isUniversalChat)
|
|
||||||
res = await fetchUniversalConversations(undefined, true)
|
|
||||||
|
|
||||||
else
|
|
||||||
res = await fetchConversations(isInstalledApp, installedAppId, undefined, true)
|
|
||||||
|
|
||||||
setHasPinned(res.data.length > 0)
|
setHasPinned(res.data.length > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,13 +76,13 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
checkHasPinned()
|
checkHasPinned()
|
||||||
}, [controlUpdateList])
|
}, [controlUpdateList])
|
||||||
|
|
||||||
const maxListHeight = (isInstalledApp || isUniversalChat) ? 'max-h-[30vh]' : 'max-h-[40vh]'
|
const maxListHeight = (isInstalledApp) ? 'max-h-[30vh]' : 'max-h-[40vh]'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
cn(
|
cn(
|
||||||
(isInstalledApp || isUniversalChat) ? 'tablet:h-[calc(100vh_-_74px)]' : '',
|
(isInstalledApp) ? 'tablet:h-[calc(100vh_-_74px)]' : '',
|
||||||
'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px] border-r border-gray-200 mobile:h-screen',
|
'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px] border-r border-gray-200 mobile:h-screen',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -125,7 +116,6 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
isClearConversationList={isClearPinnedConversationList}
|
isClearConversationList={isClearPinnedConversationList}
|
||||||
isInstalledApp={isInstalledApp}
|
isInstalledApp={isInstalledApp}
|
||||||
installedAppId={installedAppId}
|
installedAppId={installedAppId}
|
||||||
isUniversalChat={isUniversalChat}
|
|
||||||
onMoreLoaded={onPinnedMoreLoaded}
|
onMoreLoaded={onPinnedMoreLoaded}
|
||||||
isNoMore={isPinnedNoMore}
|
isNoMore={isPinnedNoMore}
|
||||||
isPinned={true}
|
isPinned={true}
|
||||||
@ -149,7 +139,6 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
isClearConversationList={isClearConversationList}
|
isClearConversationList={isClearConversationList}
|
||||||
isInstalledApp={isInstalledApp}
|
isInstalledApp={isInstalledApp}
|
||||||
installedAppId={installedAppId}
|
installedAppId={installedAppId}
|
||||||
isUniversalChat={isUniversalChat}
|
|
||||||
onMoreLoaded={onMoreLoaded}
|
onMoreLoaded={onMoreLoaded}
|
||||||
isNoMore={isNoMore}
|
isNoMore={isNoMore}
|
||||||
isPinned={false}
|
isPinned={false}
|
||||||
@ -160,11 +149,9 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{!isUniversalChat && (
|
<div className="flex flex-shrink-0 pr-4 pb-4 pl-4">
|
||||||
<div className="flex flex-shrink-0 pr-4 pb-4 pl-4">
|
<div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div>
|
||||||
<div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import RenameModal from '../rename-modal'
|
|||||||
import Item from './item'
|
import Item from './item'
|
||||||
import type { ConversationItem } from '@/models/share'
|
import type { ConversationItem } from '@/models/share'
|
||||||
import { fetchConversations, renameConversation } from '@/service/share'
|
import { fetchConversations, renameConversation } from '@/service/share'
|
||||||
import { fetchConversations as fetchUniversalConversations, renameConversation as renameUniversalConversation } from '@/service/universal-chat'
|
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
|
|
||||||
export type IListProps = {
|
export type IListProps = {
|
||||||
@ -20,7 +19,6 @@ export type IListProps = {
|
|||||||
onListChanged?: (newList: ConversationItem[]) => void
|
onListChanged?: (newList: ConversationItem[]) => void
|
||||||
isClearConversationList: boolean
|
isClearConversationList: boolean
|
||||||
isInstalledApp: boolean
|
isInstalledApp: boolean
|
||||||
isUniversalChat?: boolean
|
|
||||||
installedAppId?: string
|
installedAppId?: string
|
||||||
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||||
isNoMore: boolean
|
isNoMore: boolean
|
||||||
@ -38,7 +36,6 @@ const List: FC<IListProps> = ({
|
|||||||
onListChanged,
|
onListChanged,
|
||||||
isClearConversationList,
|
isClearConversationList,
|
||||||
isInstalledApp,
|
isInstalledApp,
|
||||||
isUniversalChat,
|
|
||||||
installedAppId,
|
installedAppId,
|
||||||
onMoreLoaded,
|
onMoreLoaded,
|
||||||
isNoMore,
|
isNoMore,
|
||||||
@ -56,11 +53,7 @@ const List: FC<IListProps> = ({
|
|||||||
let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
|
let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
|
||||||
if (lastId === '-1')
|
if (lastId === '-1')
|
||||||
lastId = undefined
|
lastId = undefined
|
||||||
let res: any
|
const res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) as any
|
||||||
if (isUniversalChat)
|
|
||||||
res = await fetchUniversalConversations(lastId, isPinned)
|
|
||||||
else
|
|
||||||
res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
|
|
||||||
const { data: conversations, has_more }: any = res
|
const { data: conversations, has_more }: any = res
|
||||||
onMoreLoaded({ data: conversations, has_more })
|
onMoreLoaded({ data: conversations, has_more })
|
||||||
}
|
}
|
||||||
@ -93,11 +86,7 @@ const List: FC<IListProps> = ({
|
|||||||
setIsSaving()
|
setIsSaving()
|
||||||
const currId = currentConversation.id
|
const currId = currentConversation.id
|
||||||
try {
|
try {
|
||||||
if (isUniversalChat)
|
await renameConversation(isInstalledApp, installedAppId, currId, newName)
|
||||||
await renameUniversalConversation(currId, newName)
|
|
||||||
|
|
||||||
else
|
|
||||||
await renameConversation(isInstalledApp, installedAppId, currId, newName)
|
|
||||||
|
|
||||||
Toast.notify({
|
Toast.notify({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
@ -52,8 +52,6 @@ export const MODEL_LIST = [
|
|||||||
{ id: 'claude-instant-1', name: 'claude-instant-1', type: AppType.completion, provider: ProviderType.anthropic }, // set 30k
|
{ id: 'claude-instant-1', name: 'claude-instant-1', type: AppType.completion, provider: ProviderType.anthropic }, // set 30k
|
||||||
{ id: 'claude-2', name: 'claude-2', type: AppType.completion, provider: ProviderType.anthropic }, // set 30k
|
{ id: 'claude-2', name: 'claude-2', type: AppType.completion, provider: ProviderType.anthropic }, // set 30k
|
||||||
]
|
]
|
||||||
const UNIVERSAL_CHAT_MODEL_ID_LIST = ['gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4', 'claude-2']
|
|
||||||
export const UNIVERSAL_CHAT_MODEL_LIST = MODEL_LIST.filter(({ id, type }) => UNIVERSAL_CHAT_MODEL_ID_LIST.includes(id) && (type === AppType.chat))
|
|
||||||
export const TONE_LIST = [
|
export const TONE_LIST = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -36,45 +36,6 @@ const translation = {
|
|||||||
Programming: 'Programming',
|
Programming: 'Programming',
|
||||||
HR: 'HR',
|
HR: 'HR',
|
||||||
},
|
},
|
||||||
universalChat: {
|
|
||||||
welcome: 'Start chat with Dify',
|
|
||||||
welcomeDescribe: 'Your AI conversation companion for personalized assistance',
|
|
||||||
model: 'Model',
|
|
||||||
plugins: {
|
|
||||||
name: 'Plugins',
|
|
||||||
google_search: {
|
|
||||||
name: 'Google Search',
|
|
||||||
more: {
|
|
||||||
left: 'Enable the plugin, ',
|
|
||||||
link: 'set up your SerpAPI key',
|
|
||||||
right: ' first.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
web_reader: {
|
|
||||||
name: 'Web Reader',
|
|
||||||
description: 'Get needed information from any web link',
|
|
||||||
},
|
|
||||||
wikipedia: {
|
|
||||||
name: 'Wikipedia',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
thought: {
|
|
||||||
show: 'Show',
|
|
||||||
hide: 'Hide',
|
|
||||||
processOfThought: ' the process of thinking',
|
|
||||||
res: {
|
|
||||||
webReader: {
|
|
||||||
normal: 'Reading {url}',
|
|
||||||
hasPageInfo: 'Reading next page of {url}',
|
|
||||||
},
|
|
||||||
google: 'Searching Google {{query}}',
|
|
||||||
wikipedia: 'Searching Wikipedia {{query}}',
|
|
||||||
dataset: 'Retrieving Knowledge {datasetName}',
|
|
||||||
date: 'Searching date',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
viewConfigDetailTip: 'In conversation, cannot change above settings',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -36,45 +36,6 @@ const translation = {
|
|||||||
Programming: 'Programação',
|
Programming: 'Programação',
|
||||||
HR: 'RH',
|
HR: 'RH',
|
||||||
},
|
},
|
||||||
universalChat: {
|
|
||||||
welcome: 'Iniciar chat com Dify',
|
|
||||||
welcomeDescribe: 'Seu companheiro de conversa de IA para assistência personalizada',
|
|
||||||
model: 'Modelo',
|
|
||||||
plugins: {
|
|
||||||
name: 'Plugins',
|
|
||||||
google_search: {
|
|
||||||
name: 'Pesquisa do Google',
|
|
||||||
more: {
|
|
||||||
left: 'Ative o plugin, ',
|
|
||||||
link: 'configure sua chave SerpAPI',
|
|
||||||
right: ' primeiro.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
web_reader: {
|
|
||||||
name: 'Leitor da Web',
|
|
||||||
description: 'Obtenha informações necessárias de qualquer link da web',
|
|
||||||
},
|
|
||||||
wikipedia: {
|
|
||||||
name: 'Wikipedia',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
thought: {
|
|
||||||
show: 'Mostrar',
|
|
||||||
hide: 'Ocultar',
|
|
||||||
processOfThought: ' o processo de pensamento',
|
|
||||||
res: {
|
|
||||||
webReader: {
|
|
||||||
normal: 'Lendo {url}',
|
|
||||||
hasPageInfo: 'Lendo próxima página de {url}',
|
|
||||||
},
|
|
||||||
google: 'Pesquisando no Google {{query}}',
|
|
||||||
wikipedia: 'Pesquisando na Wikipedia {{query}}',
|
|
||||||
dataset: 'Recuperando Conhecimento {datasetName}',
|
|
||||||
date: 'Pesquisando data',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
viewConfigDetailTip: 'Na conversa, não é possível alterar as configurações acima',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -36,45 +36,6 @@ const translation = {
|
|||||||
Programming: '编程',
|
Programming: '编程',
|
||||||
HR: '人力资源',
|
HR: '人力资源',
|
||||||
},
|
},
|
||||||
universalChat: {
|
|
||||||
welcome: '开始和 Dify 聊天吧',
|
|
||||||
welcomeDescribe: '您的 AI 对话伴侣,为您提供个性化的帮助',
|
|
||||||
model: '模型',
|
|
||||||
plugins: {
|
|
||||||
name: '插件',
|
|
||||||
google_search: {
|
|
||||||
name: '谷歌搜索',
|
|
||||||
more: {
|
|
||||||
left: '启用插件,首先',
|
|
||||||
link: '设置您的 SerpAPI 密钥',
|
|
||||||
right: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
web_reader: {
|
|
||||||
name: '解析链接',
|
|
||||||
description: '从任何网页链接获取所需信息',
|
|
||||||
},
|
|
||||||
wikipedia: {
|
|
||||||
name: '维基百科',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
thought: {
|
|
||||||
show: '显示',
|
|
||||||
hide: '隐藏',
|
|
||||||
processOfThought: '思考过程',
|
|
||||||
res: {
|
|
||||||
webReader: {
|
|
||||||
normal: '解析链接 {url}',
|
|
||||||
hasPageInfo: '解析链接 {url} 的下一页',
|
|
||||||
},
|
|
||||||
google: '搜索谷歌 {{query}}',
|
|
||||||
wikipedia: '搜索维基百科 {{query}}',
|
|
||||||
dataset: '检索知识库 {datasetName}',
|
|
||||||
date: '查询日期',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
viewConfigDetailTip: '在对话中,无法更改上述设置',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import type { IOnCompleted, IOnData, IOnError, IOnMessageEnd, IOnThought } from './base'
|
|
||||||
import {
|
|
||||||
del, get, patch, post, ssePost,
|
|
||||||
} from './base'
|
|
||||||
import type { Feedbacktype } from '@/app/components/app/chat/type'
|
|
||||||
|
|
||||||
const baseUrl = 'universal-chat'
|
|
||||||
|
|
||||||
function getUrl(url: string) {
|
|
||||||
return `${baseUrl}/${url.startsWith('/') ? url.slice(1) : url}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendChatMessage = async (body: Record<string, any>, { onData, onCompleted, onError, onThought, onMessageEnd, getAbortController }: {
|
|
||||||
onData: IOnData
|
|
||||||
onCompleted: IOnCompleted
|
|
||||||
onError: IOnError
|
|
||||||
onThought: IOnThought
|
|
||||||
onMessageEnd: IOnMessageEnd
|
|
||||||
getAbortController?: (abortController: AbortController) => void
|
|
||||||
}) => {
|
|
||||||
return ssePost(getUrl('messages'), {
|
|
||||||
body: {
|
|
||||||
...body,
|
|
||||||
response_mode: 'streaming',
|
|
||||||
},
|
|
||||||
}, { onData, onCompleted, onThought, onError, getAbortController, onMessageEnd })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const stopChatMessageResponding = async (taskId: string) => {
|
|
||||||
return post(getUrl(`messages/${taskId}/stop`))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchConversations = async (last_id?: string, pinned?: boolean, limit?: number) => {
|
|
||||||
return get(getUrl('conversations'), { params: { ...{ limit: limit || 20 }, ...(last_id ? { last_id } : {}), ...(pinned !== undefined ? { pinned } : {}) } })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pinConversation = async (id: string) => {
|
|
||||||
return patch(getUrl(`conversations/${id}/pin`))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const unpinConversation = async (id: string) => {
|
|
||||||
return patch(getUrl(`conversations/${id}/unpin`))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const delConversation = async (id: string) => {
|
|
||||||
return del(getUrl(`conversations/${id}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const renameConversation = async (id: string, name: string) => {
|
|
||||||
return post(getUrl(`conversations/${id}/name`), { body: { name } })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generationConversationName = async (id: string) => {
|
|
||||||
return post(getUrl(`conversations/${id}/name`), { body: { auto_generate: true } })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchChatList = async (conversationId: string) => {
|
|
||||||
return get(getUrl('messages'), { params: { conversation_id: conversationId, limit: 20, last_id: '' } })
|
|
||||||
}
|
|
||||||
|
|
||||||
// init value. wait for server update
|
|
||||||
export const fetchAppParams = async () => {
|
|
||||||
return get(getUrl('parameters'))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => {
|
|
||||||
return post(getUrl(url), { body })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchMoreLikeThis = async (messageId: string) => {
|
|
||||||
return get(getUrl(`/messages/${messageId}/more-like-this`), {
|
|
||||||
params: {
|
|
||||||
response_mode: 'blocking',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchSuggestedQuestions = (messageId: string) => {
|
|
||||||
return get(getUrl(`/messages/${messageId}/suggested-questions`))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const audioToText = (url: string, body: FormData) => {
|
|
||||||
return post(url, { body }, { bodyStringify: false, deleteContentType: true }) as Promise<{ text: string }>
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user