Unverified Commit 51d35926 authored by zxhlyh's avatar zxhlyh Committed by GitHub

chore: replace chat in web app (#2373)

parent 3f0c5153
'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import type { IMainProps } from '@/app/components/share/chat' import type { IMainProps } from '@/app/components/share/chat'
import Main from '@/app/components/share/chat' import ChatWithHistoryWrap from '@/app/components/base/chat/chat-with-history'
const Chat: FC<IMainProps> = () => { const Chat: FC<IMainProps> = () => {
return ( return (
<Main /> <ChatWithHistoryWrap />
) )
} }
......
import { useCallback, useEffect, useMemo } from 'react'
import Chat from '../chat'
import type {
ChatConfig,
OnSend,
} from '../types'
import { useChat } from '../chat/hooks'
import { useChatWithHistoryContext } from './context'
import Header from './header'
import ConfigPanel from './config-panel'
import {
fetchSuggestedQuestions,
getUrl,
} from '@/service/share'
const ChatWrapper = () => {
const {
appParams,
appPrevChatList,
currentConversationId,
currentConversationItem,
inputsForms,
newConversationInputs,
handleNewConversationCompleted,
isMobile,
isInstalledApp,
appId,
appMeta,
handleFeedback,
currentChatInstanceRef,
} = useChatWithHistoryContext()
const appConfig = useMemo(() => {
const config = appParams || {}
return {
...config,
supportFeedback: true,
} as ChatConfig
}, [appParams])
const {
chatList,
handleSend,
handleStop,
isResponsing,
suggestedQuestions,
} = useChat(
appConfig,
undefined,
appPrevChatList,
)
useEffect(() => {
if (currentChatInstanceRef.current)
currentChatInstanceRef.current.handleStop = handleStop
}, [])
const doSend: OnSend = useCallback((message, files) => {
const data: any = {
query: message,
inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs,
conversation_id: currentConversationId,
}
if (appConfig?.file_upload?.image.enabled && files?.length)
data.files = files
handleSend(
getUrl('chat-messages', isInstalledApp, appId || ''),
data,
{
onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId),
onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted,
isPublicAPI: !isInstalledApp,
},
)
}, [
appConfig,
currentConversationId,
currentConversationItem,
handleSend,
newConversationInputs,
handleNewConversationCompleted,
isInstalledApp,
appId,
])
const chatNode = useMemo(() => {
if (inputsForms.length) {
return (
<>
<Header
isMobile={isMobile}
title={currentConversationItem?.name || ''}
/>
{
!currentConversationId && (
<div className={`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`}>
<div className='mb-6' />
<ConfigPanel />
<div
className='my-6 h-[1px]'
style={{ background: 'linear-gradient(90deg, rgba(242, 244, 247, 0.00) 0%, #F2F4F7 49.17%, rgba(242, 244, 247, 0.00) 100%)' }}
/>
</div>
)
}
</>
)
}
return (
<Header
isMobile={isMobile}
title={currentConversationItem?.name || ''}
/>
)
}, [
currentConversationId,
inputsForms,
currentConversationItem,
isMobile,
])
return (
<Chat
config={appConfig}
chatList={chatList}
isResponsing={isResponsing}
chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[720px] ${isMobile && 'px-4'}`}
chatFooterClassName='pb-4'
chatFooterInnerClassName={`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`}
onSend={doSend}
onStopResponding={handleStop}
chatNode={chatNode}
allToolIcons={appMeta?.tool_icons || {}}
onFeedback={handleFeedback}
suggestedQuestions={suggestedQuestions}
/>
)
}
export default ChatWrapper
import { useTranslation } from 'react-i18next'
import { useChatWithHistoryContext } from '../context'
import { PortalSelect } from '@/app/components/base/select'
const Form = () => {
const { t } = useTranslation()
const {
inputsForms,
newConversationInputs,
handleNewConversationInputsChange,
isMobile,
} = useChatWithHistoryContext()
const handleFormChange = (variable: string, value: string) => {
handleNewConversationInputsChange({
...newConversationInputs,
[variable]: value,
})
}
const renderField = (form: any) => {
const {
label,
required,
max_length,
variable,
options,
} = form
if (form.type === 'text-input') {
return (
<input
className='grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none'
value={newConversationInputs[variable] || ''}
maxLength={max_length}
onChange={e => handleFormChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}
if (form.type === 'paragraph') {
return (
<textarea
value={newConversationInputs[variable]}
className='grow h-[104px] rounded-lg bg-gray-100 px-2.5 py-2 outline-none appearance-none resize-none'
onChange={e => handleFormChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}
return (
<PortalSelect
popupClassName='w-[200px]'
value={newConversationInputs[variable]}
items={options.map((option: string) => ({ value: option, name: option }))}
onSelect={item => handleFormChange(variable, item.value as string)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}
if (!inputsForms.length)
return null
return (
<div className='mb-4 py-2'>
{
inputsForms.map(form => (
<div
key={form.variable}
className={`flex mb-3 last-of-type:mb-0 text-sm text-gray-900 ${isMobile && '!flex-wrap'}`}
>
<div className={`shrink-0 mr-2 py-2 w-[128px] ${isMobile && '!w-full'}`}>{form.label}</div>
{renderField(form)}
</div>
))
}
</div>
)
}
export default Form
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useChatWithHistoryContext } from '../context'
import Form from './form'
import Button from '@/app/components/base/button'
import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication'
import { Edit02 } from '@/app/components/base/icons/src/vender/line/general'
import { Star06 } from '@/app/components/base/icons/src/vender/solid/shapes'
import { FootLogo } from '@/app/components/share/chat/welcome/massive-component'
const ConfigPanel = () => {
const { t } = useTranslation()
const {
appData,
inputsForms,
handleStartChat,
showConfigPanelBeforeChat,
isMobile,
} = useChatWithHistoryContext()
const [collapsed, setCollapsed] = useState(true)
const customConfig = appData?.custom_config
const site = appData?.site
return (
<div className='flex flex-col max-h-[80%] w-full max-w-[720px]'>
<div
className={`
grow rounded-xl overflow-y-auto
${showConfigPanelBeforeChat && 'border-[0.5px] border-gray-100 shadow-lg'}
${!showConfigPanelBeforeChat && collapsed && 'border border-indigo-100'}
${!showConfigPanelBeforeChat && !collapsed && 'border-[0.5px] border-gray-100 shadow-lg'}
`}
>
<div
className={`
flex flex-wrap px-6 py-4 rounded-t-xl bg-indigo-25
${isMobile && '!px-4 !py-3'}
`}
>
{
showConfigPanelBeforeChat && (
<>
<div className='flex items-center text-2xl font-semibold text-gray-800'>
{appData?.site.icon} {appData?.site.title}
</div>
{
appData?.site.description && (
<div className='mt-2 w-full text-sm text-gray-500'>
{appData?.site.description}
</div>
)
}
</>
)
}
{
!showConfigPanelBeforeChat && collapsed && (
<>
<Star06 className='mr-1 mt-1 w-4 h-4 text-indigo-600' />
<div className='grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'>
{t('share.chat.configStatusDes')}
</div>
<Button
className='shrink-0 px-2 py-0 h-6 bg-white text-xs font-medium text-primary-600 rounded-md'
onClick={() => setCollapsed(false)}
>
<Edit02 className='mr-1 w-3 h-3' />
{t('common.operation.edit')}
</Button>
</>
)
}
{
!showConfigPanelBeforeChat && !collapsed && (
<>
<Star06 className='mr-1 mt-1 w-4 h-4 text-indigo-600' />
<div className='grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'>
{t('share.chat.privatePromptConfigTitle')}
</div>
</>
)
}
</div>
{
!collapsed && !showConfigPanelBeforeChat && (
<div className='p-6 rounded-b-xl'>
<Form />
<div className={`pl-[136px] flex items-center ${isMobile && '!pl-0'}`}>
<Button
type='primary'
className='mr-2 text-sm font-medium'
onClick={handleStartChat}
>
{t('common.operation.save')}
</Button>
<Button
className='text-sm font-medium'
onClick={() => setCollapsed(true)}
>
{t('common.operation.cancel')}
</Button>
</div>
</div>
)
}
{
showConfigPanelBeforeChat && (
<div className='p-6 rounded-b-xl'>
<Form />
<Button
className={`px-4 py-0 h-9 ${inputsForms.length && !isMobile && 'ml-[136px]'}`}
type='primary'
onClick={handleStartChat}
>
<MessageDotsCircle className='mr-2 w-4 h-4 text-white' />
{t('share.chat.startChat')}
</Button>
</div>
)
}
</div>
{
showConfigPanelBeforeChat && (site || customConfig) && (
<div className='mt-4 flex flex-wrap justify-between items-center py-2 text-xs text-gray-400'>
{site?.privacy_policy
? <div className={`flex items-center ${isMobile && 'w-full justify-end'}`}>{t('share.chat.privacyPolicyLeft')}
<a
className='text-gray-500'
href={site?.privacy_policy}
target='_blank' rel='noopener noreferrer'>{t('share.chat.privacyPolicyMiddle')}</a>
{t('share.chat.privacyPolicyRight')}
</div>
: <div>
</div>}
{
customConfig?.remove_webapp_brand
? null
: (
<div className={`flex items-center justify-end ${isMobile && 'w-full'}`}>
<a className='flex items-center pr-3 space-x-3' href="https://dify.ai/" target="_blank">
<span className='uppercase'>{t('share.chat.powerBy')}</span>
{
customConfig?.replace_webapp_logo
? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
: <FootLogo />
}
</a>
</div>
)
}
</div>
)
}
</div>
)
}
export default ConfigPanel
'use client'
import type { RefObject } from 'react'
import { createContext, useContext } from 'use-context-selector'
import type {
Callback,
ChatConfig,
ChatItem,
Feedback,
} from '../types'
import type {
AppConversationData,
AppData,
AppMeta,
ConversationItem,
} from '@/models/share'
export type ChatWithHistoryContextValue = {
appInfoLoading?: boolean
appMeta?: AppMeta
appData?: AppData
appParams?: ChatConfig
appChatListDataLoading?: boolean
currentConversationId: string
currentConversationItem?: ConversationItem
appPrevChatList: ChatItem[]
pinnedConversationList: AppConversationData['data']
conversationList: AppConversationData['data']
showConfigPanelBeforeChat: boolean
newConversationInputs: Record<string, any>
handleNewConversationInputsChange: (v: Record<string, any>) => void
inputsForms: any[]
handleNewConversation: () => void
handleStartChat: () => void
handleChangeConversation: (conversationId: string) => void
handlePinConversation: (conversationId: string) => void
handleUnpinConversation: (conversationId: string) => void
handleDeleteConversation: (conversationId: string, callback: Callback) => void
conversationRenaming: boolean
handleRenameConversation: (conversationId: string, newName: string, callback: Callback) => void
handleNewConversationCompleted: (newConversationId: string) => void
chatShouldReloadKey: string
isMobile: boolean
isInstalledApp: boolean
appId?: string
handleFeedback: (messageId: string, feedback: Feedback) => void
currentChatInstanceRef: RefObject<{ handleStop: () => void }>
}
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
currentConversationId: '',
appPrevChatList: [],
pinnedConversationList: [],
conversationList: [],
showConfigPanelBeforeChat: false,
newConversationInputs: {},
handleNewConversationInputsChange: () => {},
inputsForms: [],
handleNewConversation: () => {},
handleStartChat: () => {},
handleChangeConversation: () => {},
handlePinConversation: () => {},
handleUnpinConversation: () => {},
handleDeleteConversation: () => {},
conversationRenaming: false,
handleRenameConversation: () => {},
handleNewConversationCompleted: () => {},
chatShouldReloadKey: '',
isMobile: false,
isInstalledApp: false,
handleFeedback: () => {},
currentChatInstanceRef: { current: { handleStop: () => {} } },
})
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)
import { useState } from 'react'
import { useChatWithHistoryContext } from './context'
import Sidebar from './sidebar'
import AppIcon from '@/app/components/base/app-icon'
import {
Edit05,
Menu01,
} from '@/app/components/base/icons/src/vender/line/general'
const HeaderInMobile = () => {
const {
appData,
handleNewConversation,
} = useChatWithHistoryContext()
const [showSidebar, setShowSidebar] = useState(false)
return (
<>
<div className='shrink-0 flex items-center px-3 h-[44px] border-b-[0.5px] border-b-gray-200'>
<div
className='shrink-0 flex items-center justify-center w-8 h-8 rounded-lg'
onClick={() => setShowSidebar(true)}
>
<Menu01 className='w-4 h-4 text-gray-700' />
</div>
<div className='grow flex justify-center items-center px-3'>
<AppIcon
className='mr-2'
size='tiny'
icon={appData?.site.icon}
background={appData?.site.icon_background}
/>
<div className='py-1 text-base font-semibold text-gray-800 truncate'>
{appData?.site.title}
</div>
</div>
<div
className='shrink-0 flex items-center justify-center w-8 h-8 rounded-lg'
onClick={handleNewConversation}
>
<Edit05 className='w-4 h-4 text-gray-700' />
</div>
</div>
{
showSidebar && (
<div className='fixed inset-0 z-50'
style={{ backgroundColor: 'rgba(35, 56, 118, 0.2)' }}
onClick={() => setShowSidebar(false)}
>
<div className='inline-block h-full bg-white' onClick={e => e.stopPropagation()}>
<Sidebar />
</div>
</div>
)
}
</>
)
}
export default HeaderInMobile
import type { FC } from 'react'
import { memo } from 'react'
type HeaderProps = {
title: string
isMobile: boolean
}
const Header: FC<HeaderProps> = ({
title,
isMobile,
}) => {
return (
<div
className={`
sticky top-0 flex items-center px-8 h-16 bg-white/80 text-base font-medium
text-gray-900 border-b-[0.5px] border-b-gray-100 backdrop-blur-md z-10
${isMobile && '!h-12'}
`}
>
{title}
</div>
)
}
export default memo(Header)
This diff is collapsed.
import type { FC } from 'react'
import {
useEffect,
useState,
} from 'react'
import { useAsyncEffect } from 'ahooks'
import {
ChatWithHistoryContext,
useChatWithHistoryContext,
} from './context'
import { useChatWithHistory } from './hooks'
import Sidebar from './sidebar'
import HeaderInMobile from './header-in-mobile'
import ConfigPanel from './config-panel'
import ChatWrapper from './chat-wrapper'
import type { InstalledApp } from '@/models/explore'
import Loading from '@/app/components/base/loading'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { checkOrSetAccessToken } from '@/app/components/share/utils'
type ChatWithHistoryProps = {
className?: string
}
const ChatWithHistory: FC<ChatWithHistoryProps> = ({
className,
}) => {
const {
appData,
appInfoLoading,
appPrevChatList,
showConfigPanelBeforeChat,
appChatListDataLoading,
chatShouldReloadKey,
isMobile,
} = useChatWithHistoryContext()
const chatReady = (!showConfigPanelBeforeChat || !!appPrevChatList.length)
const customConfig = appData?.custom_config
const site = appData?.site
useEffect(() => {
if (site) {
if (customConfig)
document.title = `${site.title}`
else
document.title = `${site.title} - Powered by Dify`
}
}, [site, customConfig])
if (appInfoLoading) {
return (
<Loading type='app' />
)
}
return (
<div className={`h-full flex bg-white ${className} ${isMobile && 'flex-col'}`}>
{
!isMobile && (
<Sidebar />
)
}
{
isMobile && (
<HeaderInMobile />
)
}
<div className={`grow overflow-hidden ${showConfigPanelBeforeChat && !appPrevChatList.length && 'flex items-center justify-center'}`}>
{
showConfigPanelBeforeChat && !appChatListDataLoading && !appPrevChatList.length && (
<div className={`flex w-full items-center justify-center h-full ${isMobile && 'px-4'}`}>
<ConfigPanel />
</div>
)
}
{
appChatListDataLoading && chatReady && (
<Loading type='app' />
)
}
{
chatReady && !appChatListDataLoading && (
<ChatWrapper key={chatShouldReloadKey} />
)
}
</div>
</div>
)
}
export type ChatWithHistoryWrapProps = {
installedAppInfo?: InstalledApp
className?: string
}
const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
installedAppInfo,
className,
}) => {
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const {
appInfoLoading,
appData,
appParams,
appMeta,
appChatListDataLoading,
currentConversationId,
currentConversationItem,
appPrevChatList,
pinnedConversationList,
conversationList,
showConfigPanelBeforeChat,
newConversationInputs,
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handlePinConversation,
handleUnpinConversation,
handleDeleteConversation,
conversationRenaming,
handleRenameConversation,
handleNewConversationCompleted,
chatShouldReloadKey,
isInstalledApp,
appId,
handleFeedback,
currentChatInstanceRef,
} = useChatWithHistory(installedAppInfo)
return (
<ChatWithHistoryContext.Provider value={{
appInfoLoading,
appData,
appParams,
appMeta,
appChatListDataLoading,
currentConversationId,
currentConversationItem,
appPrevChatList,
pinnedConversationList,
conversationList,
showConfigPanelBeforeChat,
newConversationInputs,
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handlePinConversation,
handleUnpinConversation,
handleDeleteConversation,
conversationRenaming,
handleRenameConversation,
handleNewConversationCompleted,
chatShouldReloadKey,
isMobile,
isInstalledApp,
appId,
handleFeedback,
currentChatInstanceRef,
}}>
<ChatWithHistory className={className} />
</ChatWithHistoryContext.Provider>
)
}
const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
installedAppInfo,
className,
}) => {
const [inited, setInited] = useState(false)
useAsyncEffect(async () => {
if (!inited) {
if (!installedAppInfo)
await checkOrSetAccessToken()
setInited(true)
}
}, [])
if (!inited)
return null
return (
<ChatWithHistoryWrap
installedAppInfo={installedAppInfo}
className={className}
/>
)
}
export default ChatWithHistoryWrapWithCheckToken
import {
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useChatWithHistoryContext } from '../context'
import List from './list'
import AppIcon from '@/app/components/base/app-icon'
import Button from '@/app/components/base/button'
import { Edit05 } from '@/app/components/base/icons/src/vender/line/general'
import type { ConversationItem } from '@/models/share'
import Confirm from '@/app/components/base/confirm'
import RenameModal from '@/app/components/share/chat/sidebar/rename-modal'
const Sidebar = () => {
const { t } = useTranslation()
const {
appData,
pinnedConversationList,
conversationList,
handleNewConversation,
currentConversationId,
handleChangeConversation,
handlePinConversation,
handleUnpinConversation,
conversationRenaming,
handleRenameConversation,
handleDeleteConversation,
isMobile,
} = useChatWithHistoryContext()
const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
const [showRename, setShowRename] = useState<ConversationItem | null>(null)
const handleOperate = useCallback((type: string, item: ConversationItem) => {
if (type === 'pin')
handlePinConversation(item.id)
if (type === 'unpin')
handleUnpinConversation(item.id)
if (type === 'delete')
setShowConfirm(item)
if (type === 'rename')
setShowRename(item)
}, [handlePinConversation, handleUnpinConversation])
const handleCancelConfirm = useCallback(() => {
setShowConfirm(null)
}, [])
const handleDelete = useCallback(() => {
if (showConfirm)
handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
}, [showConfirm, handleDeleteConversation, handleCancelConfirm])
const handleCancelRename = useCallback(() => {
setShowRename(null)
}, [])
const handleRename = useCallback((newName: string) => {
if (showRename)
handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
}, [showRename, handleRenameConversation, handleCancelRename])
return (
<div className='shrink-0 h-full flex flex-col w-[240px] border-r border-r-gray-100'>
{
!isMobile && (
<div className='shrink-0 flex p-4'>
<AppIcon
className='mr-3'
size='small'
icon={appData?.site.icon}
background={appData?.site.icon_background}
/>
<div className='py-1 text-base font-semibold text-gray-800'>
{appData?.site.title}
</div>
</div>
)
}
<div className='shrink-0 p-4'>
<Button
className='justify-start px-3 py-0 w-full h-9 text-sm font-medium text-primary-600'
onClick={handleNewConversation}
>
<Edit05 className='mr-2 w-4 h-4' />
{t('share.chat.newChat')}
</Button>
</div>
<div className='grow px-4 py-2 overflow-y-auto'>
{
!!pinnedConversationList.length && (
<div className='mb-4'>
<List
isPin
title={t('share.chat.pinnedTitle') || ''}
list={pinnedConversationList}
onChangeConversation={handleChangeConversation}
onOperate={handleOperate}
currentConversationId={currentConversationId}
/>
</div>
)
}
{
!!conversationList.length && (
<List
title={(pinnedConversationList.length && t('share.chat.unpinnedTitle')) || ''}
list={conversationList}
onChangeConversation={handleChangeConversation}
onOperate={handleOperate}
currentConversationId={currentConversationId}
/>
)
}
</div>
<div className='px-4 pb-4 text-xs text-gray-400'>
© {appData?.site.copyright || appData?.site.title} {(new Date()).getFullYear()}
</div>
{!!showConfirm && (
<Confirm
title={t('share.chat.deleteConversation.title')}
content={t('share.chat.deleteConversation.content') || ''}
isShow
onClose={handleCancelConfirm}
onCancel={handleCancelConfirm}
onConfirm={handleDelete}
/>
)}
{showRename && (
<RenameModal
isShow
onClose={handleCancelRename}
saveLoading={conversationRenaming}
name={showRename?.name || ''}
onSave={handleRename}
/>
)}
</div>
)
}
export default Sidebar
import type { FC } from 'react'
import {
memo,
useRef,
} from 'react'
import { useHover } from 'ahooks'
import type { ConversationItem } from '@/models/share'
import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication'
import ItemOperation from '@/app/components/explore/item-operation'
type ItemProps = {
isPin?: boolean
item: ConversationItem
onOperate: (type: string, item: ConversationItem) => void
onChangeConversation: (conversationId: string) => void
currentConversationId: string
}
const Item: FC<ItemProps> = ({
isPin,
item,
onOperate,
onChangeConversation,
currentConversationId,
}) => {
const ref = useRef(null)
const isHovering = useHover(ref)
return (
<div
ref={ref}
key={item.id}
className={`
flex mb-0.5 last-of-type:mb-0 py-1.5 pl-3 pr-1.5 text-sm font-medium text-gray-700
rounded-lg cursor-pointer hover:bg-gray-50 group
${currentConversationId === item.id && 'text-primary-600 bg-primary-50'}
`}
onClick={() => onChangeConversation(item.id)}
>
<MessageDotsCircle className={`shrink-0 mt-1 mr-2 w-4 h-4 text-gray-400 ${currentConversationId === item.id && 'text-primary-600'}`} />
<div className='grow py-0.5 break-all' title={item.name}>{item.name}</div>
{item.id !== '' && (
<div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>
<ItemOperation
isPinned={!!isPin}
isItemHovering={isHovering}
togglePin={() => onOperate(isPin ? 'unpin' : 'pin', item)}
isShowDelete
isShowRenameConversation
onRenameConversation={() => onOperate('rename', item)}
onDelete={() => onOperate('delete', item)}
/>
</div>
)}
</div>
)
}
export default memo(Item)
import type { FC } from 'react'
import Item from './item'
import type { ConversationItem } from '@/models/share'
type ListProps = {
isPin?: boolean
title?: string
list: ConversationItem[]
onOperate: (type: string, item: ConversationItem) => void
onChangeConversation: (conversationId: string) => void
currentConversationId: string
}
const List: FC<ListProps> = ({
isPin,
title,
list,
onOperate,
onChangeConversation,
currentConversationId,
}) => {
return (
<div>
{
title && (
<div className='mb-0.5 px-3 h-[26px] text-xs font-medium text-gray-500'>
{title}
</div>
)
}
{
list.map(item => (
<Item
key={item.id}
isPin={isPin}
item={item}
onOperate={onOperate}
onChangeConversation={onChangeConversation}
currentConversationId={currentConversationId}
/>
))
}
</div>
)
}
export default List
...@@ -82,7 +82,7 @@ const Answer: FC<AnswerProps> = ({ ...@@ -82,7 +82,7 @@ const Answer: FC<AnswerProps> = ({
) )
} }
{ {
hasAgentThoughts && !content && ( hasAgentThoughts && (
<AgentContent item={item} /> <AgentContent item={item} />
) )
} }
......
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { ChatItem } from '../../types' import type { ChatItem } from '../../types'
import { useCurrentAnswerIsResponsing } from '../hooks' import { useCurrentAnswerIsResponsing } from '../hooks'
import { useChatContext } from '../context' import { useChatContext } from '../context'
...@@ -8,6 +9,11 @@ import { MessageFast } from '@/app/components/base/icons/src/vender/solid/commun ...@@ -8,6 +9,11 @@ import { MessageFast } from '@/app/components/base/icons/src/vender/solid/commun
import AudioBtn from '@/app/components/base/audio-btn' import AudioBtn from '@/app/components/base/audio-btn'
import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn' import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal' import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
import {
ThumbsDown,
ThumbsUp,
} from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import TooltipPlus from '@/app/components/base/tooltip-plus'
type OperationProps = { type OperationProps = {
item: ChatItem item: ChatItem
...@@ -19,11 +25,13 @@ const Operation: FC<OperationProps> = ({ ...@@ -19,11 +25,13 @@ const Operation: FC<OperationProps> = ({
question, question,
index, index,
}) => { }) => {
const { t } = useTranslation()
const { const {
config, config,
onAnnotationAdded, onAnnotationAdded,
onAnnotationEdited, onAnnotationEdited,
onAnnotationRemoved, onAnnotationRemoved,
onFeedback,
} = useChatContext() } = useChatContext()
const [isShowReplyModal, setIsShowReplyModal] = useState(false) const [isShowReplyModal, setIsShowReplyModal] = useState(false)
const responsing = useCurrentAnswerIsResponsing(item.id) const responsing = useCurrentAnswerIsResponsing(item.id)
...@@ -32,8 +40,18 @@ const Operation: FC<OperationProps> = ({ ...@@ -32,8 +40,18 @@ const Operation: FC<OperationProps> = ({
isOpeningStatement, isOpeningStatement,
content, content,
annotation, annotation,
feedback,
} = item } = item
const hasAnnotation = !!annotation?.id const hasAnnotation = !!annotation?.id
const [localFeedback, setLocalFeedback] = useState(feedback)
const handleFeedback = async (rating: 'like' | 'dislike' | null) => {
if (!config?.supportFeedback || !onFeedback)
return
await onFeedback?.(id, { rating })
setLocalFeedback({ rating })
}
return ( return (
<div className='absolute top-[-14px] right-[-14px] flex justify-end gap-1'> <div className='absolute top-[-14px] right-[-14px] flex justify-end gap-1'>
...@@ -90,6 +108,53 @@ const Operation: FC<OperationProps> = ({ ...@@ -90,6 +108,53 @@ const Operation: FC<OperationProps> = ({
</div> </div>
) )
} }
{
config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement && (
<div className='hidden group-hover:flex ml-1 shrink-0 items-center px-0.5 bg-white border-[0.5px] border-gray-100 shadow-md text-gray-500 rounded-lg'>
<TooltipPlus popupContent={t('appDebug.operation.agree')}>
<div
className='flex items-center justify-center mr-0.5 w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer'
onClick={() => handleFeedback('like')}
>
<ThumbsUp className='w-4 h-4' />
</div>
</TooltipPlus>
<TooltipPlus popupContent={t('appDebug.operation.disagree')}>
<div
className='flex items-center justify-center w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer'
onClick={() => handleFeedback('dislike')}
>
<ThumbsDown className='w-4 h-4' />
</div>
</TooltipPlus>
</div>
)
}
{
config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement && (
<TooltipPlus popupContent={localFeedback.rating === 'like' ? t('appDebug.operation.cancelAgree') : t('appDebug.operation.cancelDisagree')}>
<div
className={`
flex items-center justify-center w-7 h-7 rounded-[10px] border-[2px] border-white cursor-pointer
${localFeedback.rating === 'like' && 'bg-blue-50 text-blue-600'}
${localFeedback.rating === 'dislike' && 'bg-red-100 text-red-600'}
`}
onClick={() => handleFeedback(null)}
>
{
localFeedback.rating === 'like' && (
<ThumbsUp className='w-4 h-4' />
)
}
{
localFeedback.rating === 'dislike' && (
<ThumbsDown className='w-4 h-4' />
)
}
</div>
</TooltipPlus>
)
}
</div> </div>
) )
} }
......
...@@ -19,7 +19,7 @@ const SuggestedQuestions: FC<SuggestedQuestionsProps> = ({ ...@@ -19,7 +19,7 @@ const SuggestedQuestions: FC<SuggestedQuestionsProps> = ({
return ( return (
<div className='flex flex-wrap'> <div className='flex flex-wrap'>
{suggestedQuestions.map((question, index) => ( {suggestedQuestions.filter(q => !!q && q.trim()).map((question, index) => (
<div <div
key={index} key={index}
className='mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer' className='mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer'
......
...@@ -15,6 +15,7 @@ export type ChatContextValue = Pick<ChatProps, 'config' ...@@ -15,6 +15,7 @@ export type ChatContextValue = Pick<ChatProps, 'config'
| 'onAnnotationEdited' | 'onAnnotationEdited'
| 'onAnnotationAdded' | 'onAnnotationAdded'
| 'onAnnotationRemoved' | 'onAnnotationRemoved'
| 'onFeedback'
> >
const ChatContext = createContext<ChatContextValue>({ const ChatContext = createContext<ChatContextValue>({
...@@ -38,6 +39,7 @@ export const ChatContextProvider = ({ ...@@ -38,6 +39,7 @@ export const ChatContextProvider = ({
onAnnotationEdited, onAnnotationEdited,
onAnnotationAdded, onAnnotationAdded,
onAnnotationRemoved, onAnnotationRemoved,
onFeedback,
}: ChatContextProviderProps) => { }: ChatContextProviderProps) => {
return ( return (
<ChatContext.Provider value={{ <ChatContext.Provider value={{
...@@ -52,6 +54,7 @@ export const ChatContextProvider = ({ ...@@ -52,6 +54,7 @@ export const ChatContextProvider = ({
onAnnotationEdited, onAnnotationEdited,
onAnnotationAdded, onAnnotationAdded,
onAnnotationRemoved, onAnnotationRemoved,
onFeedback,
}}> }}>
{children} {children}
</ChatContext.Provider> </ChatContext.Provider>
......
...@@ -5,7 +5,7 @@ import { ...@@ -5,7 +5,7 @@ import {
useState, useState,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { produce } from 'immer' import { produce, setAutoFreeze } from 'immer'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { import type {
ChatConfig, ChatConfig,
...@@ -23,8 +23,10 @@ import type { Annotation } from '@/models/log' ...@@ -23,8 +23,10 @@ import type { Annotation } from '@/models/log'
type GetAbortController = (abortController: AbortController) => void type GetAbortController = (abortController: AbortController) => void
type SendCallback = { type SendCallback = {
onGetConvesationMessages: (conversationId: string, getAbortController: GetAbortController) => Promise<any> onGetConvesationMessages?: (conversationId: string, getAbortController: GetAbortController) => Promise<any>
onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any> onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any>
onConversationComplete?: (conversationId: string) => void
isPublicAPI?: boolean
} }
export const useCheckPromptVariables = () => { export const useCheckPromptVariables = () => {
...@@ -67,7 +69,7 @@ export const useCheckPromptVariables = () => { ...@@ -67,7 +69,7 @@ export const useCheckPromptVariables = () => {
} }
export const useChat = ( export const useChat = (
config: ChatConfig, config?: ChatConfig,
promptVariablesConfig?: { promptVariablesConfig?: {
inputs: Inputs inputs: Inputs
promptVariables: PromptVariable[] promptVariables: PromptVariable[]
...@@ -90,10 +92,17 @@ export const useChat = ( ...@@ -90,10 +92,17 @@ export const useChat = (
const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null) const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
const checkPromptVariables = useCheckPromptVariables() const checkPromptVariables = useCheckPromptVariables()
useEffect(() => {
setAutoFreeze(false)
return () => {
setAutoFreeze(true)
}
}, [])
const handleUpdateChatList = useCallback((newChatList: ChatItem[]) => { const handleUpdateChatList = useCallback((newChatList: ChatItem[]) => {
setChatList(newChatList) setChatList(newChatList)
chatListRef.current = newChatList chatListRef.current = newChatList
}, []) }, [setChatList])
const handleResponsing = useCallback((isResponsing: boolean) => { const handleResponsing = useCallback((isResponsing: boolean) => {
setIsResponsing(isResponsing) setIsResponsing(isResponsing)
isResponsingRef.current = isResponsing isResponsingRef.current = isResponsing
...@@ -103,22 +112,19 @@ export const useChat = ( ...@@ -103,22 +112,19 @@ export const useChat = (
return replaceStringWithValues(str, promptVariablesConfig?.promptVariables || [], promptVariablesConfig?.inputs || {}) return replaceStringWithValues(str, promptVariablesConfig?.promptVariables || [], promptVariablesConfig?.inputs || {})
}, [promptVariablesConfig?.inputs, promptVariablesConfig?.promptVariables]) }, [promptVariablesConfig?.inputs, promptVariablesConfig?.promptVariables])
useEffect(() => { useEffect(() => {
if (config.opening_statement && !chatList.length) { if (config?.opening_statement && chatListRef.current.filter(item => item.isOpeningStatement).length === 0) {
handleUpdateChatList([{ handleUpdateChatList([
id: `${Date.now()}`, {
content: getIntroduction(config.opening_statement), id: `${Date.now()}`,
isAnswer: true, content: getIntroduction(config.opening_statement),
isOpeningStatement: true, isAnswer: true,
suggestedQuestions: config.suggested_questions, isOpeningStatement: true,
}]) suggestedQuestions: config.suggested_questions,
},
...chatListRef.current,
])
} }
}, [ }, [])
config.opening_statement,
config.suggested_questions,
getIntroduction,
chatList,
handleUpdateChatList,
])
const handleStop = useCallback(() => { const handleStop = useCallback(() => {
hasStopResponded.current = true hasStopResponded.current = true
...@@ -136,7 +142,7 @@ export const useChat = ( ...@@ -136,7 +142,7 @@ export const useChat = (
const handleRestart = useCallback(() => { const handleRestart = useCallback(() => {
handleStop() handleStop()
connversationId.current = '' connversationId.current = ''
const newChatList = config.opening_statement const newChatList = config?.opening_statement
? [{ ? [{
id: `${Date.now()}`, id: `${Date.now()}`,
content: config.opening_statement, content: config.opening_statement,
...@@ -181,6 +187,8 @@ export const useChat = ( ...@@ -181,6 +187,8 @@ export const useChat = (
{ {
onGetConvesationMessages, onGetConvesationMessages,
onGetSuggestedQuestions, onGetSuggestedQuestions,
onConversationComplete,
isPublicAPI,
}: SendCallback, }: SendCallback,
) => { ) => {
setSuggestQuestions([]) setSuggestQuestions([])
...@@ -248,6 +256,7 @@ export const useChat = ( ...@@ -248,6 +256,7 @@ export const useChat = (
body: bodyParams, body: bodyParams,
}, },
{ {
isPublicAPI,
getAbortController: (abortController) => { getAbortController: (abortController) => {
abortControllerRef.current = abortController abortControllerRef.current = abortController
}, },
...@@ -286,7 +295,10 @@ export const useChat = ( ...@@ -286,7 +295,10 @@ export const useChat = (
if (hasError) if (hasError)
return return
if (connversationId.current && !hasStopResponded.current) { if (onConversationComplete)
onConversationComplete(connversationId.current)
if (connversationId.current && !hasStopResponded.current && onGetConvesationMessages) {
const { data }: any = await onGetConvesationMessages( const { data }: any = await onGetConvesationMessages(
connversationId.current, connversationId.current,
newAbortController => conversationMessagesAbortControllerRef.current = newAbortController, newAbortController => conversationMessagesAbortControllerRef.current = newAbortController,
...@@ -315,7 +327,7 @@ export const useChat = ( ...@@ -315,7 +327,7 @@ export const useChat = (
}) })
handleUpdateChatList(newChatList) handleUpdateChatList(newChatList)
} }
if (config.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) { if (config?.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
const { data }: any = await onGetSuggestedQuestions( const { data }: any = await onGetSuggestedQuestions(
responseItem.id, responseItem.id,
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController, newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
...@@ -409,7 +421,7 @@ export const useChat = ( ...@@ -409,7 +421,7 @@ export const useChat = (
return true return true
}, [ }, [
checkPromptVariables, checkPromptVariables,
config.suggested_questions_after_answer, config?.suggested_questions_after_answer,
updateCurrentQA, updateCurrentQA,
t, t,
notify, notify,
...@@ -419,7 +431,7 @@ export const useChat = ( ...@@ -419,7 +431,7 @@ export const useChat = (
]) ])
const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => { const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
setChatList(chatListRef.current.map((item, i) => { handleUpdateChatList(chatListRef.current.map((item, i) => {
if (i === index - 1) { if (i === index - 1) {
return { return {
...item, ...item,
...@@ -438,9 +450,9 @@ export const useChat = ( ...@@ -438,9 +450,9 @@ export const useChat = (
} }
return item return item
})) }))
}, []) }, [handleUpdateChatList])
const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => { const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => {
setChatList(chatListRef.current.map((item, i) => { handleUpdateChatList(chatListRef.current.map((item, i) => {
if (i === index - 1) { if (i === index - 1) {
return { return {
...item, ...item,
...@@ -468,9 +480,9 @@ export const useChat = ( ...@@ -468,9 +480,9 @@ export const useChat = (
} }
return item return item
})) }))
}, []) }, [handleUpdateChatList])
const handleAnnotationRemoved = useCallback((index: number) => { const handleAnnotationRemoved = useCallback((index: number) => {
setChatList(chatListRef.current.map((item, i) => { handleUpdateChatList(chatListRef.current.map((item, i) => {
if (i === index) { if (i === index) {
return { return {
...item, ...item,
...@@ -483,7 +495,7 @@ export const useChat = ( ...@@ -483,7 +495,7 @@ export const useChat = (
} }
return item return item
})) }))
}, []) }, [handleUpdateChatList])
return { return {
chatList, chatList,
......
...@@ -12,6 +12,7 @@ import { useThrottleEffect } from 'ahooks' ...@@ -12,6 +12,7 @@ import { useThrottleEffect } from 'ahooks'
import type { import type {
ChatConfig, ChatConfig,
ChatItem, ChatItem,
Feedback,
OnSend, OnSend,
} from '../types' } from '../types'
import Question from './question' import Question from './question'
...@@ -32,7 +33,9 @@ export type ChatProps = { ...@@ -32,7 +33,9 @@ export type ChatProps = {
noChatInput?: boolean noChatInput?: boolean
onSend?: OnSend onSend?: OnSend
chatContainerclassName?: string chatContainerclassName?: string
chatContainerInnerClassName?: string
chatFooterClassName?: string chatFooterClassName?: string
chatFooterInnerClassName?: string
suggestedQuestions?: string[] suggestedQuestions?: string[]
showPromptLog?: boolean showPromptLog?: boolean
questionIcon?: ReactNode questionIcon?: ReactNode
...@@ -41,6 +44,8 @@ export type ChatProps = { ...@@ -41,6 +44,8 @@ export type ChatProps = {
onAnnotationEdited?: (question: string, answer: string, index: number) => void onAnnotationEdited?: (question: string, answer: string, index: number) => void
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void
onAnnotationRemoved?: (index: number) => void onAnnotationRemoved?: (index: number) => void
chatNode?: ReactNode
onFeedback?: (messageId: string, feedback: Feedback) => void
} }
const Chat: FC<ChatProps> = ({ const Chat: FC<ChatProps> = ({
config, config,
...@@ -51,7 +56,9 @@ const Chat: FC<ChatProps> = ({ ...@@ -51,7 +56,9 @@ const Chat: FC<ChatProps> = ({
onStopResponding, onStopResponding,
noChatInput, noChatInput,
chatContainerclassName, chatContainerclassName,
chatContainerInnerClassName,
chatFooterClassName, chatFooterClassName,
chatFooterInnerClassName,
suggestedQuestions, suggestedQuestions,
showPromptLog, showPromptLog,
questionIcon, questionIcon,
...@@ -60,10 +67,14 @@ const Chat: FC<ChatProps> = ({ ...@@ -60,10 +67,14 @@ const Chat: FC<ChatProps> = ({
onAnnotationAdded, onAnnotationAdded,
onAnnotationEdited, onAnnotationEdited,
onAnnotationRemoved, onAnnotationRemoved,
chatNode,
onFeedback,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const chatContainerRef = useRef<HTMLDivElement>(null) const chatContainerRef = useRef<HTMLDivElement>(null)
const chatContainerInnerRef = useRef<HTMLDivElement>(null)
const chatFooterRef = useRef<HTMLDivElement>(null) const chatFooterRef = useRef<HTMLDivElement>(null)
const chatFooterInnerRef = useRef<HTMLDivElement>(null)
const handleScrolltoBottom = () => { const handleScrolltoBottom = () => {
if (chatContainerRef.current) if (chatContainerRef.current)
...@@ -75,6 +86,9 @@ const Chat: FC<ChatProps> = ({ ...@@ -75,6 +86,9 @@ const Chat: FC<ChatProps> = ({
if (chatContainerRef.current && chatFooterRef.current) if (chatContainerRef.current && chatFooterRef.current)
chatFooterRef.current.style.width = `${chatContainerRef.current.clientWidth}px` chatFooterRef.current.style.width = `${chatContainerRef.current.clientWidth}px`
if (chatContainerInnerRef.current && chatFooterInnerRef.current)
chatFooterInnerRef.current.style.width = `${chatContainerInnerRef.current.clientWidth}px`
}, [chatList], { wait: 500 }) }, [chatList], { wait: 500 })
useEffect(() => { useEffect(() => {
...@@ -111,32 +125,39 @@ const Chat: FC<ChatProps> = ({ ...@@ -111,32 +125,39 @@ const Chat: FC<ChatProps> = ({
onAnnotationAdded={onAnnotationAdded} onAnnotationAdded={onAnnotationAdded}
onAnnotationEdited={onAnnotationEdited} onAnnotationEdited={onAnnotationEdited}
onAnnotationRemoved={onAnnotationRemoved} onAnnotationRemoved={onAnnotationRemoved}
onFeedback={onFeedback}
> >
<div className='relative h-full'> <div className='relative h-full'>
<div <div
ref={chatContainerRef} ref={chatContainerRef}
className={`relative h-full overflow-y-auto ${chatContainerclassName}`} className={`relative h-full overflow-y-auto ${chatContainerclassName}`}
> >
{ {chatNode}
chatList.map((item, index) => { <div
if (item.isAnswer) { ref={chatContainerInnerRef}
className={`${chatContainerInnerClassName}`}
>
{
chatList.map((item, index) => {
if (item.isAnswer) {
return (
<Answer
key={item.id}
item={item}
question={chatList[index - 1]?.content}
index={index}
/>
)
}
return ( return (
<Answer <Question
key={item.id} key={item.id}
item={item} item={item}
question={chatList[index - 1]?.content}
index={index}
/> />
) )
} })
return ( }
<Question </div>
key={item.id}
item={item}
/>
)
})
}
</div> </div>
<div <div
className={`absolute bottom-0 ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`} className={`absolute bottom-0 ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`}
...@@ -145,33 +166,38 @@ const Chat: FC<ChatProps> = ({ ...@@ -145,33 +166,38 @@ const Chat: FC<ChatProps> = ({
background: 'linear-gradient(0deg, #F9FAFB 40%, rgba(255, 255, 255, 0.00) 100%)', background: 'linear-gradient(0deg, #F9FAFB 40%, rgba(255, 255, 255, 0.00) 100%)',
}} }}
> >
{ <div
!noStopResponding && isResponsing && ( ref={chatFooterInnerRef}
<div className='flex justify-center mb-2'> className={`${chatFooterInnerClassName}`}
<Button className='py-0 px-3 h-7 bg-white shadow-xs' onClick={onStopResponding}> >
<StopCircle className='mr-[5px] w-3.5 h-3.5 text-gray-500' /> {
<span className='text-xs text-gray-500 font-normal'>{t('appDebug.operation.stopResponding')}</span> !noStopResponding && isResponsing && (
</Button> <div className='flex justify-center mb-2'>
</div> <Button className='py-0 px-3 h-7 bg-white shadow-xs' onClick={onStopResponding}>
) <StopCircle className='mr-[5px] w-3.5 h-3.5 text-gray-500' />
} <span className='text-xs text-gray-500 font-normal'>{t('appDebug.operation.stopResponding')}</span>
{ </Button>
hasTryToAsk && ( </div>
<TryToAsk )
suggestedQuestions={suggestedQuestions} }
onSend={onSend} {
/> hasTryToAsk && (
) <TryToAsk
} suggestedQuestions={suggestedQuestions}
{ onSend={onSend}
!noChatInput && ( />
<ChatInput )
visionConfig={config?.file_upload?.image} }
speechToTextConfig={config?.speech_to_text} {
onSend={onSend} !noChatInput && (
/> <ChatInput
) visionConfig={config?.file_upload?.image}
} speechToTextConfig={config?.speech_to_text}
onSend={onSend}
/>
)
}
</div>
</div> </div>
</div> </div>
</ChatContextProvider> </ChatContextProvider>
......
...@@ -34,7 +34,7 @@ const TryToAsk: FC<TryToAskProps> = ({ ...@@ -34,7 +34,7 @@ const TryToAsk: FC<TryToAskProps> = ({
}} }}
/> />
</div> </div>
<div className='flex flex-wrap'> <div className='flex flex-wrap justify-center'>
{ {
suggestedQuestions.map((suggestQuestion, index) => ( suggestedQuestions.map((suggestQuestion, index) => (
<Button <Button
......
export const CONVERSATION_ID_INFO = 'conversationIdInfo'
...@@ -44,8 +44,17 @@ export type EnableType = { ...@@ -44,8 +44,17 @@ export type EnableType = {
export type ChatConfig = Omit<ModelConfig, 'model'> & { export type ChatConfig = Omit<ModelConfig, 'model'> & {
supportAnnotation?: boolean supportAnnotation?: boolean
appId?: string appId?: string
supportFeedback?: boolean
} }
export type ChatItem = IChatItem export type ChatItem = IChatItem
export type OnSend = (message: string, files?: VisionFile[]) => void export type OnSend = (message: string, files?: VisionFile[]) => void
export type Callback = {
onSuccess: () => void
}
export type Feedback = {
rating: 'like' | 'dislike' | null
}
...@@ -20,6 +20,7 @@ export type ConfirmCommonProps = { ...@@ -20,6 +20,7 @@ export type ConfirmCommonProps = {
confirmBtnClassName?: string confirmBtnClassName?: string
confirmText?: string confirmText?: string
confirmWrapperClassName?: string confirmWrapperClassName?: string
confirmDisabled?: boolean
} }
const ConfirmCommon: FC<ConfirmCommonProps> = ({ const ConfirmCommon: FC<ConfirmCommonProps> = ({
...@@ -34,6 +35,7 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({ ...@@ -34,6 +35,7 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({
confirmBtnClassName, confirmBtnClassName,
confirmText, confirmText,
confirmWrapperClassName, confirmWrapperClassName,
confirmDisabled,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -78,6 +80,7 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({ ...@@ -78,6 +80,7 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({
type='primary' type='primary'
className={confirmBtnClassName || ''} className={confirmBtnClassName || ''}
onClick={onConfirm} onClick={onConfirm}
disabled={confirmDisabled}
> >
{confirmText || CONFIRM_MAP[type].confirmText} {confirmText || CONFIRM_MAP[type].confirmText}
</Button> </Button>
......
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon" clip-path="url(#clip0_17340_934)">
<path id="Icon_2" d="M11.3333 1.33398V8.66732M14.6666 6.53398V3.46732C14.6666 2.72058 14.6666 2.34721 14.5213 2.062C14.3935 1.81111 14.1895 1.60714 13.9386 1.47931C13.6534 1.33398 13.28 1.33398 12.5333 1.33398H5.41196C4.43764 1.33398 3.95048 1.33398 3.55701 1.51227C3.21022 1.66941 2.91549 1.92227 2.70745 2.24113C2.4714 2.60291 2.39732 3.08441 2.24917 4.0474L1.90045 6.31407C1.70505 7.58419 1.60735 8.21926 1.79582 8.7134C1.96125 9.14711 2.27239 9.50978 2.6759 9.73923C3.13564 10.0007 3.77818 10.0007 5.06324 10.0007H5.59995C5.97332 10.0007 6.16001 10.0007 6.30261 10.0733C6.42806 10.1372 6.53004 10.2392 6.59396 10.3647C6.66662 10.5073 6.66662 10.6939 6.66662 11.0673V13.0234C6.66662 13.9313 7.40262 14.6673 8.31051 14.6673C8.52706 14.6673 8.7233 14.5398 8.81125 14.3419L11.0518 9.30077C11.1537 9.07148 11.2046 8.95684 11.2852 8.87278C11.3563 8.79847 11.4438 8.74165 11.5406 8.70678C11.6501 8.66732 11.7756 8.66732 12.0265 8.66732H12.5333C13.28 8.66732 13.6534 8.66732 13.9386 8.52199C14.1895 8.39416 14.3935 8.19019 14.5213 7.93931C14.6666 7.65409 14.6666 7.28072 14.6666 6.53398Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_17340_934">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon" clip-path="url(#clip0_17340_931)">
<path id="Icon_2" d="M4.66671 14.6673V7.33398M1.33337 8.66732V13.334C1.33337 14.0704 1.93033 14.6673 2.66671 14.6673H11.6175C12.6047 14.6673 13.4442 13.9471 13.5943 12.9714L14.3122 8.30477C14.4986 7.09325 13.5613 6.00065 12.3355 6.00065H10C9.63185 6.00065 9.33337 5.70217 9.33337 5.33398V2.97788C9.33337 2.06998 8.59738 1.33398 7.68948 1.33398C7.47293 1.33398 7.27669 1.46151 7.18875 1.6594L4.84267 6.93808C4.73567 7.17883 4.49692 7.33398 4.23346 7.33398H2.66671C1.93033 7.33398 1.33337 7.93094 1.33337 8.66732Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_17340_931">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="edit-05" clip-path="url(#clip0_17249_52683)">
<path id="Icon" d="M7.33325 2.66617H4.53325C3.41315 2.66617 2.85309 2.66617 2.42527 2.88415C2.04895 3.0759 1.74299 3.38186 1.55124 3.75819C1.33325 4.18601 1.33325 4.74606 1.33325 5.86617V11.4662C1.33325 12.5863 1.33325 13.1463 1.55124 13.5741C1.74299 13.9505 2.04895 14.2564 2.42527 14.4482C2.85309 14.6662 3.41315 14.6662 4.53325 14.6662H10.1333C11.2534 14.6662 11.8134 14.6662 12.2412 14.4482C12.6176 14.2564 12.9235 13.9505 13.1153 13.5741C13.3333 13.1463 13.3333 12.5863 13.3333 11.4662V8.66617M5.33323 10.6662H6.4496C6.77572 10.6662 6.93878 10.6662 7.09223 10.6293C7.22828 10.5967 7.35834 10.5428 7.47763 10.4697C7.61219 10.3872 7.72749 10.2719 7.95809 10.0413L14.3333 3.66617C14.8855 3.11388 14.8855 2.21845 14.3333 1.66617C13.781 1.11388 12.8855 1.11388 12.3333 1.66617L5.95808 8.04133C5.72747 8.27193 5.61217 8.38723 5.52971 8.52179C5.45661 8.64108 5.40274 8.77114 5.37007 8.90719C5.33323 9.06064 5.33323 9.2237 5.33323 9.54982V10.6662Z" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_17249_52683">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="menu-01">
<path id="Icon" d="M2 8H14M2 4H14M2 12H14" stroke="#344054" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="pin-01">
<path id="Icon" d="M8.00037 10.0007L8.00037 14.6673M5.3337 4.87274V6.29315C5.3337 6.43183 5.3337 6.50117 5.32009 6.56749C5.30801 6.62633 5.28804 6.68327 5.26071 6.73677C5.22991 6.79706 5.18659 6.8512 5.09996 6.95949L4.05344 8.26764C3.60962 8.82242 3.3877 9.09982 3.38745 9.33326C3.38723 9.53629 3.47954 9.72835 3.63822 9.85501C3.82067 10.0007 4.1759 10.0007 4.88637 10.0007H11.1144C11.8248 10.0007 12.1801 10.0007 12.3625 9.85501C12.5212 9.72835 12.6135 9.53629 12.6133 9.33326C12.613 9.09982 12.3911 8.82242 11.9473 8.26764L10.9008 6.95949C10.8141 6.8512 10.7708 6.79706 10.74 6.73677C10.7127 6.68327 10.6927 6.62633 10.6806 6.56749C10.667 6.50117 10.667 6.43183 10.667 6.29315V4.87274C10.667 4.79599 10.667 4.75761 10.6714 4.71977C10.6752 4.68615 10.6816 4.65287 10.6905 4.62023C10.7006 4.58348 10.7148 4.54785 10.7433 4.47659L11.4152 2.7968C11.6113 2.30674 11.7093 2.06171 11.6684 1.86502C11.6327 1.693 11.5305 1.54206 11.384 1.44499C11.2166 1.33398 10.9527 1.33398 10.4249 1.33398H5.57587C5.04806 1.33398 4.78416 1.33398 4.61671 1.44499C4.47027 1.54206 4.36808 1.693 4.33233 1.86502C4.29146 2.06171 4.38947 2.30674 4.58549 2.7968L5.25741 4.47659C5.28591 4.54785 5.30017 4.58348 5.31019 4.62023C5.3191 4.65287 5.32551 4.68615 5.32936 4.71977C5.3337 4.75761 5.3337 4.79599 5.3337 4.87274Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="star-06">
<g id="Solid">
<path d="M3.66675 1.33268C3.66675 0.964492 3.36827 0.666016 3.00008 0.666016C2.63189 0.666016 2.33341 0.964492 2.33341 1.33268V2.33268H1.33341C0.965225 2.33268 0.666748 2.63116 0.666748 2.99935C0.666748 3.36754 0.965225 3.66602 1.33341 3.66602H2.33341V4.66602C2.33341 5.0342 2.63189 5.33268 3.00008 5.33268C3.36827 5.33268 3.66675 5.0342 3.66675 4.66602V3.66602H4.66675C5.03494 3.66602 5.33341 3.36754 5.33341 2.99935C5.33341 2.63116 5.03494 2.33268 4.66675 2.33268H3.66675V1.33268Z" fill="#444CE7"/>
<path d="M3.66675 11.3327C3.66675 10.9645 3.36827 10.666 3.00008 10.666C2.63189 10.666 2.33341 10.9645 2.33341 11.3327V12.3327H1.33341C0.965225 12.3327 0.666748 12.6312 0.666748 12.9993C0.666748 13.3675 0.965225 13.666 1.33341 13.666H2.33341V14.666C2.33341 15.0342 2.63189 15.3327 3.00008 15.3327C3.36827 15.3327 3.66675 15.0342 3.66675 14.666V13.666H4.66675C5.03494 13.666 5.33341 13.3675 5.33341 12.9993C5.33341 12.6312 5.03494 12.3327 4.66675 12.3327H3.66675V11.3327Z" fill="#444CE7"/>
<path d="M9.28898 1.76003C9.18995 1.50257 8.94259 1.33268 8.66675 1.33268C8.3909 1.33268 8.14354 1.50257 8.04452 1.76003L6.8884 4.76594C6.68813 5.28663 6.6252 5.43668 6.53912 5.55774C6.45274 5.67921 6.34661 5.78534 6.22514 5.87172C6.10408 5.9578 5.95403 6.02073 5.43334 6.221L2.42743 7.37712C2.16997 7.47614 2.00008 7.7235 2.00008 7.99935C2.00008 8.2752 2.16997 8.52256 2.42743 8.62158L5.43334 9.7777C5.95403 9.97797 6.10408 10.0409 6.22514 10.127C6.34661 10.2134 6.45274 10.3195 6.53912 10.441C6.6252 10.562 6.68813 10.7121 6.8884 11.2328L8.04452 14.2387C8.14354 14.4961 8.3909 14.666 8.66675 14.666C8.9426 14.666 9.18995 14.4961 9.28898 14.2387L10.4451 11.2328C10.6454 10.7121 10.7083 10.562 10.7944 10.441C10.8808 10.3195 10.9869 10.2134 11.1084 10.127C11.2294 10.0409 11.3795 9.97797 11.9002 9.7777L14.9061 8.62158C15.1635 8.52256 15.3334 8.2752 15.3334 7.99935C15.3334 7.7235 15.1635 7.47614 14.9061 7.37712L11.9002 6.221C11.3795 6.02073 11.2294 5.9578 11.1084 5.87172C10.9869 5.78534 10.8808 5.67921 10.7944 5.55774C10.7083 5.43668 10.6454 5.28663 10.4451 4.76594L9.28898 1.76003Z" fill="#444CE7"/>
</g>
</g>
</svg>
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon",
"clip-path": "url(#clip0_17340_934)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon_2",
"d": "M11.3333 1.33398V8.66732M14.6666 6.53398V3.46732C14.6666 2.72058 14.6666 2.34721 14.5213 2.062C14.3935 1.81111 14.1895 1.60714 13.9386 1.47931C13.6534 1.33398 13.28 1.33398 12.5333 1.33398H5.41196C4.43764 1.33398 3.95048 1.33398 3.55701 1.51227C3.21022 1.66941 2.91549 1.92227 2.70745 2.24113C2.4714 2.60291 2.39732 3.08441 2.24917 4.0474L1.90045 6.31407C1.70505 7.58419 1.60735 8.21926 1.79582 8.7134C1.96125 9.14711 2.27239 9.50978 2.6759 9.73923C3.13564 10.0007 3.77818 10.0007 5.06324 10.0007H5.59995C5.97332 10.0007 6.16001 10.0007 6.30261 10.0733C6.42806 10.1372 6.53004 10.2392 6.59396 10.3647C6.66662 10.5073 6.66662 10.6939 6.66662 11.0673V13.0234C6.66662 13.9313 7.40262 14.6673 8.31051 14.6673C8.52706 14.6673 8.7233 14.5398 8.81125 14.3419L11.0518 9.30077C11.1537 9.07148 11.2046 8.95684 11.2852 8.87278C11.3563 8.79847 11.4438 8.74165 11.5406 8.70678C11.6501 8.66732 11.7756 8.66732 12.0265 8.66732H12.5333C13.28 8.66732 13.6534 8.66732 13.9386 8.52199C14.1895 8.39416 14.3935 8.19019 14.5213 7.93931C14.6666 7.65409 14.6666 7.28072 14.6666 6.53398Z",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_17340_934"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "16",
"height": "16",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "ThumbsDown"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ThumbsDown.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'ThumbsDown'
export default Icon
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon",
"clip-path": "url(#clip0_17340_931)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon_2",
"d": "M4.66671 14.6673V7.33398M1.33337 8.66732V13.334C1.33337 14.0704 1.93033 14.6673 2.66671 14.6673H11.6175C12.6047 14.6673 13.4442 13.9471 13.5943 12.9714L14.3122 8.30477C14.4986 7.09325 13.5613 6.00065 12.3355 6.00065H10C9.63185 6.00065 9.33337 5.70217 9.33337 5.33398V2.97788C9.33337 2.06998 8.59738 1.33398 7.68948 1.33398C7.47293 1.33398 7.27669 1.46151 7.18875 1.6594L4.84267 6.93808C4.73567 7.17883 4.49692 7.33398 4.23346 7.33398H2.66671C1.93033 7.33398 1.33337 7.93094 1.33337 8.66732Z",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_17340_931"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "16",
"height": "16",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "ThumbsUp"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ThumbsUp.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'ThumbsUp'
export default Icon
export { default as AlertCircle } from './AlertCircle' export { default as AlertCircle } from './AlertCircle'
export { default as AlertTriangle } from './AlertTriangle' export { default as AlertTriangle } from './AlertTriangle'
export { default as ThumbsDown } from './ThumbsDown'
export { default as ThumbsUp } from './ThumbsUp'
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "edit-05",
"clip-path": "url(#clip0_17249_52683)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M7.33325 2.66617H4.53325C3.41315 2.66617 2.85309 2.66617 2.42527 2.88415C2.04895 3.0759 1.74299 3.38186 1.55124 3.75819C1.33325 4.18601 1.33325 4.74606 1.33325 5.86617V11.4662C1.33325 12.5863 1.33325 13.1463 1.55124 13.5741C1.74299 13.9505 2.04895 14.2564 2.42527 14.4482C2.85309 14.6662 3.41315 14.6662 4.53325 14.6662H10.1333C11.2534 14.6662 11.8134 14.6662 12.2412 14.4482C12.6176 14.2564 12.9235 13.9505 13.1153 13.5741C13.3333 13.1463 13.3333 12.5863 13.3333 11.4662V8.66617M5.33323 10.6662H6.4496C6.77572 10.6662 6.93878 10.6662 7.09223 10.6293C7.22828 10.5967 7.35834 10.5428 7.47763 10.4697C7.61219 10.3872 7.72749 10.2719 7.95809 10.0413L14.3333 3.66617C14.8855 3.11388 14.8855 2.21845 14.3333 1.66617C13.781 1.11388 12.8855 1.11388 12.3333 1.66617L5.95808 8.04133C5.72747 8.27193 5.61217 8.38723 5.52971 8.52179C5.45661 8.64108 5.40274 8.77114 5.37007 8.90719C5.33323 9.06064 5.33323 9.2237 5.33323 9.54982V10.6662Z",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_17249_52683"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "16",
"height": "16",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "Edit05"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Edit05.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Edit05'
export default Icon
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "menu-01"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M2 8H14M2 4H14M2 12H14",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Menu01"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Menu01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Menu01'
export default Icon
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "pin-01"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M8.00037 10.0007L8.00037 14.6673M5.3337 4.87274V6.29315C5.3337 6.43183 5.3337 6.50117 5.32009 6.56749C5.30801 6.62633 5.28804 6.68327 5.26071 6.73677C5.22991 6.79706 5.18659 6.8512 5.09996 6.95949L4.05344 8.26764C3.60962 8.82242 3.3877 9.09982 3.38745 9.33326C3.38723 9.53629 3.47954 9.72835 3.63822 9.85501C3.82067 10.0007 4.1759 10.0007 4.88637 10.0007H11.1144C11.8248 10.0007 12.1801 10.0007 12.3625 9.85501C12.5212 9.72835 12.6135 9.53629 12.6133 9.33326C12.613 9.09982 12.3911 8.82242 11.9473 8.26764L10.9008 6.95949C10.8141 6.8512 10.7708 6.79706 10.74 6.73677C10.7127 6.68327 10.6927 6.62633 10.6806 6.56749C10.667 6.50117 10.667 6.43183 10.667 6.29315V4.87274C10.667 4.79599 10.667 4.75761 10.6714 4.71977C10.6752 4.68615 10.6816 4.65287 10.6905 4.62023C10.7006 4.58348 10.7148 4.54785 10.7433 4.47659L11.4152 2.7968C11.6113 2.30674 11.7093 2.06171 11.6684 1.86502C11.6327 1.693 11.5305 1.54206 11.384 1.44499C11.2166 1.33398 10.9527 1.33398 10.4249 1.33398H5.57587C5.04806 1.33398 4.78416 1.33398 4.61671 1.44499C4.47027 1.54206 4.36808 1.693 4.33233 1.86502C4.29146 2.06171 4.38947 2.30674 4.58549 2.7968L5.25741 4.47659C5.28591 4.54785 5.30017 4.58348 5.31019 4.62023C5.3191 4.65287 5.32551 4.68615 5.32936 4.71977C5.3337 4.75761 5.3337 4.79599 5.3337 4.87274Z",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Pin01"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Pin01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Pin01'
export default Icon
...@@ -5,6 +5,7 @@ export { default as DotsHorizontal } from './DotsHorizontal' ...@@ -5,6 +5,7 @@ export { default as DotsHorizontal } from './DotsHorizontal'
export { default as Edit02 } from './Edit02' export { default as Edit02 } from './Edit02'
export { default as Edit03 } from './Edit03' export { default as Edit03 } from './Edit03'
export { default as Edit04 } from './Edit04' export { default as Edit04 } from './Edit04'
export { default as Edit05 } from './Edit05'
export { default as Hash02 } from './Hash02' export { default as Hash02 } from './Hash02'
export { default as HelpCircle } from './HelpCircle' export { default as HelpCircle } from './HelpCircle'
export { default as InfoCircle } from './InfoCircle' export { default as InfoCircle } from './InfoCircle'
...@@ -13,6 +14,8 @@ export { default as LinkExternal01 } from './LinkExternal01' ...@@ -13,6 +14,8 @@ export { default as LinkExternal01 } from './LinkExternal01'
export { default as LinkExternal02 } from './LinkExternal02' export { default as LinkExternal02 } from './LinkExternal02'
export { default as Loading02 } from './Loading02' export { default as Loading02 } from './Loading02'
export { default as LogOut01 } from './LogOut01' export { default as LogOut01 } from './LogOut01'
export { default as Menu01 } from './Menu01'
export { default as Pin01 } from './Pin01'
export { default as Pin02 } from './Pin02' export { default as Pin02 } from './Pin02'
export { default as Plus } from './Plus' export { default as Plus } from './Plus'
export { default as SearchLg } from './SearchLg' export { default as SearchLg } from './SearchLg'
......
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "star-06"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Solid"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M3.66675 1.33268C3.66675 0.964492 3.36827 0.666016 3.00008 0.666016C2.63189 0.666016 2.33341 0.964492 2.33341 1.33268V2.33268H1.33341C0.965225 2.33268 0.666748 2.63116 0.666748 2.99935C0.666748 3.36754 0.965225 3.66602 1.33341 3.66602H2.33341V4.66602C2.33341 5.0342 2.63189 5.33268 3.00008 5.33268C3.36827 5.33268 3.66675 5.0342 3.66675 4.66602V3.66602H4.66675C5.03494 3.66602 5.33341 3.36754 5.33341 2.99935C5.33341 2.63116 5.03494 2.33268 4.66675 2.33268H3.66675V1.33268Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M3.66675 11.3327C3.66675 10.9645 3.36827 10.666 3.00008 10.666C2.63189 10.666 2.33341 10.9645 2.33341 11.3327V12.3327H1.33341C0.965225 12.3327 0.666748 12.6312 0.666748 12.9993C0.666748 13.3675 0.965225 13.666 1.33341 13.666H2.33341V14.666C2.33341 15.0342 2.63189 15.3327 3.00008 15.3327C3.36827 15.3327 3.66675 15.0342 3.66675 14.666V13.666H4.66675C5.03494 13.666 5.33341 13.3675 5.33341 12.9993C5.33341 12.6312 5.03494 12.3327 4.66675 12.3327H3.66675V11.3327Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M9.28898 1.76003C9.18995 1.50257 8.94259 1.33268 8.66675 1.33268C8.3909 1.33268 8.14354 1.50257 8.04452 1.76003L6.8884 4.76594C6.68813 5.28663 6.6252 5.43668 6.53912 5.55774C6.45274 5.67921 6.34661 5.78534 6.22514 5.87172C6.10408 5.9578 5.95403 6.02073 5.43334 6.221L2.42743 7.37712C2.16997 7.47614 2.00008 7.7235 2.00008 7.99935C2.00008 8.2752 2.16997 8.52256 2.42743 8.62158L5.43334 9.7777C5.95403 9.97797 6.10408 10.0409 6.22514 10.127C6.34661 10.2134 6.45274 10.3195 6.53912 10.441C6.6252 10.562 6.68813 10.7121 6.8884 11.2328L8.04452 14.2387C8.14354 14.4961 8.3909 14.666 8.66675 14.666C8.9426 14.666 9.18995 14.4961 9.28898 14.2387L10.4451 11.2328C10.6454 10.7121 10.7083 10.562 10.7944 10.441C10.8808 10.3195 10.9869 10.2134 11.1084 10.127C11.2294 10.0409 11.3795 9.97797 11.9002 9.7777L14.9061 8.62158C15.1635 8.52256 15.3334 8.2752 15.3334 7.99935C15.3334 7.7235 15.1635 7.47614 14.9061 7.37712L11.9002 6.221C11.3795 6.02073 11.2294 5.9578 11.1084 5.87172C10.9869 5.78534 10.8808 5.67921 10.7944 5.55774C10.7083 5.43668 10.6454 5.28663 10.4451 4.76594L9.28898 1.76003Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
}
]
},
"name": "Star06"
}
\ No newline at end of file
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Star06.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Star06'
export default Icon
export { default as Star04 } from './Star04' export { default as Star04 } from './Star04'
export { default as Star06 } from './Star06'
...@@ -46,7 +46,7 @@ const Explore: FC<IExploreProps> = ({ ...@@ -46,7 +46,7 @@ const Explore: FC<IExploreProps> = ({
} }
> >
<Sidebar controlUpdateInstalledApps={controlUpdateInstalledApps} /> <Sidebar controlUpdateInstalledApps={controlUpdateInstalledApps} />
<div className='grow'> <div className='grow w-0'>
{children} {children}
</div> </div>
</ExploreContext.Provider> </ExploreContext.Provider>
......
...@@ -3,9 +3,9 @@ import type { FC } from 'react' ...@@ -3,9 +3,9 @@ import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import ExploreContext from '@/context/explore-context' import ExploreContext from '@/context/explore-context'
import ChatApp from '@/app/components/share/chat'
import TextGenerationApp from '@/app/components/share/text-generation' import TextGenerationApp from '@/app/components/share/text-generation'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import ChatWithHistory from '@/app/components/base/chat/chat-with-history'
export type IInstalledAppProps = { export type IInstalledAppProps = {
id: string id: string
...@@ -29,7 +29,7 @@ const InstalledApp: FC<IInstalledAppProps> = ({ ...@@ -29,7 +29,7 @@ const InstalledApp: FC<IInstalledAppProps> = ({
<div className='h-full py-2 pl-0 pr-2 sm:p-2'> <div className='h-full py-2 pl-0 pr-2 sm:p-2'>
{installedApp?.app.mode === 'chat' {installedApp?.app.mode === 'chat'
? ( ? (
<ChatApp isInstalledApp installedAppInfo={installedApp} /> <ChatWithHistory installedAppInfo={installedApp} className='rounded-2xl shadow-md overflow-hidden' />
) )
: ( : (
<TextGenerationApp isInstalledApp installedAppInfo={installedApp}/> <TextGenerationApp isInstalledApp installedAppInfo={installedApp}/>
......
...@@ -853,7 +853,7 @@ const Main: FC<IMainProps> = ({ ...@@ -853,7 +853,7 @@ const Main: FC<IMainProps> = ({
<Header <Header
title={siteInfo.title} title={siteInfo.title}
icon={siteInfo.icon || ''} icon={siteInfo.icon || ''}
icon_background={siteInfo.icon_background} icon_background={siteInfo.icon_background || ''}
isMobile={isMobile} isMobile={isMobile}
onShowSideBar={showSidebar} onShowSideBar={showSidebar}
onCreateNewChat={handleStartChatOnSidebar} onCreateNewChat={handleStartChatOnSidebar}
......
...@@ -11,11 +11,11 @@ export type ConversationItem = { ...@@ -11,11 +11,11 @@ export type ConversationItem = {
export type SiteInfo = { export type SiteInfo = {
title: string title: string
icon: string icon?: string
icon_background: string icon_background?: string
description: string description?: string
default_language: Locale default_language?: Locale
prompt_public: boolean prompt_public?: boolean
copyright?: string copyright?: string
privacy_policy?: string privacy_policy?: string
} }
...@@ -23,3 +23,18 @@ export type SiteInfo = { ...@@ -23,3 +23,18 @@ export type SiteInfo = {
export type AppMeta = { export type AppMeta = {
tool_icons: Record<string, string> tool_icons: Record<string, string>
} }
export type AppData = {
app_id: string
can_replace_logo?: boolean
custom_config?: Record<string, any>
enable_site?: boolean
end_user_id?: string
site: SiteInfo
}
export type AppConversationData = {
data: ConversationItem[]
has_more: boolean
limit: number
}
...@@ -4,6 +4,13 @@ import { ...@@ -4,6 +4,13 @@ import {
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost, delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
} from './base' } from './base'
import type { Feedbacktype } from '@/app/components/app/chat/type' import type { Feedbacktype } from '@/app/components/app/chat/type'
import type {
AppConversationData,
AppData,
AppMeta,
ConversationItem,
} from '@/models/share'
import type { ChatConfig } from '@/app/components/base/chat/types'
function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) { function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) {
switch (action) { switch (action) {
...@@ -18,7 +25,7 @@ function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boo ...@@ -18,7 +25,7 @@ function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boo
} }
} }
function getUrl(url: string, isInstalledApp: boolean, installedAppId: string) { export function getUrl(url: string, isInstalledApp: boolean, installedAppId: string) {
return isInstalledApp ? `installed-apps/${installedAppId}/${url.startsWith('/') ? url.slice(1) : url}` : url return isInstalledApp ? `installed-apps/${installedAppId}/${url.startsWith('/') ? url.slice(1) : url}` : url
} }
...@@ -59,11 +66,11 @@ export const sendCompletionMessage = async (body: Record<string, any>, { onData, ...@@ -59,11 +66,11 @@ export const sendCompletionMessage = async (body: Record<string, any>, { onData,
} }
export const fetchAppInfo = async () => { export const fetchAppInfo = async () => {
return get('/site') return get('/site') as Promise<AppData>
} }
export const fetchConversations = async (isInstalledApp: boolean, installedAppId = '', last_id?: string, pinned?: boolean, limit?: number) => { export const fetchConversations = async (isInstalledApp: boolean, installedAppId = '', last_id?: string, pinned?: boolean, limit?: number) => {
return getAction('get', isInstalledApp)(getUrl('conversations', isInstalledApp, installedAppId), { params: { ...{ limit: limit || 20 }, ...(last_id ? { last_id } : {}), ...(pinned !== undefined ? { pinned } : {}) } }) return getAction('get', isInstalledApp)(getUrl('conversations', isInstalledApp, installedAppId), { params: { ...{ limit: limit || 20 }, ...(last_id ? { last_id } : {}), ...(pinned !== undefined ? { pinned } : {}) } }) as Promise<AppConversationData>
} }
export const pinConversation = async (isInstalledApp: boolean, installedAppId = '', id: string) => { export const pinConversation = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
...@@ -83,11 +90,11 @@ export const renameConversation = async (isInstalledApp: boolean, installedAppId ...@@ -83,11 +90,11 @@ export const renameConversation = async (isInstalledApp: boolean, installedAppId
} }
export const generationConversationName = async (isInstalledApp: boolean, installedAppId = '', id: string) => { export const generationConversationName = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
return getAction('post', isInstalledApp)(getUrl(`conversations/${id}/name`, isInstalledApp, installedAppId), { body: { auto_generate: true } }) return getAction('post', isInstalledApp)(getUrl(`conversations/${id}/name`, isInstalledApp, installedAppId), { body: { auto_generate: true } }) as Promise<ConversationItem>
} }
export const fetchChatList = async (conversationId: string, isInstalledApp: boolean, installedAppId = '') => { export const fetchChatList = async (conversationId: string, isInstalledApp: boolean, installedAppId = '') => {
return getAction('get', isInstalledApp)(getUrl('messages', isInstalledApp, installedAppId), { params: { conversation_id: conversationId, limit: 20, last_id: '' } }) return getAction('get', isInstalledApp)(getUrl('messages', isInstalledApp, installedAppId), { params: { conversation_id: conversationId, limit: 20, last_id: '' } }) as any
} }
// Abandoned API interface // Abandoned API interface
...@@ -97,11 +104,11 @@ export const fetchChatList = async (conversationId: string, isInstalledApp: bool ...@@ -97,11 +104,11 @@ export const fetchChatList = async (conversationId: string, isInstalledApp: bool
// init value. wait for server update // init value. wait for server update
export const fetchAppParams = async (isInstalledApp: boolean, installedAppId = '') => { export const fetchAppParams = async (isInstalledApp: boolean, installedAppId = '') => {
return (getAction('get', isInstalledApp))(getUrl('parameters', isInstalledApp, installedAppId)) return (getAction('get', isInstalledApp))(getUrl('parameters', isInstalledApp, installedAppId)) as Promise<ChatConfig>
} }
export const fetchAppMeta = async (isInstalledApp: boolean, installedAppId = '') => { export const fetchAppMeta = async (isInstalledApp: boolean, installedAppId = '') => {
return (getAction('get', isInstalledApp))(getUrl('meta', isInstalledApp, installedAppId)) return (getAction('get', isInstalledApp))(getUrl('meta', isInstalledApp, installedAppId)) as Promise<AppMeta>
} }
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }, isInstalledApp: boolean, installedAppId = '') => { export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }, isInstalledApp: boolean, installedAppId = '') => {
......
...@@ -70,6 +70,7 @@ export type PromptVariable = { ...@@ -70,6 +70,7 @@ export type PromptVariable = {
} }
export type TextTypeFormItem = { export type TextTypeFormItem = {
default: string
label: string label: string
variable: string variable: string
required: boolean required: boolean
...@@ -77,11 +78,19 @@ export type TextTypeFormItem = { ...@@ -77,11 +78,19 @@ export type TextTypeFormItem = {
} }
export type SelectTypeFormItem = { export type SelectTypeFormItem = {
default: string
label: string label: string
variable: string variable: string
required: boolean required: boolean
options: string[] options: string[]
} }
export type ParagraphTypeFormItem = {
default: string
label: string
variable: string
required: boolean
}
/** /**
* User Input Form Item * User Input Form Item
*/ */
...@@ -89,6 +98,8 @@ export type UserInputFormItem = { ...@@ -89,6 +98,8 @@ export type UserInputFormItem = {
'text-input': TextTypeFormItem 'text-input': TextTypeFormItem
} | { } | {
'select': SelectTypeFormItem 'select': SelectTypeFormItem
} | {
'paragraph': TextTypeFormItem
} }
export type AgentTool = { export type AgentTool = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment