|
@@ -5,9 +5,8 @@ import useSWR from 'swr'
|
|
|
import {
|
|
|
HandThumbDownIcon,
|
|
|
HandThumbUpIcon,
|
|
|
- XMarkIcon,
|
|
|
} from '@heroicons/react/24/outline'
|
|
|
-import { RiEditFill, RiQuestionLine } from '@remixicon/react'
|
|
|
+import { RiCloseLine, RiEditFill } from '@remixicon/react'
|
|
|
import { get } from 'lodash-es'
|
|
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
|
|
import dayjs from 'dayjs'
|
|
@@ -18,20 +17,16 @@ import { useShallow } from 'zustand/react/shallow'
|
|
|
import { useTranslation } from 'react-i18next'
|
|
|
import type { ChatItemInTree } from '../../base/chat/types'
|
|
|
import VarPanel from './var-panel'
|
|
|
-import cn from '@/utils/classnames'
|
|
|
import type { FeedbackFunc, FeedbackType, IChatItem, SubmitAnnotationFunc } from '@/app/components/base/chat/chat/type'
|
|
|
import type { Annotation, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationGeneralDetail, CompletionConversationsResponse, LogAnnotation } from '@/models/log'
|
|
|
import type { App } from '@/types/app'
|
|
|
+import ActionButton from '@/app/components/base/action-button'
|
|
|
import Loading from '@/app/components/base/loading'
|
|
|
import Drawer from '@/app/components/base/drawer'
|
|
|
-import Popover from '@/app/components/base/popover'
|
|
|
import Chat from '@/app/components/base/chat/chat'
|
|
|
import { ToastContext } from '@/app/components/base/toast'
|
|
|
import { fetchChatConversationDetail, fetchChatMessages, fetchCompletionConversationDetail, updateLogMessageAnnotations, updateLogMessageFeedbacks } from '@/service/log'
|
|
|
-import { TONE_LIST } from '@/config'
|
|
|
-import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
|
|
-import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
|
|
-import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
|
|
+import ModelInfo from '@/app/components/app/log/model-info'
|
|
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
|
|
import TextGeneration from '@/app/components/app/text-generate/item'
|
|
|
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
|
@@ -44,6 +39,7 @@ import Tooltip from '@/app/components/base/tooltip'
|
|
|
import { CopyIcon } from '@/app/components/base/copy-icon'
|
|
|
import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils'
|
|
|
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
|
|
+import cn from '@/utils/classnames'
|
|
|
|
|
|
dayjs.extend(utc)
|
|
|
dayjs.extend(timezone)
|
|
@@ -75,15 +71,6 @@ const HandThumbIconWithCount: FC<{ count: number; iconType: 'up' | 'down' }> = (
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
-const PARAM_MAP = {
|
|
|
- temperature: 'Temperature',
|
|
|
- top_p: 'Top P',
|
|
|
- presence_penalty: 'Presence Penalty',
|
|
|
- max_tokens: 'Max Token',
|
|
|
- stop: 'Stop',
|
|
|
- frequency_penalty: 'Frequency Penalty',
|
|
|
-}
|
|
|
-
|
|
|
const getFormattedChatList = (messages: ChatMessage[], conversationId: string, timezone: string, format: string) => {
|
|
|
const newChatList: IChatItem[] = []
|
|
|
messages.forEach((item: ChatMessage) => {
|
|
@@ -156,9 +143,6 @@ const getFormattedChatList = (messages: ChatMessage[], conversationId: string, t
|
|
|
return newChatList
|
|
|
}
|
|
|
|
|
|
-// const displayedParams = CompletionParams.slice(0, -2)
|
|
|
-const validatedParams = ['temperature', 'top_p', 'presence_penalty', 'frequency_penalty']
|
|
|
-
|
|
|
type IDetailPanel = {
|
|
|
detail: any
|
|
|
onFeedback: FeedbackFunc
|
|
@@ -315,22 +299,6 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|
|
const isChatMode = appDetail?.mode !== 'completion'
|
|
|
const isAdvanced = appDetail?.mode === 'advanced-chat'
|
|
|
|
|
|
- const targetTone = TONE_LIST.find((item: any) => {
|
|
|
- let res = true
|
|
|
- validatedParams.forEach((param) => {
|
|
|
- res = item.config?.[param] === detail?.model_config.model?.completion_params?.[param]
|
|
|
- })
|
|
|
- return res
|
|
|
- })?.name ?? 'custom'
|
|
|
-
|
|
|
- const modelName = (detail.model_config as any).model?.name
|
|
|
- const provideName = (detail.model_config as any).model?.provider as any
|
|
|
- const {
|
|
|
- currentModel,
|
|
|
- currentProvider,
|
|
|
- } = useTextGenerationCurrentProviderAndModelAndModelList(
|
|
|
- { provider: provideName, model: modelName },
|
|
|
- )
|
|
|
const varList = (detail.model_config as any).user_input_form?.map((item: any) => {
|
|
|
const itemContent = item[Object.keys(item)[0]]
|
|
|
return {
|
|
@@ -342,18 +310,6 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|
|
? detail.message.message_files.map((item: any) => item.url)
|
|
|
: []
|
|
|
|
|
|
- const getParamValue = (param: string) => {
|
|
|
- const value = detail?.model_config.model?.completion_params?.[param] || '-'
|
|
|
- if (param === 'stop') {
|
|
|
- if (Array.isArray(value))
|
|
|
- return value.join(',')
|
|
|
- else
|
|
|
- return '-'
|
|
|
- }
|
|
|
-
|
|
|
- return value
|
|
|
- }
|
|
|
-
|
|
|
const [width, setWidth] = useState(0)
|
|
|
const ref = useRef<HTMLDivElement>(null)
|
|
|
|
|
@@ -367,162 +323,71 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|
|
}, [])
|
|
|
|
|
|
return (
|
|
|
- <div ref={ref} className='rounded-xl border-[0.5px] border-gray-200 h-full flex flex-col overflow-auto'>
|
|
|
+ <div ref={ref} className='rounded-xl border-[0.5px] border-components-panel-border h-full flex flex-col'>
|
|
|
{/* Panel Header */}
|
|
|
- <div className='border-b border-gray-100 py-4 px-6 flex items-center justify-between bg-components-panel-bg'>
|
|
|
- <div>
|
|
|
- <div className='text-gray-500 text-[10px] leading-[14px]'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div>
|
|
|
+ <div className='shrink-0 pl-4 pt-3 pr-3 pb-2 flex items-center gap-2 bg-components-panel-bg rounded-t-xl'>
|
|
|
+ <div className='shrink-0'>
|
|
|
+ <div className='mb-0.5 text-text-primary system-xs-semibold-uppercase'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div>
|
|
|
{isChatMode && (
|
|
|
- <div className='flex items-center text-gray-700 text-[13px] leading-[18px]'>
|
|
|
+ <div className='flex items-center text-text-secondary system-2xs-regular-uppercase'>
|
|
|
<Tooltip
|
|
|
popupContent={detail.id}
|
|
|
>
|
|
|
- <div className='max-w-[105px] truncate'>{detail.id}</div>
|
|
|
+ <div className='truncate'>{detail.id}</div>
|
|
|
</Tooltip>
|
|
|
<CopyIcon content={detail.id} />
|
|
|
</div>
|
|
|
)}
|
|
|
{!isChatMode && (
|
|
|
- <div className='text-gray-700 text-[13px] leading-[18px]'>{formatTime(detail.created_at, t('appLog.dateTimeFormat') as string)}</div>
|
|
|
+ <div className='text-text-secondary system-2xs-regular-uppercase'>{formatTime(detail.created_at, t('appLog.dateTimeFormat') as string)}</div>
|
|
|
)}
|
|
|
</div>
|
|
|
- <div className='flex items-center flex-wrap gap-y-1 justify-end'>
|
|
|
- {!isAdvanced && (
|
|
|
- <>
|
|
|
- <div
|
|
|
- className={cn('mr-2 flex items-center border h-8 px-2 space-x-2 rounded-lg bg-indigo-25 border-[#2A87F5]')}
|
|
|
- >
|
|
|
- <ModelIcon
|
|
|
- className='!w-5 !h-5'
|
|
|
- provider={currentProvider}
|
|
|
- modelName={currentModel?.model}
|
|
|
- />
|
|
|
- <ModelName
|
|
|
- modelItem={currentModel!}
|
|
|
- showMode
|
|
|
- />
|
|
|
- </div>
|
|
|
- <Popover
|
|
|
- position='br'
|
|
|
- className='!w-[280px]'
|
|
|
- btnClassName='mr-4 !bg-gray-50 !py-1.5 !px-2.5 border-none font-normal'
|
|
|
- btnElement={<>
|
|
|
- <span className='text-[13px]'>{targetTone}</span>
|
|
|
- <RiQuestionLine className='h-4 w-4 text-gray-800 ml-1.5' />
|
|
|
- </>}
|
|
|
- htmlContent={<div className='w-[280px]'>
|
|
|
- <div className='flex justify-between py-2 px-4 font-medium text-sm text-gray-700'>
|
|
|
- <span>Tone of responses</span>
|
|
|
- <div>{targetTone}</div>
|
|
|
- </div>
|
|
|
- {['temperature', 'top_p', 'presence_penalty', 'max_tokens', 'stop'].map((param: string, index: number) => {
|
|
|
- return <div className='flex justify-between py-2 px-4 bg-gray-50' key={index}>
|
|
|
- <span className='text-xs text-gray-700'>{PARAM_MAP[param as keyof typeof PARAM_MAP]}</span>
|
|
|
- <span className='text-gray-800 font-medium text-xs'>{getParamValue(param)}</span>
|
|
|
- </div>
|
|
|
- })}
|
|
|
- </div>}
|
|
|
- />
|
|
|
- </>
|
|
|
- )}
|
|
|
- <div className='w-6 h-6 rounded-lg flex items-center justify-center hover:cursor-pointer hover:bg-gray-100'>
|
|
|
- <XMarkIcon className='w-4 h-4 text-gray-500' onClick={onClose} />
|
|
|
- </div>
|
|
|
+ <div className='grow flex items-center flex-wrap gap-y-1 justify-end'>
|
|
|
+ {!isAdvanced && <ModelInfo model={detail.model_config.model} />}
|
|
|
</div>
|
|
|
-
|
|
|
+ <ActionButton size='l' onClick={onClose}>
|
|
|
+ <RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
|
|
+ </ActionButton>
|
|
|
</div>
|
|
|
{/* Panel Body */}
|
|
|
- {(varList.length > 0 || (!isChatMode && message_files.length > 0)) && (
|
|
|
- <div className='px-6 pt-4 pb-2'>
|
|
|
- <VarPanel
|
|
|
- varList={varList}
|
|
|
- message_files={message_files}
|
|
|
- />
|
|
|
- </div>
|
|
|
- )}
|
|
|
-
|
|
|
- {!isChatMode
|
|
|
- ? <div className="px-6 py-4">
|
|
|
- <div className='flex h-[18px] items-center space-x-3'>
|
|
|
- <div className='leading-[18px] text-xs font-semibold text-gray-500 uppercase'>{t('appLog.table.header.output')}</div>
|
|
|
- <div className='grow h-[1px]' style={{
|
|
|
- background: 'linear-gradient(270deg, rgba(243, 244, 246, 0) 0%, rgb(243, 244, 246) 100%)',
|
|
|
- }}></div>
|
|
|
- </div>
|
|
|
- <TextGeneration
|
|
|
- className='mt-2'
|
|
|
- content={detail.message.answer}
|
|
|
- messageId={detail.message.id}
|
|
|
- isError={false}
|
|
|
- onRetry={() => { }}
|
|
|
- isInstalledApp={false}
|
|
|
- supportFeedback
|
|
|
- feedback={detail.message.feedbacks.find((item: any) => item.from_source === 'admin')}
|
|
|
- onFeedback={feedback => onFeedback(detail.message.id, feedback)}
|
|
|
- supportAnnotation
|
|
|
- isShowTextToSpeech
|
|
|
- appId={appDetail?.id}
|
|
|
- varList={varList}
|
|
|
- siteInfo={null}
|
|
|
- />
|
|
|
+ <div className='shrink-0 pt-1 px-1'>
|
|
|
+ <div className='p-3 pb-2 rounded-t-xl bg-background-section-burn'>
|
|
|
+ {(varList.length > 0 || (!isChatMode && message_files.length > 0)) && (
|
|
|
+ <VarPanel
|
|
|
+ varList={varList}
|
|
|
+ message_files={message_files}
|
|
|
+ />
|
|
|
+ )}
|
|
|
</div>
|
|
|
- : threadChatItems.length < 8
|
|
|
- ? <div className="pt-4 mb-4">
|
|
|
- <Chat
|
|
|
- config={{
|
|
|
- appId: appDetail?.id,
|
|
|
- text_to_speech: {
|
|
|
- enabled: true,
|
|
|
- },
|
|
|
- supportAnnotation: true,
|
|
|
- annotation_reply: {
|
|
|
- enabled: true,
|
|
|
- },
|
|
|
- supportFeedback: true,
|
|
|
- } as any}
|
|
|
- chatList={threadChatItems}
|
|
|
- onAnnotationAdded={handleAnnotationAdded}
|
|
|
- onAnnotationEdited={handleAnnotationEdited}
|
|
|
- onAnnotationRemoved={handleAnnotationRemoved}
|
|
|
- onFeedback={onFeedback}
|
|
|
- noChatInput
|
|
|
- showPromptLog
|
|
|
- hideProcessDetail
|
|
|
- chatContainerInnerClassName='px-6'
|
|
|
- switchSibling={switchSibling}
|
|
|
+ </div>
|
|
|
+ <div className='grow mx-1 mb-1 bg-background-section-burn rounded-b-xl overflow-auto'>
|
|
|
+ {!isChatMode
|
|
|
+ ? <div className="px-6 py-4">
|
|
|
+ <div className='flex h-[18px] items-center space-x-3'>
|
|
|
+ <div className='text-text-tertiary system-xs-semibold-uppercase'>{t('appLog.table.header.output')}</div>
|
|
|
+ <div className='grow h-[1px]' style={{
|
|
|
+ background: 'linear-gradient(270deg, rgba(243, 244, 246, 0) 0%, rgb(243, 244, 246) 100%)',
|
|
|
+ }}></div>
|
|
|
+ </div>
|
|
|
+ <TextGeneration
|
|
|
+ className='mt-2'
|
|
|
+ content={detail.message.answer}
|
|
|
+ messageId={detail.message.id}
|
|
|
+ isError={false}
|
|
|
+ onRetry={() => { }}
|
|
|
+ isInstalledApp={false}
|
|
|
+ supportFeedback
|
|
|
+ feedback={detail.message.feedbacks.find((item: any) => item.from_source === 'admin')}
|
|
|
+ onFeedback={feedback => onFeedback(detail.message.id, feedback)}
|
|
|
+ supportAnnotation
|
|
|
+ isShowTextToSpeech
|
|
|
+ appId={appDetail?.id}
|
|
|
+ varList={varList}
|
|
|
+ siteInfo={null}
|
|
|
/>
|
|
|
</div>
|
|
|
- : <div
|
|
|
- className="py-4"
|
|
|
- id="scrollableDiv"
|
|
|
- style={{
|
|
|
- height: 1000, // Specify a value
|
|
|
- overflow: 'auto',
|
|
|
- display: 'flex',
|
|
|
- flexDirection: 'column-reverse',
|
|
|
- }}>
|
|
|
- {/* Put the scroll bar always on the bottom */}
|
|
|
- <InfiniteScroll
|
|
|
- scrollableTarget="scrollableDiv"
|
|
|
- dataLength={threadChatItems.length}
|
|
|
- next={fetchData}
|
|
|
- hasMore={hasMore}
|
|
|
- loader={<div className='text-center text-gray-400 text-xs'>{t('appLog.detail.loading')}...</div>}
|
|
|
- // endMessage={<div className='text-center'>Nothing more to show</div>}
|
|
|
- // below props only if you need pull down functionality
|
|
|
- refreshFunction={fetchData}
|
|
|
- pullDownToRefresh
|
|
|
- pullDownToRefreshThreshold={50}
|
|
|
- // pullDownToRefreshContent={
|
|
|
- // <div className='text-center'>Pull down to refresh</div>
|
|
|
- // }
|
|
|
- // releaseToRefreshContent={
|
|
|
- // <div className='text-center'>Release to refresh</div>
|
|
|
- // }
|
|
|
- // To put endMessage and loader to the top.
|
|
|
- style={{ display: 'flex', flexDirection: 'column-reverse' }}
|
|
|
- inverse={true}
|
|
|
- >
|
|
|
+ : threadChatItems.length < 8
|
|
|
+ ? <div className="pt-4 mb-4">
|
|
|
<Chat
|
|
|
config={{
|
|
|
appId: appDetail?.id,
|
|
@@ -543,12 +408,68 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|
|
noChatInput
|
|
|
showPromptLog
|
|
|
hideProcessDetail
|
|
|
- chatContainerInnerClassName='px-6'
|
|
|
+ chatContainerInnerClassName='px-3'
|
|
|
switchSibling={switchSibling}
|
|
|
/>
|
|
|
- </InfiniteScroll>
|
|
|
- </div>
|
|
|
- }
|
|
|
+ </div>
|
|
|
+ : <div
|
|
|
+ className="py-4"
|
|
|
+ id="scrollableDiv"
|
|
|
+ style={{
|
|
|
+ height: 1000, // Specify a value
|
|
|
+ overflow: 'auto',
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column-reverse',
|
|
|
+ }}>
|
|
|
+ {/* Put the scroll bar always on the bottom */}
|
|
|
+ <InfiniteScroll
|
|
|
+ scrollableTarget="scrollableDiv"
|
|
|
+ dataLength={threadChatItems.length}
|
|
|
+ next={fetchData}
|
|
|
+ hasMore={hasMore}
|
|
|
+ loader={<div className='text-center text-text-tertiary system-xs-regular'>{t('appLog.detail.loading')}...</div>}
|
|
|
+ // endMessage={<div className='text-center'>Nothing more to show</div>}
|
|
|
+ // below props only if you need pull down functionality
|
|
|
+ refreshFunction={fetchData}
|
|
|
+ pullDownToRefresh
|
|
|
+ pullDownToRefreshThreshold={50}
|
|
|
+ // pullDownToRefreshContent={
|
|
|
+ // <div className='text-center'>Pull down to refresh</div>
|
|
|
+ // }
|
|
|
+ // releaseToRefreshContent={
|
|
|
+ // <div className='text-center'>Release to refresh</div>
|
|
|
+ // }
|
|
|
+ // To put endMessage and loader to the top.
|
|
|
+ style={{ display: 'flex', flexDirection: 'column-reverse' }}
|
|
|
+ inverse={true}
|
|
|
+ >
|
|
|
+ <Chat
|
|
|
+ config={{
|
|
|
+ appId: appDetail?.id,
|
|
|
+ text_to_speech: {
|
|
|
+ enabled: true,
|
|
|
+ },
|
|
|
+ supportAnnotation: true,
|
|
|
+ annotation_reply: {
|
|
|
+ enabled: true,
|
|
|
+ },
|
|
|
+ supportFeedback: true,
|
|
|
+ } as any}
|
|
|
+ chatList={threadChatItems}
|
|
|
+ onAnnotationAdded={handleAnnotationAdded}
|
|
|
+ onAnnotationEdited={handleAnnotationEdited}
|
|
|
+ onAnnotationRemoved={handleAnnotationRemoved}
|
|
|
+ onFeedback={onFeedback}
|
|
|
+ noChatInput
|
|
|
+ showPromptLog
|
|
|
+ hideProcessDetail
|
|
|
+ chatContainerInnerClassName='px-3'
|
|
|
+ switchSibling={switchSibling}
|
|
|
+ />
|
|
|
+ </InfiniteScroll>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
{showMessageLogModal && (
|
|
|
<MessageLogModal
|
|
|
width={width}
|
|
@@ -780,7 +701,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
|
|
onClose={onCloseDrawer}
|
|
|
mask={isMobile}
|
|
|
footer={null}
|
|
|
- panelClassname='mt-16 mx-2 sm:mr-2 mb-4 !p-0 !max-w-[640px] rounded-xl bg-background-gradient-bg-fill-chat-bg-1'
|
|
|
+ panelClassname='mt-16 mx-2 sm:mr-2 mb-4 !p-0 !max-w-[640px] rounded-xl bg-components-panel-bg'
|
|
|
>
|
|
|
<DrawerContext.Provider value={{
|
|
|
onClose: onCloseDrawer,
|