mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 03:32:23 +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
|
||||
readonly?: boolean
|
||||
}
|
||||
// used in universal-chat
|
||||
const CardItem: FC<ICardItemProps> = ({
|
||||
className,
|
||||
config,
|
||||
|
|
|
@ -33,7 +33,7 @@ const AudioBtn = ({
|
|||
if (value !== '') {
|
||||
formData.append('text', removeCodeBlocks(value))
|
||||
|
||||
let url = '/universal-chat/text-to-audio'
|
||||
let url = ''
|
||||
let isPublic = false
|
||||
|
||||
if (params.token) {
|
||||
|
|
|
@ -86,7 +86,7 @@ const VoiceInput = ({
|
|||
const formData = new FormData()
|
||||
formData.append('file', mp3File)
|
||||
|
||||
let url = '/universal-chat/audio-to-text'
|
||||
let url = ''
|
||||
let isPublic = false
|
||||
|
||||
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
|
||||
installedAppInfo?: InstalledApp
|
||||
isSupportPlugin?: boolean
|
||||
isUniversalChat?: boolean
|
||||
}
|
||||
|
||||
const Main: FC<IMainProps> = ({
|
||||
|
|
|
@ -11,7 +11,6 @@ import AppInfo from '@/app/components/share/chat/sidebar/app-info'
|
|||
// import Card from './card'
|
||||
import type { ConversationItem, SiteInfo } from '@/models/share'
|
||||
import { fetchConversations } from '@/service/share'
|
||||
import { fetchConversations as fetchUniversalConversations } from '@/service/universal-chat'
|
||||
|
||||
export type ISidebarProps = {
|
||||
copyRight: string
|
||||
|
@ -25,7 +24,6 @@ export type ISidebarProps = {
|
|||
isClearPinnedConversationList: boolean
|
||||
isInstalledApp: boolean
|
||||
installedAppId?: string
|
||||
isUniversalChat?: boolean
|
||||
siteInfo: SiteInfo
|
||||
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||
onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||
|
@ -50,7 +48,6 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||
isClearPinnedConversationList,
|
||||
isInstalledApp,
|
||||
installedAppId,
|
||||
isUniversalChat,
|
||||
siteInfo,
|
||||
onMoreLoaded,
|
||||
onPinnedMoreLoaded,
|
||||
|
@ -66,13 +63,7 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||
const [hasPinned, setHasPinned] = useState(false)
|
||||
|
||||
const checkHasPinned = async () => {
|
||||
let res: any
|
||||
if (isUniversalChat)
|
||||
res = await fetchUniversalConversations(undefined, true)
|
||||
|
||||
else
|
||||
res = await fetchConversations(isInstalledApp, installedAppId, undefined, true)
|
||||
|
||||
const res = await fetchConversations(isInstalledApp, installedAppId, undefined, true) as any
|
||||
setHasPinned(res.data.length > 0)
|
||||
}
|
||||
|
||||
|
@ -85,13 +76,13 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||
checkHasPinned()
|
||||
}, [controlUpdateList])
|
||||
|
||||
const maxListHeight = (isInstalledApp || isUniversalChat) ? 'max-h-[30vh]' : 'max-h-[40vh]'
|
||||
const maxListHeight = (isInstalledApp) ? 'max-h-[30vh]' : 'max-h-[40vh]'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
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',
|
||||
)
|
||||
}
|
||||
|
@ -125,7 +116,6 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||
isClearConversationList={isClearPinnedConversationList}
|
||||
isInstalledApp={isInstalledApp}
|
||||
installedAppId={installedAppId}
|
||||
isUniversalChat={isUniversalChat}
|
||||
onMoreLoaded={onPinnedMoreLoaded}
|
||||
isNoMore={isPinnedNoMore}
|
||||
isPinned={true}
|
||||
|
@ -149,7 +139,6 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||
isClearConversationList={isClearConversationList}
|
||||
isInstalledApp={isInstalledApp}
|
||||
installedAppId={installedAppId}
|
||||
isUniversalChat={isUniversalChat}
|
||||
onMoreLoaded={onMoreLoaded}
|
||||
isNoMore={isNoMore}
|
||||
isPinned={false}
|
||||
|
@ -160,11 +149,9 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||
</div>
|
||||
|
||||
</div>
|
||||
{!isUniversalChat && (
|
||||
<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>
|
||||
)}
|
||||
<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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import RenameModal from '../rename-modal'
|
|||
import Item from './item'
|
||||
import type { ConversationItem } from '@/models/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'
|
||||
|
||||
export type IListProps = {
|
||||
|
@ -20,7 +19,6 @@ export type IListProps = {
|
|||
onListChanged?: (newList: ConversationItem[]) => void
|
||||
isClearConversationList: boolean
|
||||
isInstalledApp: boolean
|
||||
isUniversalChat?: boolean
|
||||
installedAppId?: string
|
||||
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||
isNoMore: boolean
|
||||
|
@ -38,7 +36,6 @@ const List: FC<IListProps> = ({
|
|||
onListChanged,
|
||||
isClearConversationList,
|
||||
isInstalledApp,
|
||||
isUniversalChat,
|
||||
installedAppId,
|
||||
onMoreLoaded,
|
||||
isNoMore,
|
||||
|
@ -56,11 +53,7 @@ const List: FC<IListProps> = ({
|
|||
let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
|
||||
if (lastId === '-1')
|
||||
lastId = undefined
|
||||
let res: any
|
||||
if (isUniversalChat)
|
||||
res = await fetchUniversalConversations(lastId, isPinned)
|
||||
else
|
||||
res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
|
||||
const res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) as any
|
||||
const { data: conversations, has_more }: any = res
|
||||
onMoreLoaded({ data: conversations, has_more })
|
||||
}
|
||||
|
@ -93,11 +86,7 @@ const List: FC<IListProps> = ({
|
|||
setIsSaving()
|
||||
const currId = currentConversation.id
|
||||
try {
|
||||
if (isUniversalChat)
|
||||
await renameUniversalConversation(currId, newName)
|
||||
|
||||
else
|
||||
await renameConversation(isInstalledApp, installedAppId, currId, newName)
|
||||
await renameConversation(isInstalledApp, installedAppId, currId, newName)
|
||||
|
||||
Toast.notify({
|
||||
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-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 = [
|
||||
{
|
||||
id: 1,
|
||||
|
|
|
@ -36,45 +36,6 @@ const translation = {
|
|||
Programming: 'Programming',
|
||||
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
|
||||
|
|
|
@ -36,45 +36,6 @@ const translation = {
|
|||
Programming: 'Programação',
|
||||
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
|
||||
|
|
|
@ -36,45 +36,6 @@ const translation = {
|
|||
Programming: '编程',
|
||||
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
|
||||
|
|
|
@ -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…
Reference in New Issue
Block a user