fix prompt log

This commit is contained in:
JzoNg 2024-03-17 12:41:23 +08:00
parent cd01c890e1
commit 9638885a67
9 changed files with 214 additions and 216 deletions

View File

@ -1,6 +1,6 @@
'use client'
import type { FC, ReactNode } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { UserCircleIcon } from '@heroicons/react/24/solid'
import cn from 'classnames'
@ -27,7 +27,6 @@ import type { Emoji } from '@/app/components/tools/types'
import type { VisionFile } from '@/types/app'
import ImageGallery from '@/app/components/base/image-gallery'
import Log from '@/app/components/app/chat/log'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => {
return <div className={'rounded-lg h-6 w-6 flex items-center justify-center hover:bg-gray-100'}>
@ -234,23 +233,9 @@ const Answer: FC<IAnswerProps> = ({
</div>
)
const [showPromptLogModal, setShowPromptLogModal] = useState(false)
const [width, setWidth] = useState(0)
const ref = useRef<HTMLDivElement>(null)
const adjustModalWidth = () => {
if (ref.current)
setWidth(document.body.clientWidth - (ref.current?.clientWidth + 56 + 16))
}
useEffect(() => {
adjustModalWidth()
}, [])
return (
// data-id for debug the item message is right
<div key={id} data-id={id} ref={ref}>
<div key={id} data-id={id}>
<div className='flex items-start'>
{
answerIcon || (
@ -336,7 +321,7 @@ const Answer: FC<IAnswerProps> = ({
{((isShowPromptLog && !isResponding) || (!item.isOpeningStatement && isShowTextToSpeech)) && (
<div className='hidden group-hover:flex items-center h-[28px] p-0.5 rounded-lg bg-white border-[0.5px] border-gray-100 shadow-md'>
{isShowPromptLog && !isResponding && (
<Log runID={item.workflow_run_id} setShowModal={setShowPromptLogModal} />
<Log logItem={item} />
)}
{!item.isOpeningStatement && isShowTextToSpeech && (
<>
@ -386,13 +371,6 @@ const Answer: FC<IAnswerProps> = ({
{!feedbackDisabled && renderFeedbackRating(feedback?.rating, !isHideFeedbackEdit, displayScene !== 'console')}
</div>
</div>
{showPromptLogModal && (
<PromptLogModal
width={width}
log={item.log || []}
onCancel={() => setShowPromptLogModal(false)}
/>
)}
{more && <MoreInfo className='invisible group-hover:visible' more={more} isQuestion={false} />}
</div>
</div>

View File

@ -1,43 +1,32 @@
import type { Dispatch, FC, ReactNode, SetStateAction } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { File02 } from '@/app/components/base/icons/src/vender/line/files'
export type LogData = {
role: string
text: string
}
import type { IChatItem } from '@/app/components/app/chat/type'
import { useStore as useAppStore } from '@/app/components/app/store'
type LogProps = {
runID?: string
setShowModal: Dispatch<SetStateAction<boolean>>
children?: (v: Dispatch<SetStateAction<boolean>>) => ReactNode
logItem: IChatItem
}
const Log: FC<LogProps> = ({
children,
runID,
setShowModal,
logItem,
}) => {
const { t } = useTranslation()
const { setCurrentLogItem, setShowPromptLogModal } = useAppStore()
const { workflow_run_id: runID } = logItem
return (
<>
{
children
? children(setShowModal)
: (
<div
className='p-1 flex items-center justify-center rounded-[6px] hover:bg-gray-50 cursor-pointer'
onClick={(e) => {
e.stopPropagation()
setShowModal(true)
}}
>
<File02 className='mr-1 w-4 h-4 text-gray-500' />
<div className='text-xs leading-4 text-gray-500'>{runID ? t('appLog.viewLog') : t('appLog.promptLog')}</div>
</div>
)
}
</>
<div
className='p-1 flex items-center justify-center rounded-[6px] hover:bg-gray-50 cursor-pointer'
onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
setCurrentLogItem(logItem)
setShowPromptLogModal(true)
}}
>
<File02 className='mr-1 w-4 h-4 text-gray-500' />
<div className='text-xs leading-4 text-gray-500'>{runID ? t('appLog.viewLog') : t('appLog.promptLog')}</div>
</div>
)
}

View File

@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useEffect, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import useSWR from 'swr'
import {
HandThumbDownIcon,
@ -35,6 +35,8 @@ import ModelName from '@/app/components/header/account-setting/model-provider-pa
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import TextGeneration from '@/app/components/app/text-generate/item'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
import { useStore as useAppStore } from '@/app/components/app/store'
type IConversationList = {
logs?: ChatConversationsResponse | CompletionConversationsResponse
@ -141,6 +143,7 @@ type IDetailPanel<T> = {
function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionConversationFullDetailResponse>({ detail, onFeedback }: IDetailPanel<T>) {
const { onClose, appDetail } = useContext(DrawerContext)
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal } = useAppStore()
const { t } = useTranslation()
const [items, setItems] = React.useState<IChatItem[]>([])
const [hasMore, setHasMore] = useState(true)
@ -228,144 +231,168 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
return value
}
return (<div className='rounded-xl border-[0.5px] border-gray-200 h-full flex flex-col overflow-auto'>
{/* Panel Header */}
<div className='border-b border-gray-100 py-4 px-6 flex items-center justify-between'>
<div>
<div className='text-gray-500 text-[10px] leading-[14px]'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div>
<div className='text-gray-700 text-[13px] leading-[18px]'>{isChatMode ? detail.id?.split('-').slice(-1)[0] : dayjs.unix(detail.created_at).format(t('appLog.dateTimeFormat') as string)}</div>
</div>
<div className='flex items-center flex-wrap gap-y-1 justify-end'>
<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
/>
const [width, setWidth] = useState(0)
const ref = useRef<HTMLDivElement>(null)
const adjustModalWidth = () => {
if (ref.current)
setWidth(document.body.clientWidth - (ref.current?.clientWidth + 16) - 8)
}
useEffect(() => {
adjustModalWidth()
}, [])
return (
<div ref={ref} className='rounded-xl border-[0.5px] border-gray-200 h-full flex flex-col overflow-auto'>
{/* Panel Header */}
<div className='border-b border-gray-100 py-4 px-6 flex items-center justify-between'>
<div>
<div className='text-gray-500 text-[10px] leading-[14px]'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div>
<div className='text-gray-700 text-[13px] leading-[18px]'>{isChatMode ? detail.id?.split('-').slice(-1)[0] : dayjs.unix(detail.created_at).format(t('appLog.dateTimeFormat') as string)}</div>
</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>
<InformationCircleIcon 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 className='flex items-center flex-wrap gap-y-1 justify-end'>
<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>
<InformationCircleIcon 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>
})}
</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} />
{['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>
</div>
</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>
{/* 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>
<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}
/>
</div>
: items.length < 8
? <div className="px-2.5 pt-4 mb-4">
<Chat
chatList={items}
isHideSendInput={true}
onFeedback={onFeedback}
displayScene='console'
isShowPromptLog
)}
{!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}
onChatListChange={setItems}
varList={varList}
/>
</div>
: <div
className="px-2.5 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={items.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}
>
: items.length < 8
? <div className="px-2.5 pt-4 mb-4">
<Chat
chatList={items}
isHideSendInput={true}
onFeedback={onFeedback}
displayScene='console'
isShowPromptLog
supportAnnotation
isShowTextToSpeech
appId={appDetail?.id}
onChatListChange={setItems}
/>
</InfiniteScroll>
</div>
}
</div>)
</div>
: <div
className="px-2.5 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={items.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}
>
<Chat
chatList={items}
isHideSendInput={true}
onFeedback={onFeedback}
displayScene='console'
isShowPromptLog
/>
</InfiniteScroll>
</div>
}
{showPromptLogModal && (
<PromptLogModal
width={width}
currentLogItem={currentLogItem}
onCancel={() => {
setCurrentLogItem()
setShowPromptLogModal(false)
}}
/>
)}
</div>
)
}
/**

View File

@ -1,14 +1,21 @@
import { create } from 'zustand'
import type { App } from '@/types/app'
import type { IChatItem } from '@/app/components/app/chat/type'
type State = {
appDetail?: App
appSidebarExpand: string
currentLogItem?: IChatItem
showPromptLogModal: boolean
showMessageLogModal: boolean
}
type Action = {
setAppDetail: (appDetail?: App) => void
setAppSiderbarExpand: (state: string) => void
setCurrentLogItem: (item?: IChatItem) => void
setShowPromptLogModal: (showPromptLogModal: boolean) => void
setShowMessageLogModal: (showMessageLogModal: boolean) => void
}
export const useStore = create<State & Action>(set => ({
@ -16,4 +23,10 @@ export const useStore = create<State & Action>(set => ({
setAppDetail: appDetail => set(() => ({ appDetail })),
appSidebarExpand: '',
setAppSiderbarExpand: appSidebarExpand => set(() => ({ appSidebarExpand })),
currentLogItem: undefined,
setCurrentLogItem: currentLogItem => set(() => ({ currentLogItem })),
showPromptLogModal: false,
setShowPromptLogModal: showPromptLogModal => set(() => ({ showPromptLogModal })),
showMessageLogModal: false,
setShowMessageLogModal: showMessageLogModal => set(() => ({ showMessageLogModal })),
}))

View File

@ -2,7 +2,7 @@ import type {
FC,
ReactNode,
} from 'react'
import { memo, useEffect, useRef, useState } from 'react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import type {
ChatConfig,
@ -18,7 +18,6 @@ import LoadingAnim from '@/app/components/app/chat/loading-anim'
import Citation from '@/app/components/app/chat/citation'
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
import type { Emoji } from '@/app/components/tools/types'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
type AnswerProps = {
item: ChatItem
@ -50,22 +49,8 @@ const Answer: FC<AnswerProps> = ({
} = item
const hasAgentThoughts = !!agent_thoughts?.length
const [showPromptLogModal, setShowPromptLogModal] = useState(false)
const [width, setWidth] = useState(0)
const ref = useRef<HTMLDivElement>(null)
const adjustModalWidth = () => {
if (ref.current)
setWidth(document.body.clientWidth - (ref.current?.clientWidth + 56 + 16))
}
useEffect(() => {
adjustModalWidth()
}, [])
return (
<div className='flex mb-2 last:mb-0' ref={ref}>
<div className='flex mb-2 last:mb-0'>
<div className='shrink-0 relative w-10 h-10'>
{
answerIcon || (
@ -93,7 +78,6 @@ const Answer: FC<AnswerProps> = ({
question={question}
index={index}
showPromptLog={showPromptLog}
setShowPromptLogModal={setShowPromptLogModal}
/>
)
}
@ -136,13 +120,6 @@ const Answer: FC<AnswerProps> = ({
</div>
<More more={more} />
</div>
{showPromptLogModal && (
<PromptLogModal
width={width}
log={item.log || []}
onCancel={() => setShowPromptLogModal(false)}
/>
)}
</div>
)
}

View File

@ -1,4 +1,4 @@
import type { Dispatch, FC, SetStateAction } from 'react'
import type { FC } from 'react'
import {
memo,
useMemo,
@ -24,14 +24,12 @@ type OperationProps = {
question: string
index: number
showPromptLog?: boolean
setShowPromptLogModal: Dispatch<SetStateAction<boolean>>
}
const Operation: FC<OperationProps> = ({
item,
question,
index,
showPromptLog,
setShowPromptLogModal,
}) => {
const { t } = useTranslation()
const {
@ -79,7 +77,7 @@ const Operation: FC<OperationProps> = ({
<div className='hidden group-hover:flex items-center h-[28px] p-0.5 rounded-lg bg-white border-[0.5px] border-gray-100 shadow-md'>
{showPromptLog && (
<Log runID={item.workflow_run_id} setShowModal={setShowPromptLogModal} />
<Log logItem={item} />
)}
{(!isOpeningStatement && config?.text_to_speech?.enabled) && (
<>

View File

@ -6,6 +6,7 @@ import {
memo,
useEffect,
useRef,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useThrottleEffect } from 'ahooks'
@ -24,6 +25,8 @@ import { ChatContextProvider } from './context'
import type { Emoji } from '@/app/components/tools/types'
import Button from '@/app/components/base/button'
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
import { useStore as useAppStore } from '@/app/components/app/store'
export type ChatProps = {
chatList: ChatItem[]
@ -72,6 +75,8 @@ const Chat: FC<ChatProps> = ({
onFeedback,
}) => {
const { t } = useTranslation()
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal } = useAppStore()
const [width, setWidth] = useState(0)
const chatContainerRef = useRef<HTMLDivElement>(null)
const chatContainerInnerRef = useRef<HTMLDivElement>(null)
const chatFooterRef = useRef<HTMLDivElement>(null)
@ -83,6 +88,9 @@ const Chat: FC<ChatProps> = ({
}
const handleWindowResize = () => {
if (chatContainerRef.current)
setWidth(document.body.clientWidth - (chatContainerRef.current?.clientWidth + 16) - 8)
if (chatContainerRef.current && chatFooterRef.current)
chatFooterRef.current.style.width = `${chatContainerRef.current.clientWidth}px`
@ -215,6 +223,16 @@ const Chat: FC<ChatProps> = ({
}
</div>
</div>
{showPromptLogModal && (
<PromptLogModal
width={width}
currentLogItem={currentLogItem}
onCancel={() => {
setCurrentLogItem()
setShowPromptLogModal(false)
}}
/>
)}
</div>
</ChatContextProvider>
)

View File

@ -4,15 +4,15 @@ import { useClickAway } from 'ahooks'
import Card from './card'
import { CopyFeedbackNew } from '@/app/components/base/copy-feedback'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
import type { VisionFile } from '@/types/app'
import type { IChatItem } from '@/app/components/app/chat/type'
type PromptLogModalProps = {
log: { role: string; text: string; files?: VisionFile[] }[]
currentLogItem?: IChatItem
width: number
onCancel: () => void
}
const PromptLogModal: FC<PromptLogModalProps> = ({
log,
currentLogItem,
width,
onCancel,
}) => {
@ -20,17 +20,15 @@ const PromptLogModal: FC<PromptLogModalProps> = ({
const [mounted, setMounted] = useState(false)
useClickAway(() => {
if (mounted) {
console.log(111)
if (mounted)
onCancel()
}
}, ref)
useEffect(() => {
setMounted(true)
}, [])
if (!log)
if (!currentLogItem || !currentLogItem.log)
return null
return (
@ -43,9 +41,9 @@ const PromptLogModal: FC<PromptLogModalProps> = ({
<div className='text-base font-semibold text-gray-900'>PROMPT LOG</div>
<div className='flex items-center'>
{
log?.length === 1 && (
currentLogItem.log?.length === 1 && (
<>
<CopyFeedbackNew className='w-6 h-6' content={log[0].text} />
<CopyFeedbackNew className='w-6 h-6' content={currentLogItem.log[0].text} />
<div className='mx-2.5 w-[1px] h-[14px] bg-gray-200' />
</>
)
@ -59,7 +57,7 @@ const PromptLogModal: FC<PromptLogModalProps> = ({
</div>
</div>
<div className='grow p-2 overflow-y-auto'>
<Card log={log} />
<Card log={currentLogItem.log} />
</div>
</div>
)

View File

@ -729,7 +729,7 @@ const Main: FC<IMainProps> = ({
title={siteInfo.title}
icon=''
customerIcon={difyIcon}
icon_background={siteInfo.icon_background}
icon_background={siteInfo.icon_background || ''}
isEmbedScene={true}
isMobile={isMobile}
onCreateNewChat={() => handleConversationIdChange('-1')}