Chore: chat log refactor (#5523)

This commit is contained in:
KVOJJJin
2024-06-24 12:29:14 +08:00
committed by GitHub
parent 47a5d4527b
commit 8294e97113
97 changed files with 165 additions and 5249 deletions

View File

@@ -7,7 +7,7 @@ import AppIcon from '@/app/components/base/app-icon'
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'
import LogoSite from '@/app/components/base/logo/logo-site'
const ConfigPanel = () => {
const { t } = useTranslation()
@@ -153,7 +153,7 @@ const ConfigPanel = () => {
{
customConfig?.replace_webapp_logo
? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
: <FootLogo />
: <LogoSite className='!h-5' />
}
</div>
</div>

View File

@@ -10,7 +10,7 @@ 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'
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
const Sidebar = () => {
const { t } = useTranslation()

View File

@@ -0,0 +1,46 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
export type IRenameModalProps = {
isShow: boolean
saveLoading: boolean
name: string
onClose: () => void
onSave: (name: string) => void
}
const RenameModal: FC<IRenameModalProps> = ({
isShow,
saveLoading,
name,
onClose,
onSave,
}) => {
const { t } = useTranslation()
const [tempName, setTempName] = useState(name)
return (
<Modal
title={t('common.chat.renameConversation')}
isShow={isShow}
onClose={onClose}
>
<div className={'mt-6 font-medium text-sm leading-[21px] text-gray-900'}>{t('common.chat.conversationName')}</div>
<input className={'mt-2 w-full rounded-lg h-10 box-border px-3 text-sm leading-10 bg-gray-100'}
value={tempName}
onChange={e => setTempName(e.target.value)}
placeholder={t('common.chat.conversationNamePlaceholder') || ''}
/>
<div className='mt-10 flex justify-end'>
<Button className='mr-2 flex-shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button>
<Button variant='primary' className='flex-shrink-0' onClick={() => onSave(tempName)} loading={saveLoading}>{t('common.operation.save')}</Button>
</div>
</Modal>
)
}
export default React.memo(RenameModal)

View File

@@ -5,7 +5,7 @@ import type {
VisionFile,
} from '../../types'
import { Markdown } from '@/app/components/base/markdown'
import Thought from '@/app/components/app/chat/thought'
import Thought from '@/app/components/base/chat/chat/thought'
import ImageGallery from '@/app/components/base/image-gallery'
import type { Emoji } from '@/app/components/tools/types'

View File

@@ -16,8 +16,8 @@ import More from './more'
import WorkflowProcess from './workflow-process'
import { AnswerTriangle } from '@/app/components/base/icons/src/vender/solid/general'
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
import LoadingAnim from '@/app/components/app/chat/loading-anim'
import Citation from '@/app/components/app/chat/citation'
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
import Citation from '@/app/components/base/chat/chat/citation'
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
import type { Emoji } from '@/app/components/tools/types'
import type { AppData } from '@/models/share'

View File

@@ -8,7 +8,7 @@ import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import type { ChatItem } from '../../types'
import { useChatContext } from '../context'
import CopyBtn from '@/app/components/app/chat/copy-btn'
import CopyBtn from '@/app/components/base/copy-btn'
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
import AudioBtn from '@/app/components/base/audio-btn'
import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
@@ -18,7 +18,7 @@ import {
ThumbsUp,
} from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import Log from '@/app/components/app/chat/log'
import Log from '@/app/components/base/chat/chat/log'
type OperationProps = {
item: ChatItem

View File

@@ -0,0 +1,125 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { RiArrowDownSLine } from '@remixicon/react'
import type { CitationItem } from '../type'
import Popup from './popup'
export type Resources = {
documentId: string
documentName: string
dataSourceType: string
sources: CitationItem[]
}
type CitationProps = {
data: CitationItem[]
showHitInfo?: boolean
containerClassName?: string
}
const Citation: FC<CitationProps> = ({
data,
showHitInfo,
containerClassName = 'chat-answer-container',
}) => {
const { t } = useTranslation()
const elesRef = useRef<HTMLDivElement[]>([])
const [limitNumberInOneLine, setlimitNumberInOneLine] = useState(0)
const [showMore, setShowMore] = useState(false)
const resources = useMemo(() => data.reduce((prev: Resources[], next) => {
const documentId = next.document_id
const documentName = next.document_name
const dataSourceType = next.data_source_type
const documentIndex = prev.findIndex(i => i.documentId === documentId)
if (documentIndex > -1) {
prev[documentIndex].sources.push(next)
}
else {
prev.push({
documentId,
documentName,
dataSourceType,
sources: [next],
})
}
return prev
}, []), [data])
const handleAdjustResourcesLayout = () => {
const containerWidth = document.querySelector(`.${containerClassName}`)!.clientWidth - 40
let totalWidth = 0
for (let i = 0; i < resources.length; i++) {
totalWidth += elesRef.current[i].clientWidth
if (totalWidth + i * 4 > containerWidth!) {
totalWidth -= elesRef.current[i].clientWidth
if (totalWidth + 34 > containerWidth!)
setlimitNumberInOneLine(i - 1)
else
setlimitNumberInOneLine(i)
break
}
else {
setlimitNumberInOneLine(i + 1)
}
}
}
useEffect(() => {
handleAdjustResourcesLayout()
}, [])
const resourcesLength = resources.length
return (
<div className='mt-3 -mb-1'>
<div className='flex items-center mb-2 text-xs font-medium text-gray-500'>
{t('common.chat.citation.title')}
<div className='grow ml-2 h-[1px] bg-black/5' />
</div>
<div className='relative flex flex-wrap'>
{
resources.map((res, index) => (
<div
key={index}
className='absolute top-0 left-0 w-auto mr-1 mb-1 pl-7 pr-2 max-w-[240px] h-7 text-xs whitespace-nowrap opacity-0 -z-10'
ref={ele => (elesRef.current[index] = ele!)}
>
{res.documentName}
</div>
))
}
{
resources.slice(0, showMore ? resourcesLength : limitNumberInOneLine).map((res, index) => (
<div key={index} className='mr-1 mb-1 cursor-pointer'>
<Popup
data={res}
showHitInfo={showHitInfo}
/>
</div>
))
}
{
limitNumberInOneLine < resourcesLength && (
<div
className='flex items-center px-2 h-7 bg-white rounded-lg text-xs font-medium text-gray-500 cursor-pointer'
onClick={() => setShowMore(v => !v)}
>
{
!showMore
? `+ ${resourcesLength - limitNumberInOneLine}`
: <RiArrowDownSLine className='w-4 h-4 text-gray-600 rotate-180' />
}
</div>
)
}
</div>
</div>
)
}
export default Citation

View File

@@ -0,0 +1,129 @@
import { Fragment, useState } from 'react'
import type { FC } from 'react'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import Tooltip from './tooltip'
import ProgressTooltip from './progress-tooltip'
import type { Resources } from './index'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import FileIcon from '@/app/components/base/file-icon'
import {
Hash02,
Target04,
} from '@/app/components/base/icons/src/vender/line/general'
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
import {
BezierCurve03,
TypeSquare,
} from '@/app/components/base/icons/src/vender/line/editor'
type PopupProps = {
data: Resources
showHitInfo?: boolean
}
const Popup: FC<PopupProps> = ({
data,
showHitInfo = false,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const fileType = data.dataSourceType !== 'notion'
? (/\.([^.]*)$/g.exec(data.documentName)?.[1] || '')
: 'notion'
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='top-start'
offset={{
mainAxis: 8,
crossAxis: -2,
}}
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<div className='flex items-center px-2 max-w-[240px] h-7 bg-white rounded-lg'>
<FileIcon type={fileType} className='shrink-0 mr-1 w-4 h-4' />
<div className='text-xs text-gray-600 truncate'>{data.documentName}</div>
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
<div className='w-[360px] bg-gray-50 rounded-xl shadow-lg'>
<div className='px-4 pt-3 pb-2'>
<div className='flex items-center h-[18px]'>
<FileIcon type={fileType} className='shrink-0 mr-1 w-4 h-4' />
<div className='text-xs font-medium text-gray-600 truncate'>{data.documentName}</div>
</div>
</div>
<div className='px-4 py-0.5 max-h-[450px] bg-white rounded-lg overflow-auto'>
{
data.sources.map((source, index) => (
<Fragment key={index}>
<div className='group py-3'>
<div className='flex items-center justify-between mb-2'>
<div className='flex items-center px-1.5 h-5 border border-gray-200 rounded-md'>
<Hash02 className='mr-0.5 w-3 h-3 text-gray-400' />
<div className='text-[11px] font-medium text-gray-500'>
{source.segment_position || index + 1}
</div>
</div>
{
showHitInfo && (
<Link
href={`/datasets/${source.dataset_id}/documents/${source.document_id}`}
className='hidden items-center h-[18px] text-xs text-primary-600 group-hover:flex'>
{t('common.chat.citation.linkToDataset')}
<ArrowUpRight className='ml-1 w-3 h-3' />
</Link>
)
}
</div>
<div className='text-[13px] text-gray-800'>{source.content}</div>
{
showHitInfo && (
<div className='flex items-center mt-2 text-xs font-medium text-gray-500'>
<Tooltip
text={t('common.chat.citation.characters')}
data={source.word_count}
icon={<TypeSquare className='mr-1 w-3 h-3' />}
/>
<Tooltip
text={t('common.chat.citation.hitCount')}
data={source.hit_count}
icon={<Target04 className='mr-1 w-3 h-3' />}
/>
<Tooltip
text={t('common.chat.citation.vectorHash')}
data={source.index_node_hash.substring(0, 7)}
icon={<BezierCurve03 className='mr-1 w-3 h-3' />}
/>
{
source.score && (
<ProgressTooltip data={Number(source.score.toFixed(2))} />
)
}
</div>
)
}
</div>
{
index !== data.sources.length - 1 && (
<div className='my-1 h-[1px] bg-black/5' />
)
}
</Fragment>
))
}
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default Popup

View File

@@ -0,0 +1,46 @@
import { useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
type ProgressTooltipProps = {
data: number
}
const ProgressTooltip: FC<ProgressTooltipProps> = ({
data,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='top-start'
>
<PortalToFollowElemTrigger
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
<div className='grow flex items-center'>
<div className='mr-1 w-16 h-1.5 rounded-[3px] border border-gray-400 overflow-hidden'>
<div className='bg-gray-400 h-full' style={{ width: `${data * 100}%` }}></div>
</div>
{data}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 1001 }}>
<div className='p-3 bg-white text-xs font-medium text-gray-500 rounded-lg shadow-lg'>
{t('common.chat.citation.hitScore')} {data}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default ProgressTooltip

View File

@@ -0,0 +1,46 @@
import React, { useState } from 'react'
import type { FC } from 'react'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
type TooltipProps = {
data: number | string
text: string
icon: React.ReactNode
}
const Tooltip: FC<TooltipProps> = ({
data,
text,
icon,
}) => {
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='top-start'
>
<PortalToFollowElemTrigger
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
<div className='flex items-center mr-6'>
{icon}
{data}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 1001 }}>
<div className='p-3 bg-white text-xs font-medium text-gray-500 rounded-lg shadow-lg'>
{text} {data}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default Tooltip

View File

@@ -263,6 +263,9 @@ const Chat: FC<ChatProps> = ({
/>
)
}
{appData && appData.site.custom_disclaimer && <div className='text-xs text-gray-500 mt-1 text-center'>
{appData.site.custom_disclaimer}
</div>}
</div>
</div>
{showPromptLogModal && (

View File

@@ -0,0 +1,17 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import s from './style.module.css'
export type ILoaidingAnimProps = {
type: 'text' | 'avatar'
}
const LoaidingAnim: FC<ILoaidingAnimProps> = ({
type,
}) => {
return (
<div className={`${s['dot-flashing']} ${s[type]}`}></div>
)
}
export default React.memo(LoaidingAnim)

View File

@@ -0,0 +1,82 @@
.dot-flashing {
position: relative;
animation: 1s infinite linear alternate;
animation-delay: 0.5s;
}
.dot-flashing::before,
.dot-flashing::after {
content: "";
display: inline-block;
position: absolute;
top: 0;
animation: 1s infinite linear alternate;
}
.dot-flashing::before {
animation-delay: 0s;
}
.dot-flashing::after {
animation-delay: 1s;
}
@keyframes dot-flashing {
0% {
background-color: #667085;
}
50%,
100% {
background-color: rgba(102, 112, 133, 0.3);
}
}
@keyframes dot-flashing-avatar {
0% {
background-color: #155EEF;
}
50%,
100% {
background-color: rgba(21, 94, 239, 0.3);
}
}
.text,
.text::before,
.text::after {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #667085;
color: #667085;
animation-name: dot-flashing;
}
.text::before {
left: -7px;
}
.text::after {
left: 7px;
}
.avatar,
.avatar::before,
.avatar::after {
width: 2px;
height: 2px;
border-radius: 50%;
background-color: #155EEF;
color: #155EEF;
animation-name: dot-flashing-avatar;
}
.avatar::before {
left: -5px;
}
.avatar::after {
left: 5px;
}

View File

@@ -0,0 +1,42 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { File02 } from '@/app/components/base/icons/src/vender/line/files'
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import { useStore as useAppStore } from '@/app/components/app/store'
type LogProps = {
logItem: IChatItem
}
const Log: FC<LogProps> = ({
logItem,
}) => {
const { t } = useTranslation()
const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem)
const setShowPromptLogModal = useAppStore(s => s.setShowPromptLogModal)
const setShowAgentLogModal = useAppStore(s => s.setShowAgentLogModal)
const setShowMessageLogModal = useAppStore(s => s.setShowMessageLogModal)
const { workflow_run_id: runID, agent_thoughts } = logItem
const isAgent = agent_thoughts && agent_thoughts.length > 0
return (
<div
className='shrink-0 p-1 flex items-center justify-center rounded-[6px] font-medium text-gray-500 hover:bg-gray-50 cursor-pointer hover:text-gray-700'
onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
setCurrentLogItem(logItem)
if (runID)
setShowMessageLogModal(true)
else if (isAgent)
setShowAgentLogModal(true)
else
setShowPromptLogModal(true)
}}
>
<File02 className='mr-1 w-4 h-4' />
<div className='text-xs leading-4'>{runID ? t('appLog.viewLog') : isAgent ? t('appLog.agentLog') : t('appLog.promptLog')}</div>
</div>
)
}
export default Log

View File

@@ -0,0 +1,69 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useContext } from 'use-context-selector'
import type { ThoughtItem, ToolInfoInThought } from '../type'
import Tool from '@/app/components/base/chat/chat/thought/tool'
import type { Emoji } from '@/app/components/tools/types'
import I18n from '@/context/i18n'
import { getLanguage } from '@/i18n/language'
export type IThoughtProps = {
thought: ThoughtItem
allToolIcons: Record<string, string | Emoji>
isFinished: boolean
}
function getValue(value: string, isValueArray: boolean, index: number) {
if (isValueArray) {
try {
return JSON.parse(value)[index]
}
catch (e) {
}
}
return value
}
const Thought: FC<IThoughtProps> = ({
thought,
allToolIcons,
isFinished,
}) => {
const { locale } = useContext(I18n)
const language = getLanguage(locale)
const [toolNames, isValueArray]: [string[], boolean] = (() => {
try {
if (Array.isArray(JSON.parse(thought.tool)))
return [JSON.parse(thought.tool), true]
}
catch (e) {
}
return [[thought.tool], false]
})()
const toolThoughtList = toolNames.map((toolName, index) => {
return {
name: toolName,
label: thought.tool_labels?.toolName?.language ?? toolName,
input: getValue(thought.tool_input, isValueArray, index),
output: getValue(thought.observation, isValueArray, index),
isFinished,
}
})
return (
<div className='my-2 space-y-2'>
{toolThoughtList.map((item: ToolInfoInThought, index) => (
<Tool
key={index}
payload={item}
allToolIcons={allToolIcons}
/>
))}
</div>
)
}
export default React.memo(Thought)

View File

@@ -0,0 +1,28 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
type Props = {
isRequest: boolean
toolName: string
content: string
}
const Panel: FC<Props> = ({
isRequest,
toolName,
content,
}) => {
const { t } = useTranslation()
return (
<div className='rounded-md bg-gray-100 overflow-hidden border border-black/5'>
<div className='flex items-center px-2 py-1 leading-[18px] bg-gray-50 uppercase text-xs font-medium text-gray-500'>
{t(`tools.thought.${isRequest ? 'requestTitle' : 'responseTitle'}`)} {toolName}
</div>
<div className='p-2 border-t border-black/5 leading-4 text-xs text-gray-700'>{content}</div>
</div>
)
}
export default React.memo(Panel)

View File

@@ -0,0 +1,106 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import {
RiArrowDownSLine,
RiLoader2Line,
} from '@remixicon/react'
import type { ToolInfoInThought } from '../type'
import Panel from './panel'
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { DataSet as DataSetIcon } from '@/app/components/base/icons/src/public/thought'
import type { Emoji } from '@/app/components/tools/types'
import AppIcon from '@/app/components/base/app-icon'
type Props = {
payload: ToolInfoInThought
allToolIcons?: Record<string, string | Emoji>
}
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
if (toolName.startsWith('dataset_'))
return <DataSetIcon className='shrink-0'></DataSetIcon>
const icon = allToolIcons[toolName]
if (!icon)
return null
return (
typeof icon === 'string'
? (
<div
className='w-3 h-3 bg-cover bg-center rounded-[3px] shrink-0'
style={{
backgroundImage: `url(${icon})`,
}}
></div>
)
: (
<AppIcon
className='rounded-[3px] shrink-0'
size='xs'
icon={icon?.content}
background={icon?.background}
/>
))
}
const Tool: FC<Props> = ({
payload,
allToolIcons = {},
}) => {
const { t } = useTranslation()
const { name, label, input, isFinished, output } = payload
const toolName = name.startsWith('dataset_') ? t('dataset.knowledge') : name
const toolLabel = name.startsWith('dataset_') ? t('dataset.knowledge') : label
const [isShowDetail, setIsShowDetail] = useState(false)
const icon = getIcon(name, allToolIcons) as any
return (
<div>
<div className={cn(!isShowDetail && 'shadow-sm', !isShowDetail && 'inline-block', 'max-w-full overflow-x-auto bg-white rounded-md')}>
<div
className={cn('flex items-center h-7 px-2 cursor-pointer')}
onClick={() => setIsShowDetail(!isShowDetail)}
>
{!isFinished && (
<RiLoader2Line className='w-3 h-3 text-gray-500 animate-spin shrink-0' />
)}
{isFinished && !isShowDetail && (
<CheckCircle className='w-3 h-3 text-[#12B76A] shrink-0' />
)}
{isFinished && isShowDetail && (
icon
)}
<span className='mx-1 text-xs font-medium text-gray-500 shrink-0'>
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
</span>
<span
className='text-xs font-medium text-gray-700 truncate'
title={toolLabel}
>
{toolLabel}
</span>
<RiArrowDownSLine
className={cn(isShowDetail && 'rotate-180', 'ml-1 w-3 h-3 text-gray-500 select-none cursor-pointer shrink-0')}
/>
</div>
{isShowDetail && (
<div className='border-t border-black/5 p-2 space-y-2 '>
<Panel
isRequest={true}
toolName={toolName}
content={input} />
{output && (
<Panel
isRequest={false}
toolName={toolName}
content={output as string} />
)}
</div>
)}
</div>
</div>
)
}
export default React.memo(Tool)

View File

@@ -0,0 +1,122 @@
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Annotation, MessageRating } from '@/models/log'
import type { VisionFile } from '@/types/app'
export type MessageMore = {
time: string
tokens: number
latency: number | string
}
export type Feedbacktype = {
rating: MessageRating
content?: string | null
}
export type FeedbackFunc = (messageId: string, feedback: Feedbacktype) => Promise<any>
export type SubmitAnnotationFunc = (messageId: string, content: string) => Promise<any>
export type DisplayScene = 'web' | 'console'
export type ToolInfoInThought = {
name: string
label: string
input: string
output: string
isFinished: boolean
}
export type ThoughtItem = {
id: string
tool: string // plugin or dataset. May has multi.
thought: string
tool_input: string
tool_labels?: { [key: string]: TypeWithI18N }
message_id: string
observation: string
position: number
files?: string[]
message_files?: VisionFile[]
}
export type CitationItem = {
content: string
data_source_type: string
dataset_name: string
dataset_id: string
document_id: string
document_name: string
hit_count: number
index_node_hash: string
segment_id: string
segment_position: number
score: number
word_count: number
}
export type IChatItem = {
id: string
content: string
citation?: CitationItem[]
/**
* Specific message type
*/
isAnswer: boolean
/**
* The user feedback result of this message
*/
feedback?: Feedbacktype
/**
* The admin feedback result of this message
*/
adminFeedback?: Feedbacktype
/**
* Whether to hide the feedback area
*/
feedbackDisabled?: boolean
/**
* More information about this message
*/
more?: MessageMore
annotation?: Annotation
useCurrentUserAvatar?: boolean
isOpeningStatement?: boolean
suggestedQuestions?: string[]
log?: { role: string; text: string; files?: VisionFile[] }[]
agent_thoughts?: ThoughtItem[]
message_files?: VisionFile[]
workflow_run_id?: string
// for agent log
conversationId?: string
input?: any
}
export type MessageEnd = {
id: string
metadata: {
retriever_resources?: CitationItem[]
annotation_reply: {
id: string
account: {
id: string
name: string
}
}
}
}
export type MessageReplace = {
id: string
task_id: string
answer: string
conversation_id: string
}
export type AnnotationReply = {
id: string
task_id: string
answer: string
conversation_id: string
annotation_id: string
annotation_author_name: string
}

View File

@@ -8,7 +8,7 @@ import AppIcon from '@/app/components/base/app-icon'
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'
import LogoSite from '@/app/components/base/logo/logo-site'
const ConfigPanel = () => {
const { t } = useTranslation()
@@ -154,7 +154,7 @@ const ConfigPanel = () => {
{
customConfig?.replace_webapp_logo
? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
: <FootLogo />
: <LogoSite className='!h-5' />
}
</div>
</div>

View File

@@ -1,24 +1,19 @@
import type { FC } from 'react'
import React from 'react'
import { RiRefreshLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
// import AppIcon from '@/app/components/base/app-icon'
import { ReplayIcon } from '@/app/components/app/chat/icon-component'
import Tooltip from '@/app/components/base/tooltip'
export type IHeaderProps = {
isMobile?: boolean
customerIcon?: React.ReactNode
title: string
// icon: string
// icon_background: string
onCreateNewChat?: () => void
}
const Header: FC<IHeaderProps> = ({
isMobile,
customerIcon,
title,
// icon,
// icon_background,
onCreateNewChat,
}) => {
const { t } = useTranslation()
@@ -48,7 +43,7 @@ const Header: FC<IHeaderProps> = ({
<div className='flex cursor-pointer hover:rounded-lg hover:bg-black/5 w-8 h-8 items-center justify-center' onClick={() => {
onCreateNewChat?.()
}}>
<ReplayIcon className="h-4 w-4 text-sm font-bold text-white" />
<RiRefreshLine className="h-4 w-4 text-sm font-bold text-white" />
</div>
</Tooltip>
</div>

View File

@@ -3,7 +3,7 @@ import type {
VisionFile,
VisionSettings,
} from '@/types/app'
import type { IChatItem } from '@/app/components/app/chat/type'
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import type { NodeTracing } from '@/types/workflow'
import type { WorkflowRunningStatus } from '@/app/components/workflow/types'