Feat/chat add origin (#1130)
This commit is contained in:
parent
6effcd3755
commit
84c76bc04a
74 changed files with 2454 additions and 28 deletions
|
|
@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { useContext } from 'use-context-selector'
|
||||
import { UserCircleIcon } from '@heroicons/react/24/solid'
|
||||
import cn from 'classnames'
|
||||
import type { DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc, ThoughtItem } from '../type'
|
||||
import type { CitationItem, DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc, ThoughtItem } from '../type'
|
||||
import OperationBtn from '../operation'
|
||||
import LoadingAnim from '../loading-anim'
|
||||
import { EditIcon, EditIconSolid, OpeningStatementIcon, RatingIcon } from '../icon-component'
|
||||
|
|
@ -13,6 +13,7 @@ import s from '../style.module.css'
|
|||
import MoreInfo from '../more-info'
|
||||
import CopyBtn from '../copy-btn'
|
||||
import Thought from '../thought'
|
||||
import Citation from '../citation'
|
||||
import { randomString } from '@/utils'
|
||||
import type { Annotation, MessageRating } from '@/models/log'
|
||||
import AppContext from '@/context/app-context'
|
||||
|
|
@ -45,11 +46,14 @@ export type IAnswerProps = {
|
|||
isResponsing?: boolean
|
||||
answerIconClassName?: string
|
||||
thoughts?: ThoughtItem[]
|
||||
citation?: CitationItem[]
|
||||
isThinking?: boolean
|
||||
dataSets?: DataSet[]
|
||||
isShowCitation?: boolean
|
||||
isShowCitationHitInfo?: boolean
|
||||
}
|
||||
// The component needs to maintain its own state to control whether to display input component
|
||||
const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName, thoughts, isThinking, dataSets }) => {
|
||||
const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName, thoughts, citation, isThinking, dataSets, isShowCitation, isShowCitationHitInfo = false }) => {
|
||||
const { id, content, more, feedback, adminFeedback, annotation: initAnnotation } = item
|
||||
const [showEdit, setShowEdit] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
|
@ -171,7 +175,7 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className={s.answerWrapWrap}>
|
||||
<div className={cn(s.answerWrapWrap, 'chat-answer-container')}>
|
||||
<div className={`${s.answerWrap} ${showEdit ? 'w-full' : ''}`}>
|
||||
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
||||
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
|
||||
|
|
@ -237,6 +241,11 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
|
|||
</div>
|
||||
</>
|
||||
}
|
||||
{
|
||||
!!citation?.length && !isThinking && isShowCitation && !isResponsing && (
|
||||
<Citation data={citation} showHitInfo={isShowCitationHitInfo} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
|
||||
{!item.isOpeningStatement && (
|
||||
|
|
|
|||
123
web/app/components/app/chat/citation/index.tsx
Normal file
123
web/app/components/app/chat/citation/index.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { CitationItem } from '../type'
|
||||
import Popup from './popup'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
export type Resources = {
|
||||
documentId: string
|
||||
documentName: string
|
||||
dataSourceType: string
|
||||
sources: CitationItem[]
|
||||
}
|
||||
|
||||
type CitationProps = {
|
||||
data: CitationItem[]
|
||||
showHitInfo?: boolean
|
||||
}
|
||||
const Citation: FC<CitationProps> = ({
|
||||
data,
|
||||
showHitInfo,
|
||||
}) => {
|
||||
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('.chat-answer-container')!.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}`
|
||||
: <ChevronDown className='w-4 h-4 text-gray-600 rotate-180' />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Citation
|
||||
123
web/app/components/app/chat/citation/popup.tsx
Normal file
123
web/app/components/app/chat/citation/popup.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
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 === 'upload_file'
|
||||
? (/\.([^.]*)$/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='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='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'>
|
||||
{
|
||||
showHitInfo && (
|
||||
<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}</div>
|
||||
</div>
|
||||
<Link
|
||||
href={`/datasets/${source.dataset_id}/documents/${source.document_id}`}
|
||||
className='hidden items-center h-[18px] text-xs text-primary-600 group-hover:flex'>
|
||||
Link to dataset
|
||||
<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' />}
|
||||
/>
|
||||
<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
|
||||
46
web/app/components/app/chat/citation/progress-tooltip.tsx
Normal file
46
web/app/components/app/chat/citation/progress-tooltip.tsx
Normal 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
|
||||
46
web/app/components/app/chat/citation/tooltip.tsx
Normal file
46
web/app/components/app/chat/citation/tooltip.tsx
Normal 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
|
||||
|
|
@ -48,9 +48,11 @@ export type IChatProps = {
|
|||
isShowSuggestion?: boolean
|
||||
suggestionList?: string[]
|
||||
isShowSpeechToText?: boolean
|
||||
isShowCitation?: boolean
|
||||
answerIconClassName?: string
|
||||
isShowConfigElem?: boolean
|
||||
dataSets?: DataSet[]
|
||||
isShowCitationHitInfo?: boolean
|
||||
}
|
||||
|
||||
const Chat: FC<IChatProps> = ({
|
||||
|
|
@ -74,9 +76,11 @@ const Chat: FC<IChatProps> = ({
|
|||
isShowSuggestion,
|
||||
suggestionList,
|
||||
isShowSpeechToText,
|
||||
isShowCitation,
|
||||
answerIconClassName,
|
||||
isShowConfigElem,
|
||||
dataSets,
|
||||
isShowCitationHitInfo,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
|
|
@ -162,6 +166,7 @@ const Chat: FC<IChatProps> = ({
|
|||
if (item.isAnswer) {
|
||||
const isLast = item.id === chatList[chatList.length - 1].id
|
||||
const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]')
|
||||
const citation = item.citation
|
||||
const isThinking = !item.content && item.agent_thoughts && item.agent_thoughts?.length > 0 && !item.agent_thoughts.some(item => item.thought === '[DONE]')
|
||||
return <Answer
|
||||
key={item.id}
|
||||
|
|
@ -174,8 +179,11 @@ const Chat: FC<IChatProps> = ({
|
|||
isResponsing={isResponsing && isLast}
|
||||
answerIconClassName={answerIconClassName}
|
||||
thoughts={thoughts}
|
||||
citation={citation}
|
||||
isThinking={isThinking}
|
||||
dataSets={dataSets}
|
||||
isShowCitation={isShowCitation}
|
||||
isShowCitationHitInfo={isShowCitationHitInfo}
|
||||
/>
|
||||
}
|
||||
return <Question key={item.id} id={item.id} content={item.content} more={item.more} useCurrentUserAvatar={useCurrentUserAvatar} />
|
||||
|
|
|
|||
|
|
@ -23,10 +23,26 @@ export type ThoughtItem = {
|
|||
tool_input: string
|
||||
message_id: string
|
||||
}
|
||||
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
|
||||
agent_thoughts?: ThoughtItem[]
|
||||
citation?: CitationItem[]
|
||||
/**
|
||||
* Specific message type
|
||||
*/
|
||||
|
|
@ -51,3 +67,8 @@ export type IChatItem = {
|
|||
useCurrentUserAvatar?: boolean
|
||||
isOpeningStatement?: boolean
|
||||
}
|
||||
|
||||
export type MessageEnd = {
|
||||
id: string
|
||||
retriever_resources?: CitationItem[]
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 175 KiB |
|
|
@ -26,4 +26,8 @@
|
|||
|
||||
.speechToTextPreview {
|
||||
background-image: url(./preview-imgs/speech-to-text.svg);
|
||||
}
|
||||
|
||||
.citationPreview {
|
||||
background-image: url(./preview-imgs/citation.svg);
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@ import FeatureItem from './feature-item'
|
|||
import Modal from '@/app/components/base/modal'
|
||||
import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon'
|
||||
import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
import { Citations } from '@/app/components/base/icons/src/vender/solid/editor'
|
||||
type IConfig = {
|
||||
openingStatement: boolean
|
||||
moreLikeThis: boolean
|
||||
suggestedQuestionsAfterAnswer: boolean
|
||||
speechToText: boolean
|
||||
citation: boolean
|
||||
}
|
||||
|
||||
export type IChooseFeatureProps = {
|
||||
|
|
@ -85,6 +87,14 @@ const ChooseFeature: FC<IChooseFeatureProps> = ({
|
|||
/>
|
||||
)
|
||||
}
|
||||
<FeatureItem
|
||||
icon={<Citations className='w-4 h-4 text-[#FD853A]' />}
|
||||
previewImgClassName='citationPreview'
|
||||
title={t('appDebug.feature.citation.title')}
|
||||
description={t('appDebug.feature.citation.description')}
|
||||
value={config.citation}
|
||||
onChange={value => onChange('citation', value)}
|
||||
/>
|
||||
</>
|
||||
</FeatureGroup>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ function useFeature({
|
|||
setSuggestedQuestionsAfterAnswer,
|
||||
speechToText,
|
||||
setSpeechToText,
|
||||
citation,
|
||||
setCitation,
|
||||
}: {
|
||||
introduction: string
|
||||
setIntroduction: (introduction: string) => void
|
||||
|
|
@ -18,6 +20,8 @@ function useFeature({
|
|||
setSuggestedQuestionsAfterAnswer: (suggestedQuestionsAfterAnswer: boolean) => void
|
||||
speechToText: boolean
|
||||
setSpeechToText: (speechToText: boolean) => void
|
||||
citation: boolean
|
||||
setCitation: (citation: boolean) => void
|
||||
}) {
|
||||
const [tempshowOpeningStatement, setTempShowOpeningStatement] = React.useState(!!introduction)
|
||||
useEffect(() => {
|
||||
|
|
@ -36,6 +40,7 @@ function useFeature({
|
|||
moreLikeThis,
|
||||
suggestedQuestionsAfterAnswer,
|
||||
speechToText,
|
||||
citation,
|
||||
}
|
||||
const handleFeatureChange = (key: string, value: boolean) => {
|
||||
switch (key) {
|
||||
|
|
@ -53,6 +58,9 @@ function useFeature({
|
|||
break
|
||||
case 'speechToText':
|
||||
setSpeechToText(value)
|
||||
break
|
||||
case 'citation':
|
||||
setCitation(value)
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ const Config: FC = () => {
|
|||
setSuggestedQuestionsAfterAnswerConfig,
|
||||
speechToTextConfig,
|
||||
setSpeechToTextConfig,
|
||||
citationConfig,
|
||||
setCitationConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const isChatApp = mode === AppType.chat
|
||||
const { speech2textDefaultModel } = useProviderContext()
|
||||
|
|
@ -88,9 +90,15 @@ const Config: FC = () => {
|
|||
draft.enabled = value
|
||||
}))
|
||||
},
|
||||
citation: citationConfig.enabled,
|
||||
setCitation: (value) => {
|
||||
setCitationConfig(produce(citationConfig, (draft) => {
|
||||
draft.enabled = value
|
||||
}))
|
||||
},
|
||||
})
|
||||
|
||||
const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && !!speech2textDefaultModel))
|
||||
const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && !!speech2textDefaultModel) || featureConfig.citation)
|
||||
const hasToolbox = false
|
||||
|
||||
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
|
||||
|
|
@ -161,6 +169,7 @@ const Config: FC = () => {
|
|||
}
|
||||
isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer}
|
||||
isShowSpeechText={featureConfig.speechToText && !!speech2textDefaultModel}
|
||||
isShowCitation={featureConfig.citation}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ const Debug: FC<IDebug> = ({
|
|||
introduction,
|
||||
suggestedQuestionsAfterAnswerConfig,
|
||||
speechToTextConfig,
|
||||
citationConfig,
|
||||
moreLikeThisConfig,
|
||||
inputs,
|
||||
// setInputs,
|
||||
|
|
@ -162,6 +163,7 @@ const Debug: FC<IDebug> = ({
|
|||
},
|
||||
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
|
||||
speech_to_text: speechToTextConfig,
|
||||
retriever_resource: citationConfig,
|
||||
agent_mode: {
|
||||
enabled: true,
|
||||
tools: [...postDatasets],
|
||||
|
|
@ -199,7 +201,7 @@ const Debug: FC<IDebug> = ({
|
|||
setChatList(newList)
|
||||
|
||||
// answer
|
||||
const responseItem = {
|
||||
const responseItem: IChatItem = {
|
||||
id: `${Date.now()}`,
|
||||
content: '',
|
||||
isAnswer: true,
|
||||
|
|
@ -266,6 +268,19 @@ const Debug: FC<IDebug> = ({
|
|||
setIsShowSuggestion(true)
|
||||
}
|
||||
},
|
||||
onMessageEnd: (messageEnd) => {
|
||||
responseItem.citation = messageEnd.retriever_resources
|
||||
|
||||
const newListWithAnswer = produce(
|
||||
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
||||
(draft) => {
|
||||
if (!draft.find(item => item.id === questionId))
|
||||
draft.push({ ...questionItem })
|
||||
|
||||
draft.push({ ...responseItem })
|
||||
})
|
||||
setChatList(newListWithAnswer)
|
||||
},
|
||||
onError() {
|
||||
setResponsingFalse()
|
||||
// role back placeholder answer
|
||||
|
|
@ -312,6 +327,7 @@ const Debug: FC<IDebug> = ({
|
|||
opening_statement: introduction,
|
||||
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
|
||||
speech_to_text: speechToTextConfig,
|
||||
retriever_resource: citationConfig,
|
||||
more_like_this: moreLikeThisConfig,
|
||||
agent_mode: {
|
||||
enabled: true,
|
||||
|
|
@ -391,6 +407,8 @@ const Debug: FC<IDebug> = ({
|
|||
isShowSuggestion={doShowSuggestion}
|
||||
suggestionList={suggestQuestions}
|
||||
isShowSpeechToText={speechToTextConfig.enabled && !!speech2textDefaultModel}
|
||||
isShowCitation={citationConfig.enabled}
|
||||
isShowCitationHitInfo
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
'use client'
|
||||
import React, { type FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import { Citations } from '@/app/components/base/icons/src/vender/solid/editor'
|
||||
|
||||
const Citation: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<div className='flex items-center gap-2'>
|
||||
<div>{t('appDebug.feature.citation.title')}</div>
|
||||
</div>
|
||||
}
|
||||
headerIcon={<Citations className='w-4 h-4 text-[#FD853A]' />}
|
||||
headerRight={
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.feature.citation.resDes')}</div>
|
||||
}
|
||||
noBodySpacing
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(Citation)
|
||||
|
|
@ -7,6 +7,7 @@ import type { IOpeningStatementProps } from './opening-statement'
|
|||
import OpeningStatement from './opening-statement'
|
||||
import SuggestedQuestionsAfterAnswer from './suggested-questions-after-answer'
|
||||
import SpeechToText from './speech-to-text'
|
||||
import Citation from './citation'
|
||||
|
||||
/*
|
||||
* Include
|
||||
|
|
@ -19,12 +20,14 @@ type ChatGroupProps = {
|
|||
openingStatementConfig: IOpeningStatementProps
|
||||
isShowSuggestedQuestionsAfterAnswer: boolean
|
||||
isShowSpeechText: boolean
|
||||
isShowCitation: boolean
|
||||
}
|
||||
const ChatGroup: FC<ChatGroupProps> = ({
|
||||
isShowOpeningStatement,
|
||||
openingStatementConfig,
|
||||
isShowSuggestedQuestionsAfterAnswer,
|
||||
isShowSpeechText,
|
||||
isShowCitation,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
@ -43,6 +46,11 @@ const ChatGroup: FC<ChatGroupProps> = ({
|
|||
<SpeechToText />
|
||||
)
|
||||
}
|
||||
{
|
||||
isShowCitation && (
|
||||
<Citation />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ const Configuration: FC = () => {
|
|||
const [speechToTextConfig, setSpeechToTextConfig] = useState<MoreLikeThisConfig>({
|
||||
enabled: false,
|
||||
})
|
||||
const [citationConfig, setCitationConfig] = useState<MoreLikeThisConfig>({
|
||||
enabled: false,
|
||||
})
|
||||
const [formattingChanged, setFormattingChanged] = useState(false)
|
||||
const [inputs, setInputs] = useState<Inputs>({})
|
||||
const [query, setQuery] = useState('')
|
||||
|
|
@ -77,6 +80,7 @@ const Configuration: FC = () => {
|
|||
more_like_this: null,
|
||||
suggested_questions_after_answer: null,
|
||||
speech_to_text: null,
|
||||
retriever_resource: null,
|
||||
dataSets: [],
|
||||
})
|
||||
|
||||
|
|
@ -110,6 +114,9 @@ const Configuration: FC = () => {
|
|||
setSpeechToTextConfig(modelConfig.speech_to_text || {
|
||||
enabled: false,
|
||||
})
|
||||
setCitationConfig(modelConfig.retriever_resource || {
|
||||
enabled: false,
|
||||
})
|
||||
}
|
||||
|
||||
const { textGenerationModelList } = useProviderContext()
|
||||
|
|
@ -159,6 +166,9 @@ const Configuration: FC = () => {
|
|||
if (modelConfig.speech_to_text)
|
||||
setSpeechToTextConfig(modelConfig.speech_to_text)
|
||||
|
||||
if (modelConfig.retriever_resource)
|
||||
setCitationConfig(modelConfig.retriever_resource)
|
||||
|
||||
const config = {
|
||||
modelConfig: {
|
||||
provider: model.provider,
|
||||
|
|
@ -171,6 +181,7 @@ const Configuration: FC = () => {
|
|||
more_like_this: modelConfig.more_like_this,
|
||||
suggested_questions_after_answer: modelConfig.suggested_questions_after_answer,
|
||||
speech_to_text: modelConfig.speech_to_text,
|
||||
retriever_resource: modelConfig.retriever_resource,
|
||||
dataSets: datasets || [],
|
||||
},
|
||||
completionParams: model.completion_params,
|
||||
|
|
@ -202,6 +213,7 @@ const Configuration: FC = () => {
|
|||
more_like_this: moreLikeThisConfig,
|
||||
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
|
||||
speech_to_text: speechToTextConfig,
|
||||
retriever_resource: citationConfig,
|
||||
agent_mode: {
|
||||
enabled: true,
|
||||
tools: [...postDatasets],
|
||||
|
|
@ -219,6 +231,7 @@ const Configuration: FC = () => {
|
|||
draft.more_like_this = moreLikeThisConfig
|
||||
draft.suggested_questions_after_answer = suggestedQuestionsAfterAnswerConfig
|
||||
draft.speech_to_text = speechToTextConfig
|
||||
draft.retriever_resource = citationConfig
|
||||
draft.dataSets = dataSets
|
||||
})
|
||||
setPublishedConfig({
|
||||
|
|
@ -263,6 +276,8 @@ const Configuration: FC = () => {
|
|||
setSuggestedQuestionsAfterAnswerConfig,
|
||||
speechToTextConfig,
|
||||
setSpeechToTextConfig,
|
||||
citationConfig,
|
||||
setCitationConfig,
|
||||
formattingChanged,
|
||||
setFormattingChanged,
|
||||
inputs,
|
||||
|
|
|
|||
48
web/app/components/base/file-icon/index.tsx
Normal file
48
web/app/components/base/file-icon/index.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import type { FC } from 'react'
|
||||
import {
|
||||
Csv,
|
||||
Html,
|
||||
Json,
|
||||
Md,
|
||||
Pdf,
|
||||
Txt,
|
||||
Unknow,
|
||||
Xlsx,
|
||||
} from '@/app/components/base/icons/src/public/files'
|
||||
import { Notion } from '@/app/components/base/icons/src/public/common'
|
||||
|
||||
type FileIconProps = {
|
||||
type: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const FileIcon: FC<FileIconProps> = ({
|
||||
type,
|
||||
className,
|
||||
}) => {
|
||||
switch (type) {
|
||||
case 'csv':
|
||||
return <Csv className={className} />
|
||||
case 'htm':
|
||||
case 'html':
|
||||
return <Html className={className} />
|
||||
case 'json':
|
||||
return <Json className={className} />
|
||||
case 'md':
|
||||
case 'markdown':
|
||||
return <Md className={className} />
|
||||
case 'pdf':
|
||||
return <Pdf className={className} />
|
||||
case 'txt':
|
||||
return <Txt className={className} />
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
return <Xlsx className={className} />
|
||||
case 'notion':
|
||||
return <Notion className={className} />
|
||||
default:
|
||||
return <Unknow className={className} />
|
||||
}
|
||||
}
|
||||
|
||||
export default FileIcon
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5364_42310)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5725 18.2611L1.4229 15.5832C0.905706 14.9389 0.625 14.1466 0.625 13.3312V3.63437C0.625 2.4129 1.60224 1.39936 2.86295 1.31328L12.8326 0.632614C13.5569 0.583164 14.2768 0.775682 14.8717 1.17794L18.3745 3.5462C19.0015 3.97012 19.375 4.66312 19.375 5.40266V16.427C19.375 17.6223 18.4141 18.6121 17.1798 18.688L6.11458 19.3692C5.12958 19.4298 4.17749 19.0148 3.5725 18.2611Z" fill="white"/>
|
||||
<path d="M7.03006 8.48663V8.35968C7.03006 8.03787 7.28779 7.77098 7.61997 7.7488L10.0396 7.58726L13.3857 12.5146V8.19003L12.5244 8.07522V8.01492C12.5244 7.68933 12.788 7.42068 13.1244 7.40344L15.326 7.29066V7.60749C15.326 7.75622 15.2154 7.88343 15.0638 7.90907L14.534 7.99868V15.0022L13.8691 15.2309C13.3136 15.4219 12.6952 15.2174 12.3772 14.7376L9.12879 9.83568V14.5143L10.1287 14.7056L10.1147 14.7984C10.0711 15.0889 9.82028 15.3086 9.51687 15.3221L7.03006 15.4328C6.99718 15.1204 7.23132 14.8409 7.55431 14.807L7.88143 14.7726V8.53447L7.03006 8.48663Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9218 1.85418L2.95217 2.53485C2.35499 2.57562 1.89209 3.05572 1.89209 3.63431V13.3311C1.89209 13.8748 2.07923 14.4029 2.42402 14.8325L4.57362 17.5104C4.92117 17.9433 5.46812 18.1817 6.03397 18.1469L17.0991 17.4658C17.6663 17.4309 18.1078 16.9761 18.1078 16.4269V5.4026C18.1078 5.06281 17.9362 4.74441 17.6481 4.54963L14.1453 2.18137C13.7883 1.94002 13.3564 1.82451 12.9218 1.85418ZM3.44654 3.78556C3.30788 3.6829 3.37387 3.46903 3.54806 3.45654L12.9889 2.77938C13.2897 2.75781 13.5886 2.84064 13.8318 3.01299L15.7261 4.35502C15.798 4.40597 15.7642 4.51596 15.6752 4.5208L5.67742 5.06454C5.37485 5.081 5.0762 4.99211 4.83563 4.814L3.44654 3.78556ZM5.20848 6.76913C5.20848 6.44433 5.47088 6.17604 5.80642 6.15777L16.3769 5.5821C16.7039 5.56429 16.9792 5.81577 16.9792 6.13232V15.6782C16.9792 16.0024 16.7177 16.2705 16.3829 16.2895L5.8793 16.8871C5.51537 16.9079 5.20848 16.6282 5.20848 16.2759V6.76913Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5364_42310">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
23
web/app/components/base/icons/assets/public/files/html.svg
Normal file
23
web/app/components/base/icons/assets/public/files/html.svg
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_3055_14424)">
|
||||
<path d="M4 7.73349C4 5.49329 4 4.37318 4.43597 3.51753C4.81947 2.76489 5.43139 2.15296 6.18404 1.76947C7.03969 1.3335 8.15979 1.3335 10.4 1.3335H18.6667L28 10.6668V24.2668C28 26.507 28 27.6271 27.564 28.4828C27.1805 29.2354 26.5686 29.8474 25.816 30.2309C24.9603 30.6668 23.8402 30.6668 21.6 30.6668H10.4C8.15979 30.6668 7.03969 30.6668 6.18404 30.2309C5.43139 29.8474 4.81947 29.2354 4.43597 28.4828C4 27.6271 4 26.507 4 24.2668V7.73349Z" fill="#EC5B27"/>
|
||||
</g>
|
||||
<g opacity="0.96">
|
||||
<path d="M10.2704 24.0002V18.3042H8.87042V20.4962H7.38242V18.3042H5.98242V24.0002H7.38242V21.7442H8.87042V24.0002H10.2704Z" fill="white"/>
|
||||
<path d="M15.2839 19.5522V18.3042H11.0839V19.5522H12.4839V24.0002H13.8839V19.5522H15.2839Z" fill="white"/>
|
||||
<path d="M21.4116 24.0002V18.3042H20.0356L18.7556 20.8162L17.4756 18.3042H16.0996V24.0002H17.4996V21.2722L18.3076 22.6802H19.2036L20.0116 21.2722V24.0002H21.4116Z" fill="white"/>
|
||||
<path d="M26.3525 24.0002V22.7522H23.9605V18.3042H22.5605V24.0002H26.3525Z" fill="white"/>
|
||||
</g>
|
||||
<path opacity="0.5" d="M18.6665 1.3335L27.9998 10.6668H21.3332C19.8604 10.6668 18.6665 9.47292 18.6665 8.00016V1.3335Z" fill="white"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_3055_14424" x="2" y="0.333496" width="28" height="33.3335" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3055_14424"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3055_14424" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue