chore: remove universal chat code (#2194)

This commit is contained in:
Joel 2024-01-25 11:47:35 +08:00 committed by GitHub
parent 77636945fb
commit bec998ab94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 10 additions and 1720 deletions

View File

@ -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)

View File

@ -16,7 +16,6 @@ export type ICardItemProps = {
onRemove: (id: string) => void
readonly?: boolean
}
// used in universal-chat
const CardItem: FC<ICardItemProps> = ({
className,
config,

View File

@ -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) {

View File

@ -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) {

View File

@ -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)

View File

@ -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);
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -1,3 +0,0 @@
.shadow {
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -58,7 +58,6 @@ export type IMainProps = {
isInstalledApp?: boolean
installedAppInfo?: InstalledApp
isSupportPlugin?: boolean
isUniversalChat?: boolean
}
const Main: FC<IMainProps> = ({

View File

@ -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>
)
}

View File

@ -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',

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }>
}