feat: support assistant frontend (#2139)
Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
This commit is contained in:
parent
e65a2a400d
commit
7bbe12b2bd
194 changed files with 8726 additions and 1586 deletions
|
|
@ -4,10 +4,10 @@ import React, { useState } from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { UserCircleIcon } from '@heroicons/react/24/solid'
|
||||
import cn from 'classnames'
|
||||
import type { CitationItem, DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, ThoughtItem } from '../type'
|
||||
import type { CitationItem, DisplayScene, FeedbackFunc, Feedbacktype, IChatItem } from '../type'
|
||||
import OperationBtn from '../operation'
|
||||
import LoadingAnim from '../loading-anim'
|
||||
import { EditIconSolid, OpeningStatementIcon, RatingIcon } from '../icon-component'
|
||||
import { EditIconSolid, RatingIcon } from '../icon-component'
|
||||
import s from '../style.module.css'
|
||||
import MoreInfo from '../more-info'
|
||||
import CopyBtn from '../copy-btn'
|
||||
|
|
@ -22,6 +22,9 @@ import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annota
|
|||
import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
|
||||
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
||||
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
import type { VisionFile } from '@/types/app'
|
||||
import ImageGallery from '@/app/components/base/image-gallery'
|
||||
|
||||
const Divider: FC<{ name: string }> = ({ name }) => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -41,13 +44,12 @@ export type IAnswerProps = {
|
|||
item: IChatItem
|
||||
feedbackDisabled: boolean
|
||||
isHideFeedbackEdit: boolean
|
||||
onQueryChange: (query: string) => void
|
||||
onFeedback?: FeedbackFunc
|
||||
displayScene: DisplayScene
|
||||
isResponsing?: boolean
|
||||
answerIcon?: ReactNode
|
||||
thoughts?: ThoughtItem[]
|
||||
citation?: CitationItem[]
|
||||
isThinking?: boolean
|
||||
dataSets?: DataSet[]
|
||||
isShowCitation?: boolean
|
||||
isShowCitationHitInfo?: boolean
|
||||
|
|
@ -58,20 +60,19 @@ export type IAnswerProps = {
|
|||
onAnnotationEdited?: (question: string, answer: string) => void
|
||||
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string) => void
|
||||
onAnnotationRemoved?: () => void
|
||||
allToolIcons?: Record<string, string | Emoji>
|
||||
}
|
||||
// The component needs to maintain its own state to control whether to display input component
|
||||
const Answer: FC<IAnswerProps> = ({
|
||||
item,
|
||||
onQueryChange,
|
||||
feedbackDisabled = false,
|
||||
isHideFeedbackEdit = false,
|
||||
onFeedback,
|
||||
displayScene = 'web',
|
||||
isResponsing,
|
||||
answerIcon,
|
||||
thoughts,
|
||||
citation,
|
||||
isThinking,
|
||||
dataSets,
|
||||
isShowCitation,
|
||||
isShowCitationHitInfo = false,
|
||||
supportAnnotation,
|
||||
|
|
@ -80,8 +81,10 @@ const Answer: FC<IAnswerProps> = ({
|
|||
onAnnotationEdited,
|
||||
onAnnotationAdded,
|
||||
onAnnotationRemoved,
|
||||
allToolIcons,
|
||||
}) => {
|
||||
const { id, content, more, feedback, adminFeedback, annotation } = item
|
||||
const { id, content, more, feedback, adminFeedback, annotation, agent_thoughts } = item
|
||||
const isAgentMode = !!agent_thoughts && agent_thoughts.length > 0
|
||||
const hasAnnotation = !!annotation?.id
|
||||
const [showEdit, setShowEdit] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
|
@ -202,8 +205,40 @@ const Answer: FC<IAnswerProps> = ({
|
|||
)
|
||||
}
|
||||
|
||||
const getImgs = (list?: VisionFile[]) => {
|
||||
if (!list)
|
||||
return []
|
||||
return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant')
|
||||
}
|
||||
|
||||
const agentModeAnswer = (
|
||||
<div>
|
||||
{agent_thoughts?.map((item, index) => (
|
||||
<div key={index}>
|
||||
{item.thought && (
|
||||
<Markdown content={item.thought} />
|
||||
)}
|
||||
{/* {item.tool} */}
|
||||
{/* perhaps not use tool */}
|
||||
{!!item.tool && (
|
||||
<Thought
|
||||
thought={item}
|
||||
allToolIcons={allToolIcons || {}}
|
||||
isFinished={!!item.observation}
|
||||
/>
|
||||
)}
|
||||
|
||||
{getImgs(item.message_files).length > 0 && (
|
||||
<ImageGallery srcs={getImgs(item.message_files).map(item => item.url)} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div key={id}>
|
||||
// data-id for debug the item message is right
|
||||
<div key={id} data-id={id}>
|
||||
<div className='flex items-start'>
|
||||
{
|
||||
answerIcon || (
|
||||
|
|
@ -220,20 +255,7 @@ const Answer: FC<IAnswerProps> = ({
|
|||
<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'}>
|
||||
{item.isOpeningStatement && (
|
||||
<div className='flex items-center mb-1 gap-1'>
|
||||
<OpeningStatementIcon />
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.title')}</div>
|
||||
</div>
|
||||
)}
|
||||
{(thoughts && thoughts.length > 0) && (
|
||||
<Thought
|
||||
list={thoughts || []}
|
||||
isThinking={isThinking}
|
||||
dataSets={dataSets}
|
||||
/>
|
||||
)}
|
||||
{(isResponsing && !content)
|
||||
{(isResponsing && (isAgentMode ? (!content && (agent_thoughts || []).length === 0) : !content))
|
||||
? (
|
||||
<div className='flex items-center justify-center w-6 h-5'>
|
||||
<LoadingAnim type='text' />
|
||||
|
|
@ -241,29 +263,54 @@ const Answer: FC<IAnswerProps> = ({
|
|||
)
|
||||
: (
|
||||
<div>
|
||||
|
||||
{annotation?.logAnnotation && (
|
||||
<div className='mb-1'>
|
||||
<div className='mb-3'>
|
||||
<Markdown className='line-through !text-gray-400' content={content} />
|
||||
{isAgentMode
|
||||
? (<div className='line-through !text-gray-400'>{agentModeAnswer}</div>)
|
||||
: (
|
||||
<Markdown className='line-through !text-gray-400' content={content} />
|
||||
)}
|
||||
</div>
|
||||
<EditTitle title={t('appAnnotation.editBy', {
|
||||
author: annotation?.logAnnotation.account?.name,
|
||||
})} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Markdown content={annotation?.logAnnotation ? annotation?.logAnnotation.content : content} />
|
||||
{annotation?.logAnnotation
|
||||
? (
|
||||
<Markdown content={annotation?.logAnnotation.content || ''} />
|
||||
)
|
||||
: (isAgentMode
|
||||
? agentModeAnswer
|
||||
: (
|
||||
<Markdown content={content} />
|
||||
))}
|
||||
</div>
|
||||
{(hasAnnotation && !annotation?.logAnnotation) && (
|
||||
<EditTitle className='mt-1' title={t('appAnnotation.editBy', {
|
||||
author: annotation.authorName,
|
||||
})} />
|
||||
)}
|
||||
{item.isOpeningStatement && item.suggestedQuestions && item.suggestedQuestions.length > 0 && (
|
||||
<div className='flex flex-wrap'>
|
||||
{item.suggestedQuestions.map((question, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer'
|
||||
onClick={() => onQueryChange(question)}
|
||||
>
|
||||
{question}
|
||||
</div>),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
!!citation?.length && !isThinking && isShowCitation && !isResponsing && (
|
||||
!!citation?.length && isShowCitation && !isResponsing && (
|
||||
<Citation data={citation} showHitInfo={isShowCitationHitInfo} />
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import ImageList from '@/app/components/base/image-uploader/image-list'
|
|||
import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app'
|
||||
import { useClipboardUploader, useDraggableUploader, useImageFiles } from '@/app/components/base/image-uploader/hooks'
|
||||
import type { Annotation } from '@/models/log'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
|
||||
export type IChatProps = {
|
||||
appId?: string
|
||||
|
|
@ -43,6 +44,8 @@ export type IChatProps = {
|
|||
isHideSendInput?: boolean
|
||||
onFeedback?: FeedbackFunc
|
||||
checkCanSend?: () => boolean
|
||||
query?: string
|
||||
onQueryChange?: (query: string) => void
|
||||
onSend?: (message: string, files: VisionFile[]) => void
|
||||
displayScene?: DisplayScene
|
||||
useCurrentUserAvatar?: boolean
|
||||
|
|
@ -62,12 +65,14 @@ export type IChatProps = {
|
|||
isShowPromptLog?: boolean
|
||||
visionConfig?: VisionSettings
|
||||
supportAnnotation?: boolean
|
||||
allToolIcons?: Record<string, string | Emoji>
|
||||
}
|
||||
|
||||
const Chat: FC<IChatProps> = ({
|
||||
configElem,
|
||||
chatList,
|
||||
|
||||
query = '',
|
||||
onQueryChange = () => { },
|
||||
feedbackDisabled = false,
|
||||
isHideFeedbackEdit = false,
|
||||
isHideSendInput = false,
|
||||
|
|
@ -94,6 +99,7 @@ const Chat: FC<IChatProps> = ({
|
|||
appId,
|
||||
supportAnnotation,
|
||||
onChatListChange,
|
||||
allToolIcons,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
|
|
@ -110,18 +116,18 @@ const Chat: FC<IChatProps> = ({
|
|||
const { onDragEnter, onDragLeave, onDragOver, onDrop, isDragActive } = useDraggableUploader<HTMLTextAreaElement>({ onUpload, files, visionConfig })
|
||||
const isUseInputMethod = useRef(false)
|
||||
|
||||
const [query, setQuery] = React.useState('')
|
||||
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const value = e.target.value
|
||||
setQuery(value)
|
||||
onQueryChange(value)
|
||||
}
|
||||
|
||||
const logError = (message: string) => {
|
||||
notify({ type: 'error', message, duration: 3000 })
|
||||
}
|
||||
|
||||
const valid = () => {
|
||||
if (!query || query.trim() === '') {
|
||||
const valid = (q?: string) => {
|
||||
const sendQuery = q || query
|
||||
if (!sendQuery || sendQuery.trim() === '') {
|
||||
logError('Message cannot be empty')
|
||||
return false
|
||||
}
|
||||
|
|
@ -130,13 +136,13 @@ const Chat: FC<IChatProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (controlClearQuery)
|
||||
setQuery('')
|
||||
onQueryChange('')
|
||||
}, [controlClearQuery])
|
||||
|
||||
const handleSend = () => {
|
||||
if (!valid() || (checkCanSend && !checkCanSend()))
|
||||
const handleSend = (q?: string) => {
|
||||
if (!valid(q) || (checkCanSend && !checkCanSend()))
|
||||
return
|
||||
onSend(query, files.filter(file => file.progress !== -1).map(fileItem => ({
|
||||
onSend(q || query, files.filter(file => file.progress !== -1).map(fileItem => ({
|
||||
type: 'image',
|
||||
transfer_method: fileItem.type,
|
||||
url: fileItem.url,
|
||||
|
|
@ -146,7 +152,7 @@ const Chat: FC<IChatProps> = ({
|
|||
if (files.length)
|
||||
onClear()
|
||||
if (!isResponsing)
|
||||
setQuery('')
|
||||
onQueryChange('')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,14 +168,14 @@ const Chat: FC<IChatProps> = ({
|
|||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
isUseInputMethod.current = e.nativeEvent.isComposing
|
||||
if (e.code === 'Enter' && !e.shiftKey) {
|
||||
setQuery(query.replace(/\n$/, ''))
|
||||
onQueryChange(query.replace(/\n$/, ''))
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const sendBtn = <div className={cn(!(!query || query.trim() === '') && s.sendBtnActive, `${s.sendBtn} w-8 h-8 cursor-pointer rounded-md`)} onClick={handleSend}></div>
|
||||
const sendBtn = <div className={cn(!(!query || query.trim() === '') && s.sendBtnActive, `${s.sendBtn} w-8 h-8 cursor-pointer rounded-md`)} onClick={() => handleSend()}></div>
|
||||
|
||||
const suggestionListRef = useRef<HTMLDivElement>(null)
|
||||
const [hasScrollbar, setHasScrollbar] = useState(false)
|
||||
|
|
@ -198,21 +204,21 @@ const Chat: FC<IChatProps> = ({
|
|||
{chatList.map((item, index) => {
|
||||
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}
|
||||
item={item}
|
||||
onQueryChange={(val) => {
|
||||
onQueryChange(val)
|
||||
handleSend(val)
|
||||
}}
|
||||
feedbackDisabled={feedbackDisabled}
|
||||
isHideFeedbackEdit={isHideFeedbackEdit}
|
||||
onFeedback={onFeedback}
|
||||
displayScene={displayScene ?? 'web'}
|
||||
isResponsing={isResponsing && isLast}
|
||||
answerIcon={answerIcon}
|
||||
thoughts={thoughts}
|
||||
citation={citation}
|
||||
isThinking={isThinking}
|
||||
dataSets={dataSets}
|
||||
isShowCitation={isShowCitation}
|
||||
isShowCitationHitInfo={isShowCitationHitInfo}
|
||||
|
|
@ -285,7 +291,7 @@ const Chat: FC<IChatProps> = ({
|
|||
return item
|
||||
}))
|
||||
}}
|
||||
|
||||
allToolIcons={allToolIcons}
|
||||
/>
|
||||
}
|
||||
return (
|
||||
|
|
@ -308,7 +314,7 @@ const Chat: FC<IChatProps> = ({
|
|||
!isHideSendInput && (
|
||||
<div className={cn(!feedbackDisabled && '!left-3.5 !right-3.5', 'absolute z-10 bottom-0 left-0 right-0')}>
|
||||
{/* Thinking is sync and can not be stopped */}
|
||||
{(isResponsing && canStopResponsing && !!chatList[chatList.length - 1]?.content) && (
|
||||
{(isResponsing && canStopResponsing && ((!!chatList[chatList.length - 1]?.content) || (chatList[chatList.length - 1]?.agent_thoughts && chatList[chatList.length - 1].agent_thoughts!.length > 0))) && (
|
||||
<div className='flex justify-center mb-4'>
|
||||
<Button className='flex items-center space-x-1 bg-white' onClick={() => abortResponsing?.()}>
|
||||
{stopIcon}
|
||||
|
|
@ -339,7 +345,7 @@ const Chat: FC<IChatProps> = ({
|
|||
<div key={item} className='shrink-0 flex justify-center mr-2'>
|
||||
<Button
|
||||
key={index}
|
||||
onClick={() => setQuery(item)}
|
||||
onClick={() => onQueryChange(item)}
|
||||
>
|
||||
<span className='text-primary-600 text-xs font-medium'>{item}</span>
|
||||
</Button>
|
||||
|
|
@ -393,7 +399,7 @@ const Chat: FC<IChatProps> = ({
|
|||
{
|
||||
query
|
||||
? (
|
||||
<div className='flex justify-center items-center w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => setQuery('')}>
|
||||
<div className='flex justify-center items-center w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => onQueryChange('')}>
|
||||
<XCircle className='w-4 h-4 text-[#98A2B3]' />
|
||||
</div>
|
||||
)
|
||||
|
|
@ -429,7 +435,7 @@ const Chat: FC<IChatProps> = ({
|
|||
voiceInputShow && (
|
||||
<VoiceInput
|
||||
onCancel={() => setVoiceInputShow(false)}
|
||||
onConverted={text => setQuery(text)}
|
||||
onConverted={text => onQueryChange(text)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,92 +1,60 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ThoughtItem } from '../type'
|
||||
import s from './style.module.css'
|
||||
import { DataSet as DataSetIcon, Loading as LodingIcon, Search, ThoughtList, WebReader } from '@/app/components/base/icons/src/public/thought'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import type { ThoughtItem, ToolInfoInThought } from '../type'
|
||||
import Tool from '@/app/components/app/chat/thought/tool'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
|
||||
export type IThoughtProps = {
|
||||
list: ThoughtItem[]
|
||||
isThinking?: boolean
|
||||
dataSets?: DataSet[]
|
||||
thought: ThoughtItem
|
||||
allToolIcons: Record<string, string | Emoji>
|
||||
isFinished: boolean
|
||||
}
|
||||
|
||||
const getIcon = (toolId: string) => {
|
||||
switch (toolId) {
|
||||
case 'dataset':
|
||||
return <DataSetIcon />
|
||||
case 'web_reader':
|
||||
return <WebReader />
|
||||
default:
|
||||
return <Search />
|
||||
function getValue(value: string, isValueArray: boolean, index: number) {
|
||||
if (isValueArray) {
|
||||
try {
|
||||
return JSON.parse(value)[index]
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const Thought: FC<IThoughtProps> = ({
|
||||
list,
|
||||
isThinking,
|
||||
dataSets,
|
||||
thought,
|
||||
allToolIcons,
|
||||
isFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShowDetail, setIsShowDetail] = React.useState(false)
|
||||
|
||||
const getThoughtText = (item: ThoughtItem) => {
|
||||
const [toolNames, isValueArray]: [string[], boolean] = (() => {
|
||||
try {
|
||||
const input = JSON.parse(item.tool_input)
|
||||
// dataset
|
||||
if (item.tool.startsWith('dataset-')) {
|
||||
const dataSetId = item.tool.replace('dataset-', '')
|
||||
const datasetName = dataSets?.find(item => item.id === dataSetId)?.name || 'unknown dataset'
|
||||
return t('explore.universalChat.thought.res.dataset').replace('{datasetName}', `<span class="text-gray-700">${datasetName}</span>`)
|
||||
}
|
||||
switch (item.tool) {
|
||||
case 'web_reader':
|
||||
return t(`explore.universalChat.thought.res.webReader.${!input.cursor ? 'normal' : 'hasPageInfo'}`).replace('{url}', `<a href="${input.url}" class="text-[#155EEF]">${input.url}</a>`)
|
||||
case 'google_search':
|
||||
return t('explore.universalChat.thought.res.google', { query: input.query })
|
||||
case 'wikipedia':
|
||||
return t('explore.universalChat.thought.res.wikipedia', { query: input.query })
|
||||
case 'current_datetime':
|
||||
return t('explore.universalChat.thought.res.date')
|
||||
default:
|
||||
return `Unknown tool: ${item.tool}`
|
||||
}
|
||||
if (Array.isArray(JSON.parse(thought.tool)))
|
||||
return [JSON.parse(thought.tool), true]
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
return item
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
const renderItem = (item: ThoughtItem) => (
|
||||
<div className='flex space-x-1 py-[3px] leading-[18px]' key={item.id}>
|
||||
<div className='flex items-center h-[18px] shrink-0'>{getIcon(item.tool)}</div>
|
||||
<div dangerouslySetInnerHTML={{
|
||||
__html: getThoughtText(item),
|
||||
// item.thought.replace(urlRegex, (url) => {
|
||||
// return `<a href="${url}" class="text-[#155EEF]">${url}</a>`
|
||||
// }),
|
||||
}}></div>
|
||||
</div>
|
||||
)
|
||||
return [[thought.tool], false]
|
||||
})()
|
||||
|
||||
const toolThoughtList = toolNames.map((toolName, index) => {
|
||||
return {
|
||||
name: toolName,
|
||||
input: getValue(thought.tool_input, isValueArray, index),
|
||||
output: getValue(thought.observation, isValueArray, index),
|
||||
isFinished,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn(s.wrap, !isShowDetail && s.wrapHoverEffect, 'inline-block mb-2 px-2 py-0.5 rounded-md text-xs text-gray-500 font-medium')} >
|
||||
<div className='flex items-center h-6 space-x-1 cursor-pointer' onClick={() => setIsShowDetail(!isShowDetail)} >
|
||||
{!isThinking ? <ThoughtList /> : <div className='animate-spin'><LodingIcon /></div>}
|
||||
<div dangerouslySetInnerHTML= {{
|
||||
__html: isThinking ? getThoughtText(list[list.length - 1]) : (t(`explore.universalChat.thought.${isShowDetail ? 'hide' : 'show'}`) + t('explore.universalChat.thought.processOfThought')),
|
||||
}}
|
||||
></div>
|
||||
<ChevronDown className={isShowDetail ? 'rotate-180' : '' } />
|
||||
</div>
|
||||
{isShowDetail && (
|
||||
<div>
|
||||
{list.map(item => renderItem(item))}
|
||||
</div>
|
||||
)}
|
||||
<div className='my-2 space-y-2'>
|
||||
{toolThoughtList.map((item: ToolInfoInThought, index) => (
|
||||
<Tool
|
||||
key={index}
|
||||
payload={item}
|
||||
allToolIcons={allToolIcons}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
28
web/app/components/app/chat/thought/panel.tsx
Normal file
28
web/app/components/app/chat/thought/panel.tsx
Normal 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)
|
||||
103
web/app/components/app/chat/thought/tool.tsx
Normal file
103
web/app/components/app/chat/thought/tool.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import cn from 'classnames'
|
||||
import type { ToolInfoInThought } from '../type'
|
||||
import Panel from './panel'
|
||||
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
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, input, isFinished, output } = payload
|
||||
const toolName = name.startsWith('dataset-') ? t('dataset.knowledge') : name
|
||||
const [isShowDetail, setIsShowDetail] = useState(false)
|
||||
const icon = getIcon(toolName, 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 && (
|
||||
<Loading02 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={toolName}
|
||||
>
|
||||
{toolName}
|
||||
</span>
|
||||
<ChevronDown
|
||||
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)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Annotation, MessageRating } from '@/models/log'
|
||||
import type { VisionFile } from '@/types/app'
|
||||
|
||||
export type MessageMore = {
|
||||
time: string
|
||||
tokens: number
|
||||
|
|
@ -16,13 +17,25 @@ export type SubmitAnnotationFunc = (messageId: string, content: string) => Promi
|
|||
|
||||
export type DisplayScene = 'web' | 'console'
|
||||
|
||||
export type ToolInfoInThought = {
|
||||
name: string
|
||||
input: string
|
||||
output: string
|
||||
isFinished: boolean
|
||||
}
|
||||
|
||||
export type ThoughtItem = {
|
||||
id: string
|
||||
tool: string // plugin or dataset
|
||||
tool: string // plugin or dataset. May has multi.
|
||||
thought: string
|
||||
tool_input: string
|
||||
message_id: string
|
||||
observation: string
|
||||
position: number
|
||||
files?: string[]
|
||||
message_files?: VisionFile[]
|
||||
}
|
||||
|
||||
export type CitationItem = {
|
||||
content: string
|
||||
data_source_type: string
|
||||
|
|
@ -41,7 +54,6 @@ export type CitationItem = {
|
|||
export type IChatItem = {
|
||||
id: string
|
||||
content: string
|
||||
agent_thoughts?: ThoughtItem[]
|
||||
citation?: CitationItem[]
|
||||
/**
|
||||
* Specific message type
|
||||
|
|
@ -66,7 +78,9 @@ export type IChatItem = {
|
|||
annotation?: Annotation
|
||||
useCurrentUserAvatar?: boolean
|
||||
isOpeningStatement?: boolean
|
||||
suggestedQuestions?: string[]
|
||||
log?: { role: string; text: string }[]
|
||||
agent_thoughts?: ThoughtItem[]
|
||||
message_files?: VisionFile[]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ import type { FC } from 'react'
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlusIcon } from '@heroicons/react/20/solid'
|
||||
import cn from 'classnames'
|
||||
|
||||
export type IOperationBtnProps = {
|
||||
className?: string
|
||||
type: 'add' | 'edit'
|
||||
actionName?: string
|
||||
onClick: () => void
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const iconMap = {
|
||||
|
|
@ -19,14 +21,15 @@ const iconMap = {
|
|||
}
|
||||
|
||||
const OperationBtn: FC<IOperationBtnProps> = ({
|
||||
className,
|
||||
type,
|
||||
actionName,
|
||||
onClick,
|
||||
onClick = () => { },
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div
|
||||
className='flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200'
|
||||
className={cn(className, 'flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200 select-none')}
|
||||
onClick={onClick}>
|
||||
<div>
|
||||
{iconMap[type]}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alerts
|
|||
import { useModalContext } from '@/context/modal-context'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var'
|
||||
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
|
||||
|
||||
type Props = {
|
||||
type: PromptRole
|
||||
|
|
@ -49,6 +52,7 @@ const AdvancedPromptInput: FC<Props> = ({
|
|||
onHideContextMissingTip,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const {
|
||||
mode,
|
||||
|
|
@ -60,7 +64,6 @@ const AdvancedPromptInput: FC<Props> = ({
|
|||
dataSets,
|
||||
showSelectDataSet,
|
||||
externalDataToolsConfig,
|
||||
setExternalDataToolsConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const { notify } = useToastContext()
|
||||
const { setShowExternalDataToolModal } = useModalContext()
|
||||
|
|
@ -68,7 +71,14 @@ const AdvancedPromptInput: FC<Props> = ({
|
|||
setShowExternalDataToolModal({
|
||||
payload: {},
|
||||
onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
setExternalDataToolsConfig([...externalDataToolsConfig, newExternalDataTool])
|
||||
eventEmitter?.emit({
|
||||
type: ADD_EXTERNAL_DATA_TOOL,
|
||||
payload: newExternalDataTool,
|
||||
} as any)
|
||||
eventEmitter?.emit({
|
||||
type: INSERT_VARIABLE_VALUE_BLOCK_COMMAND,
|
||||
payload: newExternalDataTool.variable,
|
||||
} as any)
|
||||
},
|
||||
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
for (let i = 0; i < promptVariables.length; i++) {
|
||||
|
|
@ -78,13 +88,6 @@ const AdvancedPromptInput: FC<Props> = ({
|
|||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < externalDataToolsConfig.length; i++) {
|
||||
if (externalDataToolsConfig[i].variable === newExternalDataTool.variable) {
|
||||
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: externalDataToolsConfig[i].variable }) })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
|
@ -108,7 +111,7 @@ const AdvancedPromptInput: FC<Props> = ({
|
|||
}
|
||||
const handleBlur = () => {
|
||||
const keys = getVars(value)
|
||||
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key))
|
||||
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key, ''))
|
||||
if (newPromptVariables.length > 0) {
|
||||
setNewPromptVariables(newPromptVariables)
|
||||
showConfirmAddVar()
|
||||
|
|
@ -202,13 +205,13 @@ const AdvancedPromptInput: FC<Props> = ({
|
|||
onAddContext: showSelectDataSet,
|
||||
}}
|
||||
variableBlock={{
|
||||
variables: modelConfig.configs.prompt_variables.map(item => ({
|
||||
variables: modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({
|
||||
name: item.name,
|
||||
value: item.key,
|
||||
})),
|
||||
externalTools: externalDataToolsConfig.map(item => ({
|
||||
name: item.label!,
|
||||
variableName: item.variable!,
|
||||
externalTools: modelConfig.configs.prompt_variables.filter(item => item.type === 'api').map(item => ({
|
||||
name: item.name,
|
||||
variableName: item.key,
|
||||
icon: item.icon,
|
||||
icon_background: item.icon_background,
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import produce from 'immer'
|
|||
import { useContext } from 'use-context-selector'
|
||||
import ConfirmAddVar from './confirm-add-var'
|
||||
import s from './style.module.css'
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
import { PromptMode, type PromptVariable } from '@/models/debug'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { AppType } from '@/types/app'
|
||||
import { getNewVar, getVars } from '@/utils/var'
|
||||
|
|
@ -21,6 +21,10 @@ import ConfigContext from '@/context/debug-configuration'
|
|||
import { useModalContext } from '@/context/modal-context'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { ArrowNarrowRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var'
|
||||
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
|
||||
|
||||
export type ISimplePromptInput = {
|
||||
mode: AppType
|
||||
|
|
@ -38,6 +42,7 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const {
|
||||
modelConfig,
|
||||
dataSets,
|
||||
|
|
@ -47,7 +52,9 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
hasSetBlockStatus,
|
||||
showSelectDataSet,
|
||||
externalDataToolsConfig,
|
||||
setExternalDataToolsConfig,
|
||||
isAdvancedMode,
|
||||
isAgent,
|
||||
setPromptMode,
|
||||
} = useContext(ConfigContext)
|
||||
const { notify } = useToastContext()
|
||||
const { setShowExternalDataToolModal } = useModalContext()
|
||||
|
|
@ -55,7 +62,14 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
setShowExternalDataToolModal({
|
||||
payload: {},
|
||||
onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
setExternalDataToolsConfig([...externalDataToolsConfig, newExternalDataTool])
|
||||
eventEmitter?.emit({
|
||||
type: ADD_EXTERNAL_DATA_TOOL,
|
||||
payload: newExternalDataTool,
|
||||
} as any)
|
||||
eventEmitter?.emit({
|
||||
type: INSERT_VARIABLE_VALUE_BLOCK_COMMAND,
|
||||
payload: newExternalDataTool.variable,
|
||||
} as any)
|
||||
},
|
||||
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
for (let i = 0; i < promptVariables.length; i++) {
|
||||
|
|
@ -65,13 +79,6 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < externalDataToolsConfig.length; i++) {
|
||||
if (externalDataToolsConfig[i].variable === newExternalDataTool.variable) {
|
||||
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: externalDataToolsConfig[i].variable }) })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
|
@ -89,7 +96,7 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false)
|
||||
|
||||
const handleChange = (newTemplates: string, keys: string[]) => {
|
||||
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key))
|
||||
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key, ''))
|
||||
if (newPromptVariables.length > 0) {
|
||||
setNewPromptVariables(newPromptVariables)
|
||||
setNewTemplates(newTemplates)
|
||||
|
|
@ -138,7 +145,21 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<AutomaticBtn onClick={showAutomaticTrue}/>
|
||||
<div className='flex items-center'>
|
||||
<AutomaticBtn onClick={showAutomaticTrue} />
|
||||
{!isAgent && !isAdvancedMode && (
|
||||
<>
|
||||
<div className='mx-1 w-px h-3.5 bg-black/5'></div>
|
||||
<div
|
||||
className='flex items-center px-2 space-x-1 leading-[18px] text-xs font-semibold text-[#444CE7] cursor-pointer'
|
||||
onClick={() => setPromptMode(PromptMode.advanced)}
|
||||
>
|
||||
<div>{t('appDebug.promptMode.advanced')}</div>
|
||||
<ArrowNarrowRight className='w-3 h-3'></ArrowNarrowRight>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4 py-2 min-h-[228px] max-h-[156px] overflow-y-auto bg-white rounded-xl text-sm text-gray-700'>
|
||||
<PromptEditor
|
||||
|
|
@ -155,13 +176,13 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
onAddContext: showSelectDataSet,
|
||||
}}
|
||||
variableBlock={{
|
||||
variables: modelConfig.configs.prompt_variables.map(item => ({
|
||||
variables: modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({
|
||||
name: item.name,
|
||||
value: item.key,
|
||||
})),
|
||||
externalTools: externalDataToolsConfig.map(item => ({
|
||||
name: item.label!,
|
||||
variableName: item.variable!,
|
||||
externalTools: modelConfig.configs.prompt_variables.filter(item => item.type === 'api').map(item => ({
|
||||
name: item.name,
|
||||
variableName: item.key,
|
||||
icon: item.icon,
|
||||
icon_background: item.icon_background,
|
||||
})),
|
||||
|
|
@ -174,7 +195,7 @@ const Prompt: FC<ISimplePromptInput> = ({
|
|||
user: '',
|
||||
assistant: '',
|
||||
},
|
||||
onEditRole: () => {},
|
||||
onEditRole: () => { },
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@
|
|||
}
|
||||
|
||||
.inputWrap:hover {
|
||||
border-color: #FEE4E2;
|
||||
background-color: #FFFBFA;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@
|
|||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Cog8ToothIcon, TrashIcon } from '@heroicons/react/24/outline'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { Timeout } from 'ahooks/lib/useRequest/src/types'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import Panel from '../base/feature-panel'
|
||||
import OperationBtn from '../base/operation-btn'
|
||||
import EditModal from './config-modal'
|
||||
import IconTypeIcon from './input-type-icon'
|
||||
import type { IInputTypeIconProps } from './input-type-icon'
|
||||
import s from './style.module.css'
|
||||
import SelectVarType from './select-var-type'
|
||||
import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
|
|
@ -19,10 +18,25 @@ import { DEFAULT_VALUE_MAX_LEN, getMaxVarNameLength } from '@/config'
|
|||
import { checkKeys, getNewVar } from '@/utils/var'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { HelpCircle, Settings01, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import ConfirmModal from '@/app/components/base/confirm/common'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { AppType } from '@/types/app'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
||||
export const ADD_EXTERNAL_DATA_TOOL = 'ADD_EXTERNAL_DATA_TOOL'
|
||||
|
||||
type ExternalDataToolParams = {
|
||||
key: string
|
||||
type: string
|
||||
index: number
|
||||
name: string
|
||||
config?: Record<string, any>
|
||||
icon?: string
|
||||
icon_background?: string
|
||||
}
|
||||
|
||||
export type IConfigVarProps = {
|
||||
promptVariables: PromptVariable[]
|
||||
|
|
@ -37,17 +51,11 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||
const {
|
||||
mode,
|
||||
dataSets,
|
||||
externalDataToolsConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const hasVar = promptVariables.length > 0
|
||||
const promptVariableObj = (() => {
|
||||
const obj: Record<string, boolean> = {}
|
||||
promptVariables.forEach((item) => {
|
||||
obj[item.key] = true
|
||||
})
|
||||
return obj
|
||||
})()
|
||||
|
||||
const updatePromptVariable = (key: string, updateKey: string, newValue: string | boolean) => {
|
||||
const newPromptVariables = promptVariables.map((item) => {
|
||||
if (item.key === key) {
|
||||
|
|
@ -131,11 +139,89 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||
onPromptVariablesChange?.(newPromptVariables)
|
||||
}
|
||||
|
||||
const handleAddVar = () => {
|
||||
const newVar = getNewVar('')
|
||||
onPromptVariablesChange?.([...promptVariables, newVar])
|
||||
const { setShowExternalDataToolModal } = useModalContext()
|
||||
|
||||
const handleOpenExternalDataToolModal = (
|
||||
{ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams,
|
||||
oldPromptVariables: PromptVariable[],
|
||||
) => {
|
||||
setShowExternalDataToolModal({
|
||||
payload: {
|
||||
variable: key,
|
||||
label: name,
|
||||
config,
|
||||
icon,
|
||||
icon_background,
|
||||
},
|
||||
onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
const newPromptVariables = oldPromptVariables.map((item, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
key: newExternalDataTool.variable as string,
|
||||
name: newExternalDataTool.label as string,
|
||||
enabled: newExternalDataTool.enabled,
|
||||
type: newExternalDataTool.type as string,
|
||||
config: newExternalDataTool.config,
|
||||
required: item.required,
|
||||
icon: newExternalDataTool.icon,
|
||||
icon_background: newExternalDataTool.icon_background,
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
onPromptVariablesChange?.(newPromptVariables)
|
||||
},
|
||||
onCancelCallback: () => {
|
||||
if (!key)
|
||||
onPromptVariablesChange?.(promptVariables.filter((_, i) => i !== index))
|
||||
},
|
||||
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
for (let i = 0; i < promptVariables.length; i++) {
|
||||
if (promptVariables[i].key === newExternalDataTool.variable && i !== index) {
|
||||
Toast.notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: promptVariables[i].key }) })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleAddVar = (type: string) => {
|
||||
const newVar = getNewVar('', type)
|
||||
const newPromptVariables = [...promptVariables, newVar]
|
||||
onPromptVariablesChange?.(newPromptVariables)
|
||||
|
||||
if (type === 'api') {
|
||||
handleOpenExternalDataToolModal({
|
||||
type,
|
||||
key: newVar.key,
|
||||
name: newVar.name,
|
||||
index: promptVariables.length,
|
||||
}, newPromptVariables)
|
||||
}
|
||||
}
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === ADD_EXTERNAL_DATA_TOOL) {
|
||||
const payload = v.payload
|
||||
onPromptVariablesChange?.([
|
||||
...promptVariables,
|
||||
{
|
||||
key: payload.variable as string,
|
||||
name: payload.label as string,
|
||||
enabled: payload.enabled,
|
||||
type: payload.type as string,
|
||||
config: payload.config,
|
||||
required: true,
|
||||
icon: payload.icon,
|
||||
icon_background: payload.icon_background,
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
const [isShowDeleteContextVarModal, { setTrue: showDeleteContextVarModal, setFalse: hideDeleteContextVarModal }] = useBoolean(false)
|
||||
const [removeIndex, setRemoveIndex] = useState<number | null>(null)
|
||||
const didRemoveVar = (index: number) => {
|
||||
|
|
@ -156,16 +242,21 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||
const [currKey, setCurrKey] = useState<string | null>(null)
|
||||
const currItem = currKey ? promptVariables.find(item => item.key === currKey) : null
|
||||
const [isShowEditModal, { setTrue: showEditModal, setFalse: hideEditModal }] = useBoolean(false)
|
||||
const handleConfig = (key: string) => {
|
||||
|
||||
const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => {
|
||||
setCurrKey(key)
|
||||
if (type === 'api') {
|
||||
handleOpenExternalDataToolModal({ key, type, index, name, config, icon, icon_background }, promptVariables)
|
||||
return
|
||||
}
|
||||
|
||||
showEditModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
className="mt-4"
|
||||
headerIcon={
|
||||
<VarIcon className='w-4 h-4 text-primary-500'/>
|
||||
<VarIcon className='w-4 h-4 text-primary-500' />
|
||||
}
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
|
|
@ -179,7 +270,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||
)}
|
||||
</div>
|
||||
}
|
||||
headerRight={!readonly ? <OperationBtn type="add" onClick={handleAddVar} /> : null}
|
||||
headerRight={!readonly ? <SelectVarType onChange={handleAddVar} /> : null}
|
||||
>
|
||||
{!hasVar && (
|
||||
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div>
|
||||
|
|
@ -201,7 +292,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-gray-700">
|
||||
{promptVariables.map(({ key, name, type, required }, index) => (
|
||||
{promptVariables.map(({ key, name, type, required, config, icon, icon_background }, index) => (
|
||||
<tr key={index} className="h-9 leading-9">
|
||||
<td className="w-[160px] border-b border-gray-100 pl-3">
|
||||
<div className='flex items-center space-x-1'>
|
||||
|
|
@ -247,11 +338,11 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||
</td>
|
||||
<td className='w-20 border-b border-gray-100'>
|
||||
<div className='flex h-full items-center space-x-1'>
|
||||
<div className='flex items-center justify-items-center w-6 h-6 text-gray-500 cursor-pointer' onClick={() => handleConfig(key)}>
|
||||
<Cog8ToothIcon width={16} height={16} />
|
||||
<div className=' p-1 rounded-md hover:bg-black/5 w-6 h-6 cursor-pointer' onClick={() => handleConfig({ type, key, index, name, config, icon, icon_background })}>
|
||||
<Settings01 className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
<div className='flex items-center justify-items-center w-6 h-6 text-gray-500 cursor-pointer' onClick={() => handleRemoveVar(index)} >
|
||||
<TrashIcon width={16} height={16} />
|
||||
<div className=' p-1 rounded-md hover:bg-black/5 w-6 h-6 cursor-pointer' onClick={() => handleRemoveVar(index)} >
|
||||
<Trash03 className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import React from 'react'
|
|||
import type { FC } from 'react'
|
||||
import { Paragraph, TypeSquare } from '@/app/components/base/icons/src/vender/solid/editor'
|
||||
import { CheckDone01 } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { ApiConnection } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
|
||||
export type IInputTypeIconProps = {
|
||||
type: 'string' | 'select'
|
||||
|
|
@ -21,6 +22,9 @@ const IconMap = (type: IInputTypeIconProps['type'], className: string) => {
|
|||
select: (
|
||||
<CheckDone01 className={classNames} />
|
||||
),
|
||||
api: (
|
||||
<ApiConnection className={classNames} />
|
||||
),
|
||||
}
|
||||
|
||||
return icons[type]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { Paragraph, TypeSquare } from '@/app/components/base/icons/src/vender/solid/editor'
|
||||
import { CheckDone01 } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { ApiConnection } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
type Props = {
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
type ItemProps = {
|
||||
text: string
|
||||
value: string
|
||||
Icon: any
|
||||
onClick: (value: string) => void
|
||||
}
|
||||
|
||||
const SelectItem: FC<ItemProps> = ({ text, value, Icon, onClick }) => {
|
||||
return (
|
||||
<div
|
||||
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
|
||||
onClick={() => onClick(value)}
|
||||
>
|
||||
<Icon className='w-4 h-4 text-gray-500' />
|
||||
<div className='ml-2 text-xs text-gray-600 truncate'>{text}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SelectVarType: FC<Props> = ({
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const handleChange = (value: string) => {
|
||||
onChange(value)
|
||||
setOpen(false)
|
||||
}
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 8,
|
||||
crossAxis: -2,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<OperationBtn type='add' className={cn(open && 'bg-gray-200')} />
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
|
||||
<div className='bg-white border border-gray-200 shadow-lg rounded-lg min-w-[192px]'>
|
||||
<div className='p-1'>
|
||||
<SelectItem Icon={TypeSquare} value='string' text={t('appDebug.variableConig.string')} onClick={handleChange}></SelectItem>
|
||||
<SelectItem Icon={Paragraph} value='paragraph' text={t('appDebug.variableConig.paragraph')} onClick={handleChange}></SelectItem>
|
||||
<SelectItem Icon={CheckDone01} value='select' text={t('appDebug.variableConig.select')} onClick={handleChange}></SelectItem>
|
||||
</div>
|
||||
<div className='h-[1px] bg-gray-100'></div>
|
||||
<div className='p-1'>
|
||||
<SelectItem Icon={ApiConnection} value='api' text={t('appDebug.variableConig.apiBasedVar')} onClick={handleChange}></SelectItem>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
export default React.memo(SelectVarType)
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ItemPanel from './item-panel'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import { Unblur } from '@/app/components/base/icons/src/vender/solid/education'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import type { AgentConfig } from '@/models/debug'
|
||||
import { DEFAULT_AGENT_PROMPT } from '@/config'
|
||||
|
||||
type Props = {
|
||||
isChatModel: boolean
|
||||
payload: AgentConfig
|
||||
isFunctionCall: boolean
|
||||
onCancel: () => void
|
||||
onSave: (payload: any) => void
|
||||
}
|
||||
|
||||
const maxIterationsMin = 1
|
||||
const maxIterationsMax = 5
|
||||
|
||||
const AgentSetting: FC<Props> = ({
|
||||
isChatModel,
|
||||
payload,
|
||||
isFunctionCall,
|
||||
onCancel,
|
||||
onSave,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [tempPayload, setTempPayload] = useState(payload)
|
||||
const handleSave = () => {
|
||||
onSave(tempPayload)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='fixed z-[100] inset-0 overflow-hidden flex justify-end p-2'
|
||||
style={{
|
||||
backgroundColor: 'rgba(16, 24, 40, 0.20)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='w-[640px] flex flex-col h-full overflow-hidden bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl'
|
||||
>
|
||||
<div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-b-gray-100'>
|
||||
<div className='flex flex-col text-base font-semibold text-gray-900'>
|
||||
<div className='leading-6'>{t('appDebug.agent.setting.name')}</div>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
onClick={onCancel}
|
||||
className='flex justify-center items-center w-6 h-6 cursor-pointer'
|
||||
>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Body */}
|
||||
<div className='grow p-6 pt-5 border-b overflow-y-auto pb-[68px]' style={{
|
||||
borderBottom: 'rgba(0, 0, 0, 0.05)',
|
||||
}}>
|
||||
{/* Agent Mode */}
|
||||
<ItemPanel
|
||||
className='mb-4'
|
||||
icon={
|
||||
<CuteRobote className='w-4 h-4 text-indigo-600' />
|
||||
}
|
||||
name={t('appDebug.agent.agentMode')}
|
||||
description={t('appDebug.agent.agentModeDes')}
|
||||
>
|
||||
<div className='leading-[18px] text-[13px] font-medium text-gray-900'>{isFunctionCall ? t('appDebug.agent.agentModeType.functionCall') : t('appDebug.agent.agentModeType.ReACT')}</div>
|
||||
</ItemPanel>
|
||||
|
||||
<ItemPanel
|
||||
className='mb-4'
|
||||
icon={
|
||||
<Unblur className='w-4 h-4 text-[#FB6514]' />
|
||||
}
|
||||
name={t('appDebug.agent.setting.maximumIterations.name')}
|
||||
description={t('appDebug.agent.setting.maximumIterations.description')}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<Slider
|
||||
className='mr-3 w-[156px]'
|
||||
min={maxIterationsMin}
|
||||
max={maxIterationsMax}
|
||||
value={tempPayload.max_iteration}
|
||||
onChange={(value) => {
|
||||
setTempPayload({
|
||||
...tempPayload,
|
||||
max_iteration: value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
min={maxIterationsMin}
|
||||
max={maxIterationsMax} step={1}
|
||||
className="block w-11 h-7 leading-7 rounded-lg border-0 pl-1 px-1.5 bg-gray-100 text-gray-900 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-primary-600"
|
||||
value={tempPayload.max_iteration}
|
||||
onChange={(e) => {
|
||||
let value = parseInt(e.target.value, 10)
|
||||
if (value < maxIterationsMin)
|
||||
value = maxIterationsMin
|
||||
|
||||
if (value > maxIterationsMax)
|
||||
value = maxIterationsMax
|
||||
setTempPayload({
|
||||
...tempPayload,
|
||||
max_iteration: value,
|
||||
})
|
||||
}} />
|
||||
</div>
|
||||
</ItemPanel>
|
||||
|
||||
{!isFunctionCall && (
|
||||
<div className='py-2 bg-gray-50 rounded-xl shadow-xs'>
|
||||
<div className='flex items-center h-8 px-4 leading-6 text-sm font-semibold text-gray-700'>{t('tools.builtInPromptTitle')}</div>
|
||||
<div className='h-[396px] px-4 overflow-y-auto leading-5 text-sm font-normal text-gray-700 whitespace-pre-line'>
|
||||
{isChatModel ? DEFAULT_AGENT_PROMPT.chat : DEFAULT_AGENT_PROMPT.completion}
|
||||
</div>
|
||||
<div className='px-4'>
|
||||
<div className='inline-flex items-center h-5 px-1 rounded-md bg-gray-100 leading-[18px] text-xs font-medium text-gray-500'>{(isChatModel ? DEFAULT_AGENT_PROMPT.chat : DEFAULT_AGENT_PROMPT.completion).length}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div
|
||||
className='sticky z-[5] bottom-0 w-full flex justify-end py-4 px-6 border-t bg-white '
|
||||
style={{
|
||||
borderColor: 'rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
className='mr-2 text-sm font-medium'
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
className='text-sm font-medium'
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t('common.operation.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(AgentSetting)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
type Props = {
|
||||
className?: string
|
||||
icon: JSX.Element
|
||||
name: string
|
||||
description: string
|
||||
children: JSX.Element
|
||||
}
|
||||
|
||||
const ItemPanel: FC<Props> = ({
|
||||
className,
|
||||
icon,
|
||||
name,
|
||||
description,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(className, 'flex justify-between items-center h-12 px-3 rounded-lg bg-gray-50')}>
|
||||
<div className='flex items-center'>
|
||||
{icon}
|
||||
<div className='ml-3 mr-1 leading-6 text-sm font-semibold text-gray-800'>{name}</div>
|
||||
<Tooltip
|
||||
htmlContent={
|
||||
<div className='w-[180px]'>
|
||||
{description}
|
||||
</div>
|
||||
}
|
||||
selector={`agent-setting-tooltip-${name}`}
|
||||
>
|
||||
<HelpCircle className='w-[14px] h-[14px] text-gray-400' />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ItemPanel)
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import Tools from '@/app/components/tools'
|
||||
import { LOC } from '@/app/components/tools/types'
|
||||
import Drawer from '@/app/components/base/drawer-plus'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import type { ModelConfig } from '@/models/debug'
|
||||
import I18n from '@/context/i18n'
|
||||
type Props = {
|
||||
show: boolean
|
||||
onHide: () => void
|
||||
selectedProviderId?: string
|
||||
}
|
||||
|
||||
const ChooseTool: FC<Props> = ({
|
||||
show,
|
||||
onHide,
|
||||
selectedProviderId,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const {
|
||||
modelConfig,
|
||||
setModelConfig,
|
||||
} = useContext(ConfigContext)
|
||||
if (!show)
|
||||
return null
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isShow
|
||||
onHide={onHide}
|
||||
title={t('tools.addTool') as string}
|
||||
panelClassName='mt-2 !w-[760px]'
|
||||
maxWidthClassName='!max-w-[760px]'
|
||||
height='calc(100vh - 16px)'
|
||||
contentClassName='!bg-gray-100'
|
||||
headerClassName='!border-b-black/5'
|
||||
body={
|
||||
<Tools
|
||||
loc={LOC.app}
|
||||
selectedProviderId={selectedProviderId}
|
||||
onAddTool={(collection, tool) => {
|
||||
const parameters: Record<string, string> = {}
|
||||
if (tool.parameters) {
|
||||
tool.parameters.forEach((item) => {
|
||||
parameters[item.name] = ''
|
||||
})
|
||||
}
|
||||
|
||||
const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => {
|
||||
draft.agentConfig.tools.push({
|
||||
provider_id: collection.id || collection.name,
|
||||
provider_type: collection.type,
|
||||
provider_name: collection.name,
|
||||
tool_name: tool.name,
|
||||
tool_label: tool.label[locale === 'en' ? 'en_US' : 'zh_Hans'],
|
||||
tool_parameters: parameters,
|
||||
enabled: true,
|
||||
})
|
||||
})
|
||||
setModelConfig(nexModelConfig)
|
||||
}}
|
||||
addedTools={(modelConfig?.agentConfig?.tools as any) || []}
|
||||
/>
|
||||
}
|
||||
isShowMask={true}
|
||||
clickOutsideNotOpen={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(ChooseTool)
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import produce from 'immer'
|
||||
import ChooseTool from './choose-tool'
|
||||
import SettingBuiltInTool from './setting-built-in-tool'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { HelpCircle, InfoCircle, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
|
||||
import { ToolsActive } from '@/app/components/base/icons/src/public/header-nav/tools'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import type { AgentTool } from '@/types/app'
|
||||
import { type Collection, CollectionType } from '@/app/components/tools/types'
|
||||
import { MAX_TOOLS_NUM } from '@/config'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
|
||||
|
||||
type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
|
||||
const AgentTools: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
|
||||
const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext)
|
||||
|
||||
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
|
||||
const [selectedProviderId, setSelectedProviderId] = useState<string | undefined>(undefined)
|
||||
const [isShowSettingTool, setIsShowSettingTool] = useState(false)
|
||||
const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => {
|
||||
const collection = collectionList.find(collection => collection.id === item.provider_id)
|
||||
const icon = collection?.icon
|
||||
return {
|
||||
...item,
|
||||
icon,
|
||||
collection,
|
||||
}
|
||||
})
|
||||
|
||||
const handleToolSettingChange = (value: Record<string, any>) => {
|
||||
const newModelConfig = produce(modelConfig, (draft) => {
|
||||
const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === currentTool?.collection?.id && item.tool_name === currentTool?.tool_name)
|
||||
if (tool)
|
||||
(tool as AgentTool).tool_parameters = value
|
||||
})
|
||||
setModelConfig(newModelConfig)
|
||||
setIsShowSettingTool(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Panel
|
||||
className="mt-4"
|
||||
noBodySpacing={tools.length === 0}
|
||||
headerIcon={
|
||||
<ToolsActive className='w-4 h-4 text-primary-500' />
|
||||
}
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-1'>{t('appDebug.agent.tools.name')}</div>
|
||||
<Tooltip htmlContent={<div className='w-[180px]'>
|
||||
{t('appDebug.agent.tools.description')}
|
||||
</div>} selector='config-tools-tooltip'>
|
||||
<HelpCircle className='w-[14px] h-[14px] text-gray-400' />
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
headerRight={
|
||||
<div className='flex items-center'>
|
||||
<div className='leading-[18px] text-xs font-normal text-gray-500'>{tools.filter((item: any) => !!item.enabled).length}/{tools.length} {t('appDebug.agent.tools.enabled')}</div>
|
||||
{tools.length < MAX_TOOLS_NUM && (
|
||||
<>
|
||||
<div className='ml-3 mr-1 h-3.5 w-px bg-gray-200'></div>
|
||||
<OperationBtn type="add" onClick={() => {
|
||||
setSelectedProviderId(undefined)
|
||||
setIsShowChooseTool(true)
|
||||
}} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className='flex items-center flex-wrap justify-between'>
|
||||
{tools.map((item: AgentTool & { icon: any; collection?: Collection }, index) => (
|
||||
<div key={index}
|
||||
className={cn((item.isDeleted || item.notAuthor) ? 'bg-white/50' : 'bg-white', (item.enabled && !item.isDeleted && !item.notAuthor) && 'shadow-xs', index > 1 && 'mt-1', 'group relative flex justify-between items-center last-of-type:mb-0 pl-2.5 py-2 pr-3 w-full rounded-lg border-[0.5px] border-gray-200 ')}
|
||||
style={{
|
||||
width: 'calc(50% - 2px)',
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
{(item.isDeleted || item.notAuthor)
|
||||
? (
|
||||
<DefaultToolIcon className='w-6 h-6' />
|
||||
)
|
||||
: (
|
||||
typeof item.icon === 'string'
|
||||
? (
|
||||
<div
|
||||
className='w-6 h-6 bg-cover bg-center rounded-md'
|
||||
style={{
|
||||
backgroundImage: `url(${item.icon})`,
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
: (
|
||||
<AppIcon
|
||||
className='rounded-md'
|
||||
size='tiny'
|
||||
icon={item.icon?.content}
|
||||
background={item.icon?.background}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
title={item.tool_name}
|
||||
className={cn((item.isDeleted || item.notAuthor) ? 'line-through opacity-50' : 'group-hover:max-w-[70px]', 'ml-2 max-w-[200px] leading-[18px] text-[13px] font-medium text-gray-800 truncate')}
|
||||
>
|
||||
{item.tool_label || item.tool_name}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
{(item.isDeleted || item.notAuthor)
|
||||
? (
|
||||
<div className='flex items-center'>
|
||||
<TooltipPlus
|
||||
popupContent={t(`tools.${item.isDeleted ? 'toolRemoved' : 'notAuthorized'}`)}
|
||||
>
|
||||
<div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
|
||||
if (item.notAuthor) {
|
||||
setSelectedProviderId(item.provider_id)
|
||||
setIsShowChooseTool(true)
|
||||
}
|
||||
}}>
|
||||
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
|
||||
<div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
|
||||
const newModelConfig = produce(modelConfig, (draft) => {
|
||||
draft.agentConfig.tools.splice(index, 1)
|
||||
})
|
||||
setModelConfig(newModelConfig)
|
||||
}}>
|
||||
<Trash03 className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
<div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='hidden group-hover:flex items-center'>
|
||||
{item.provider_type === CollectionType.builtIn && (
|
||||
<TooltipPlus
|
||||
popupContent={t('tools.setBuiltInTools.infoAndSetting')}
|
||||
>
|
||||
<div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
|
||||
setCurrentTool(item)
|
||||
setIsShowSettingTool(true)
|
||||
}}>
|
||||
<InfoCircle className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
)}
|
||||
|
||||
<div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
|
||||
const newModelConfig = produce(modelConfig, (draft) => {
|
||||
draft.agentConfig.tools.splice(index, 1)
|
||||
})
|
||||
setModelConfig(newModelConfig)
|
||||
}}>
|
||||
<Trash03 className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
<div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
|
||||
</div>
|
||||
)}
|
||||
<div className={cn((item.isDeleted || item.notAuthor) && 'opacity-50')}>
|
||||
<Switch
|
||||
defaultValue={(item.isDeleted || item.notAuthor) ? false : item.enabled}
|
||||
disabled={(item.isDeleted || item.notAuthor)}
|
||||
size='md'
|
||||
onChange={(enabled) => {
|
||||
const newModelConfig = produce(modelConfig, (draft) => {
|
||||
(draft.agentConfig.tools[index] as any).enabled = enabled
|
||||
})
|
||||
setModelConfig(newModelConfig)
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div >
|
||||
</Panel >
|
||||
{isShowChooseTool && (
|
||||
<ChooseTool
|
||||
show
|
||||
onHide={() => setIsShowChooseTool(false)}
|
||||
selectedProviderId={selectedProviderId}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
isShowSettingTool && (
|
||||
<SettingBuiltInTool
|
||||
toolName={currentTool?.tool_name as string}
|
||||
setting={currentTool?.tool_parameters as any}
|
||||
collection={currentTool?.collection as Collection}
|
||||
onSave={handleToolSettingChange}
|
||||
onHide={() => setIsShowSettingTool(false)}
|
||||
/>)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(AgentTools)
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import cn from 'classnames'
|
||||
import Drawer from '@/app/components/base/drawer-plus'
|
||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
||||
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import type { Collection, Tool } from '@/app/components/tools/types'
|
||||
import { fetchBuiltInToolList } from '@/service/tools'
|
||||
import I18n from '@/context/i18n'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common'
|
||||
|
||||
type Props = {
|
||||
collection: Collection
|
||||
toolName: string
|
||||
setting?: Record<string, any>
|
||||
readonly?: boolean
|
||||
onHide: () => void
|
||||
onSave?: (value: Record<string, any>) => void
|
||||
}
|
||||
|
||||
const SettingBuiltInTool: FC<Props> = ({
|
||||
collection,
|
||||
toolName,
|
||||
setting = {},
|
||||
readonly,
|
||||
onHide,
|
||||
onSave,
|
||||
}) => {
|
||||
const { locale } = useContext(I18n)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [tools, setTools] = useState<Tool[]>([])
|
||||
const currTool = tools.find(tool => tool.name === toolName)
|
||||
const formSchemas = currTool ? toolParametersToFormSchemas(currTool.parameters) : []
|
||||
const infoSchemas = formSchemas.filter((item: any) => item.form === 'llm')
|
||||
const settingSchemas = formSchemas.filter((item: any) => item.form !== 'llm')
|
||||
const hasSetting = settingSchemas.length > 0
|
||||
const [tempSetting, setTempSetting] = useState(setting)
|
||||
const [currType, setCurrType] = useState('info')
|
||||
const isInfoActive = currType === 'info'
|
||||
useEffect(() => {
|
||||
if (!collection)
|
||||
return
|
||||
|
||||
(async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const list = await fetchBuiltInToolList(collection.name) as Tool[]
|
||||
setTools(list)
|
||||
const currTool = list.find(tool => tool.name === toolName)
|
||||
if (currTool) {
|
||||
const formSchemas = toolParametersToFormSchemas(currTool.parameters)
|
||||
setTempSetting(addDefaultValue(setting, formSchemas))
|
||||
}
|
||||
}
|
||||
catch (e) { }
|
||||
setIsLoading(false)
|
||||
})()
|
||||
}, [collection?.name])
|
||||
|
||||
useEffect(() => {
|
||||
setCurrType((!readonly && hasSetting) ? 'setting' : 'info')
|
||||
}, [hasSetting])
|
||||
|
||||
const isValid = (() => {
|
||||
let valid = true
|
||||
settingSchemas.forEach((item: any) => {
|
||||
if (item.required && !tempSetting[item.name])
|
||||
valid = false
|
||||
})
|
||||
return valid
|
||||
})()
|
||||
|
||||
const infoUI = (
|
||||
<div className='pt-2'>
|
||||
<div className='leading-5 text-sm font-medium text-gray-900'>
|
||||
{t('tools.setBuiltInTools.toolDescription')}
|
||||
</div>
|
||||
<div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'>
|
||||
{currTool?.description[locale === 'en' ? 'en_US' : 'zh_Hans']}
|
||||
</div>
|
||||
|
||||
{infoSchemas.length > 0 && (
|
||||
<div className='mt-6'>
|
||||
<div className='flex items-center mb-4 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>
|
||||
<div className='mr-3'>{t('tools.setBuiltInTools.parameters')}</div>
|
||||
<div className='grow w-0 h-px bg-[#f3f4f6]'></div>
|
||||
</div>
|
||||
<div className='space-y-4'>
|
||||
{infoSchemas.map((item: any, index) => (
|
||||
<div key={index}>
|
||||
<div className='flex items-center space-x-2 leading-[18px]'>
|
||||
<div className='text-[13px] font-semibold text-gray-900'>{item.label[locale === 'en' ? 'en_US' : 'zh_Hans']}</div>
|
||||
<div className='text-xs font-medium text-gray-500'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div>
|
||||
{item.required && (
|
||||
<div className='text-xs font-medium text-[#EC4A0A]'>{t('tools.setBuiltInTools.required')}</div>
|
||||
)}
|
||||
</div>
|
||||
{item.human_description && (
|
||||
<div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'>
|
||||
{item.human_description?.[locale === 'en' ? 'en_US' : 'zh_Hans']}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
const setttingUI = (
|
||||
<Form
|
||||
value={tempSetting}
|
||||
onChange={setTempSetting}
|
||||
formSchemas={settingSchemas as any}
|
||||
isEditMode={false}
|
||||
showOnVariableMap={{}}
|
||||
validating={false}
|
||||
inputClassName='!bg-gray-50'
|
||||
readonly={readonly}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isShow
|
||||
onHide={onHide}
|
||||
title={(
|
||||
<div className='flex'>
|
||||
<div
|
||||
className='w-6 h-6 bg-cover bg-center rounded-md'
|
||||
style={{
|
||||
backgroundImage: `url(${collection.icon})`,
|
||||
}}
|
||||
></div>
|
||||
<div className='ml-2 leading-6 text-base font-semibold text-gray-900'>{currTool?.label[locale === 'en' ? 'en_US' : 'zh_Hans']}</div>
|
||||
{(hasSetting && !readonly) && (<>
|
||||
<DiagonalDividingLine className='mx-4' />
|
||||
<div className='flex space-x-6'>
|
||||
<div
|
||||
className={cn(isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base')}
|
||||
onClick={() => setCurrType('info')}
|
||||
>
|
||||
{t('tools.setBuiltInTools.info')}
|
||||
{isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>}
|
||||
</div>
|
||||
<div className={cn(!isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base ')}
|
||||
onClick={() => setCurrType('setting')}
|
||||
>
|
||||
{t('tools.setBuiltInTools.setting')}
|
||||
{!isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>}
|
||||
</div>
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
)}
|
||||
panelClassName='mt-[65px] !w-[480px]'
|
||||
maxWidthClassName='!max-w-[480px]'
|
||||
height='calc(100vh - 65px)'
|
||||
headerClassName='!border-b-black/5'
|
||||
body={
|
||||
<div className='h-full pt-3'>
|
||||
{isLoading
|
||||
? <div className='flex h-full items-center'>
|
||||
<Loading type='app' />
|
||||
</div>
|
||||
: (<div className='flex flex-col h-full'>
|
||||
<div className='grow h-0 overflow-y-auto px-6'>
|
||||
{isInfoActive ? infoUI : setttingUI}
|
||||
</div>
|
||||
{!readonly && !isInfoActive && (
|
||||
<div className='mt-2 shrink-0 flex justify-end py-4 px-6 space-x-2 rounded-b-[10px] bg-gray-50 border-t border-black/5'>
|
||||
<Button className='flex items-center h-8 !px-3 !text-[13px] font-medium !text-gray-700' onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
<Button className='flex items-center h-8 !px-3 !text-[13px] font-medium' type='primary' disabled={!isValid} onClick={() => onSave?.(addDefaultValue(tempSetting, formSchemas))}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>)}
|
||||
</div>
|
||||
}
|
||||
isShowMask={true}
|
||||
clickOutsideNotOpen={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(SettingBuiltInTool)
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import cn from 'classnames'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Clipboard, ClipboardCheck } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
|
||||
import s from '@/app/components/app/configuration/config-prompt/style.module.css'
|
||||
type Props = {
|
||||
className?: string
|
||||
type: 'first-prompt' | 'next-iteration'
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
const Editor: FC<Props> = ({
|
||||
className,
|
||||
type,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { notify } = useToastContext()
|
||||
|
||||
const [isCopied, setIsCopied] = React.useState(false)
|
||||
const {
|
||||
modelConfig,
|
||||
hasSetBlockStatus,
|
||||
dataSets,
|
||||
showSelectDataSet,
|
||||
externalDataToolsConfig,
|
||||
setExternalDataToolsConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const promptVariables = modelConfig.configs.prompt_variables
|
||||
const { setShowExternalDataToolModal } = useModalContext()
|
||||
const isFirstPrompt = type === 'first-prompt'
|
||||
const editorHeight = isFirstPrompt ? 'h-[336px]' : 'h-[52px]'
|
||||
|
||||
const handleOpenExternalDataToolModal = () => {
|
||||
setShowExternalDataToolModal({
|
||||
payload: {},
|
||||
onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
setExternalDataToolsConfig([...externalDataToolsConfig, newExternalDataTool])
|
||||
},
|
||||
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
|
||||
for (let i = 0; i < promptVariables.length; i++) {
|
||||
if (promptVariables[i].key === newExternalDataTool.variable) {
|
||||
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: promptVariables[i].key }) })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < externalDataToolsConfig.length; i++) {
|
||||
if (externalDataToolsConfig[i].variable === newExternalDataTool.variable) {
|
||||
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: externalDataToolsConfig[i].variable }) })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className={cn(className, s.gradientBorder, 'relative')}>
|
||||
<div className='rounded-xl bg-white'>
|
||||
<div className={cn(s.boxHeader, 'flex justify-between items-center h-11 pt-2 pr-3 pb-1 pl-4 rounded-tl-xl rounded-tr-xl bg-white hover:shadow-xs')}>
|
||||
<div className='text-sm font-semibold uppercase text-indigo-800'>{t(`appDebug.agent.${isFirstPrompt ? 'firstPrompt' : 'nextIteration'}`)}</div>
|
||||
<div className={cn(s.optionWrap, 'items-center space-x-1')}>
|
||||
{!isCopied
|
||||
? (
|
||||
<Clipboard className='h-6 w-6 p-1 text-gray-500 cursor-pointer' onClick={() => {
|
||||
copy(value)
|
||||
setIsCopied(true)
|
||||
}} />
|
||||
)
|
||||
: (
|
||||
<ClipboardCheck className='h-6 w-6 p-1 text-gray-500' />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(editorHeight, ' px-4 min-h-[102px] overflow-y-auto text-sm text-gray-700')}>
|
||||
<PromptEditor
|
||||
className={editorHeight}
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: true,
|
||||
selectable: !hasSetBlockStatus.context,
|
||||
datasets: dataSets.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
type: item.data_source_type,
|
||||
})),
|
||||
onAddContext: showSelectDataSet,
|
||||
}}
|
||||
variableBlock={{
|
||||
variables: modelConfig.configs.prompt_variables.map(item => ({
|
||||
name: item.name,
|
||||
value: item.key,
|
||||
})),
|
||||
externalTools: externalDataToolsConfig.map(item => ({
|
||||
name: item.label!,
|
||||
variableName: item.variable!,
|
||||
icon: item.icon,
|
||||
icon_background: item.icon_background,
|
||||
})),
|
||||
onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: '',
|
||||
},
|
||||
onEditRole: () => { },
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={() => { }}
|
||||
/>
|
||||
</div>
|
||||
<div className='pl-4 pb-2 flex'>
|
||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{value.length}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Editor)
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import AgentSetting from '../agent/agent-setting'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { BubbleText } from '@/app/components/base/icons/src/vender/solid/education'
|
||||
import Radio from '@/app/components/base/radio/ui'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/solid/arrows'
|
||||
import { CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import type { AgentConfig } from '@/models/debug'
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
disabled: boolean
|
||||
onChange: (value: string) => void
|
||||
isFunctionCall: boolean
|
||||
isChatModel: boolean
|
||||
agentConfig?: AgentConfig
|
||||
onAgentSettingChange: (payload: AgentConfig) => void
|
||||
}
|
||||
|
||||
type ItemProps = {
|
||||
text: string
|
||||
disabled: boolean
|
||||
value: string
|
||||
isChecked: boolean
|
||||
description: string
|
||||
Icon: any
|
||||
onClick: (value: string) => void
|
||||
}
|
||||
|
||||
const SelectItem: FC<ItemProps> = ({ text, value, Icon, isChecked, description, onClick, disabled }) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(disabled ? 'opacity-50' : 'cursor-pointer', isChecked ? 'border-[2px] border-indigo-600 shadow-sm' : 'border border-gray-100', 'mb-2 p-3 pr-4 rounded-xl bg-gray-25 hover:bg-gray-50')}
|
||||
onClick={() => !disabled && onClick(value)}
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center '>
|
||||
<div className='mr-3 p-1 bg-indigo-50 rounded-lg'>
|
||||
<Icon className='w-4 h-4 text-indigo-600' />
|
||||
</div>
|
||||
<div className='leading-5 text-sm font-medium text-gray-900'>{text}</div>
|
||||
</div>
|
||||
<Radio isChecked={isChecked} />
|
||||
</div>
|
||||
<div className='ml-9 leading-[18px] text-xs font-normal text-gray-500'>{description}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const AssistantTypePicker: FC<Props> = ({
|
||||
value,
|
||||
disabled,
|
||||
onChange,
|
||||
onAgentSettingChange,
|
||||
isFunctionCall,
|
||||
isChatModel,
|
||||
agentConfig,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const handleChange = (chosenValue: string) => {
|
||||
if (value === chosenValue)
|
||||
return
|
||||
|
||||
onChange(chosenValue)
|
||||
if (chosenValue !== 'agent')
|
||||
setOpen(false)
|
||||
}
|
||||
const isAgent = value === 'agent'
|
||||
const [isShowAgentSetting, setIsShowAgentSetting] = useState(false)
|
||||
|
||||
const agentConfigUI = (
|
||||
<>
|
||||
<div className='my-4 h-[1px] bg-gray-100'></div>
|
||||
<div
|
||||
className={cn(isAgent ? 'group cursor-pointer hover:bg-primary-50' : 'opacity-30', 'p-3 pr-4 rounded-xl bg-gray-50 ')}
|
||||
onClick={() => {
|
||||
if (isAgent) {
|
||||
setOpen(false)
|
||||
setIsShowAgentSetting(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center '>
|
||||
<div className='mr-3 p-1 bg-gray-200 group-hover:bg-white rounded-lg'>
|
||||
<Settings04 className='w-4 h-4 text-gray-600 group-hover:text-[#155EEF]' />
|
||||
</div>
|
||||
<div className='leading-5 text-sm font-medium text-gray-900 group-hover:text-[#155EEF]'>{t('appDebug.agent.setting.name')}</div>
|
||||
</div>
|
||||
<ArrowUpRight className='w-4 h-4 text-gray-500 group-hover:text-[#155EEF]' />
|
||||
</div>
|
||||
<div className='ml-9 leading-[18px] text-xs font-normal text-gray-500'>{t('appDebug.agent.setting.description')}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 8,
|
||||
crossAxis: -2,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className={cn(open && 'bg-gray-50', 'flex items-center h-8 px-3 border border-black/5 rounded-lg cursor-pointer select-none space-x-1 text-indigo-600')}>
|
||||
{isAgent ? <BubbleText className='w-3 h-3' /> : <CuteRobote className='w-3 h-3' />}
|
||||
<div className='text-xs font-medium'>{t(`appDebug.assistantType.${isAgent ? 'agentAssistant' : 'chatAssistant'}.name`)}</div>
|
||||
<ChevronDown className='w-3 h-3' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
|
||||
<div className='relative left-0.5 p-6 bg-white border border-black/[0.08] shadow-lg rounded-xl w-[480px]'>
|
||||
<div className='mb-2 leading-5 text-sm font-semibold text-gray-900'>{t('appDebug.assistantType.name')}</div>
|
||||
<SelectItem
|
||||
Icon={BubbleText}
|
||||
value='chat'
|
||||
disabled={disabled}
|
||||
text={t('appDebug.assistantType.chatAssistant.name')}
|
||||
description={t('appDebug.assistantType.chatAssistant.description')}
|
||||
isChecked={!isAgent}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
<SelectItem
|
||||
Icon={CuteRobote}
|
||||
value='agent'
|
||||
disabled={disabled}
|
||||
text={t('appDebug.assistantType.agentAssistant.name')}
|
||||
description={t('appDebug.assistantType.agentAssistant.description')}
|
||||
isChecked={isAgent}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
{!disabled && agentConfigUI}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
{isShowAgentSetting && (
|
||||
<AgentSetting
|
||||
isFunctionCall={isFunctionCall}
|
||||
payload={agentConfig as AgentConfig}
|
||||
isChatModel={isChatModel}
|
||||
onSave={(payloadNew) => {
|
||||
onAgentSettingChange(payloadNew)
|
||||
setIsShowAgentSetting(false)
|
||||
}}
|
||||
onCancel={() => setIsShowAgentSetting(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(AssistantTypePicker)
|
||||
|
|
@ -9,10 +9,10 @@ export type IAutomaticBtnProps = {
|
|||
|
||||
const leftIcon = (
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.31346 0.905711C4.21464 0.708087 4.01266 0.583252 3.79171 0.583252C3.57076 0.583252 3.36877 0.708087 3.26996 0.905711L2.81236 1.82091C2.64757 2.15048 2.59736 2.24532 2.53635 2.32447C2.47515 2.40386 2.40398 2.47503 2.32459 2.53623C2.24544 2.59724 2.1506 2.64745 1.82103 2.81224L0.905833 3.26984C0.708209 3.36865 0.583374 3.57064 0.583374 3.79159C0.583374 4.01254 0.708209 4.21452 0.905833 4.31333L1.82103 4.77094C2.1506 4.93572 2.24544 4.98593 2.32459 5.04694C2.40398 5.10814 2.47515 5.17931 2.53635 5.2587C2.59736 5.33785 2.64758 5.43269 2.81236 5.76226L3.26996 6.67746C3.36877 6.87508 3.57076 6.99992 3.79171 6.99992C4.01266 6.99992 4.21465 6.87508 4.31346 6.67746L4.77106 5.76226C4.93584 5.43269 4.98605 5.33786 5.04707 5.2587C5.10826 5.17931 5.17943 5.10814 5.25883 5.04694C5.33798 4.98593 5.43282 4.93572 5.76238 4.77094L6.67758 4.31333C6.87521 4.21452 7.00004 4.01254 7.00004 3.79159C7.00004 3.57064 6.87521 3.36865 6.67758 3.26984L5.76238 2.81224C5.43282 2.64745 5.33798 2.59724 5.25883 2.53623C5.17943 2.47503 5.10826 2.40386 5.04707 2.32447C4.98605 2.24532 4.93584 2.15048 4.77106 1.82091L4.31346 0.905711Z" fill="#444CE7"/>
|
||||
<path d="M11.375 1.74992C11.375 1.42775 11.1139 1.16659 10.7917 1.16659C10.4695 1.16659 10.2084 1.42775 10.2084 1.74992V2.62492H9.33337C9.01121 2.62492 8.75004 2.88609 8.75004 3.20825C8.75004 3.53042 9.01121 3.79159 9.33337 3.79159H10.2084V4.66659C10.2084 4.98875 10.4695 5.24992 10.7917 5.24992C11.1139 5.24992 11.375 4.98875 11.375 4.66659V3.79159H12.25C12.5722 3.79159 12.8334 3.53042 12.8334 3.20825C12.8334 2.88609 12.5722 2.62492 12.25 2.62492H11.375V1.74992Z" fill="#444CE7"/>
|
||||
<path d="M3.79171 9.33325C3.79171 9.01109 3.53054 8.74992 3.20837 8.74992C2.88621 8.74992 2.62504 9.01109 2.62504 9.33325V10.2083H1.75004C1.42787 10.2083 1.16671 10.4694 1.16671 10.7916C1.16671 11.1138 1.42787 11.3749 1.75004 11.3749H2.62504V12.2499C2.62504 12.5721 2.88621 12.8333 3.20837 12.8333C3.53054 12.8333 3.79171 12.5721 3.79171 12.2499V11.3749H4.66671C4.98887 11.3749 5.25004 11.1138 5.25004 10.7916C5.25004 10.4694 4.98887 10.2083 4.66671 10.2083H3.79171V9.33325Z" fill="#444CE7"/>
|
||||
<path d="M10.4385 6.73904C10.3396 6.54142 10.1377 6.41659 9.91671 6.41659C9.69576 6.41659 9.49377 6.54142 9.39496 6.73904L8.84014 7.84869C8.67535 8.17826 8.62514 8.27309 8.56413 8.35225C8.50293 8.43164 8.43176 8.50281 8.35237 8.56401C8.27322 8.62502 8.17838 8.67523 7.84881 8.84001L6.73917 9.39484C6.54154 9.49365 6.41671 9.69564 6.41671 9.91659C6.41671 10.1375 6.54154 10.3395 6.73917 10.4383L7.84881 10.9932C8.17838 11.1579 8.27322 11.2082 8.35237 11.2692C8.43176 11.3304 8.50293 11.4015 8.56413 11.4809C8.62514 11.5601 8.67535 11.6549 8.84014 11.9845L9.39496 13.0941C9.49377 13.2918 9.69576 13.4166 9.91671 13.4166C10.1377 13.4166 10.3396 13.2918 10.4385 13.0941L10.9933 11.9845C11.1581 11.6549 11.2083 11.5601 11.2693 11.4809C11.3305 11.4015 11.4017 11.3304 11.481 11.2692C11.5602 11.2082 11.655 11.1579 11.9846 10.9932L13.0942 10.4383C13.2919 10.3395 13.4167 10.1375 13.4167 9.91659C13.4167 9.69564 13.2919 9.49365 13.0942 9.39484L11.9846 8.84001C11.655 8.67523 11.5602 8.62502 11.481 8.56401C11.4017 8.50281 11.3305 8.43164 11.2693 8.35225C11.2083 8.27309 11.1581 8.17826 10.9933 7.84869L10.4385 6.73904Z" fill="#444CE7"/>
|
||||
<path d="M4.31346 0.905711C4.21464 0.708087 4.01266 0.583252 3.79171 0.583252C3.57076 0.583252 3.36877 0.708087 3.26996 0.905711L2.81236 1.82091C2.64757 2.15048 2.59736 2.24532 2.53635 2.32447C2.47515 2.40386 2.40398 2.47503 2.32459 2.53623C2.24544 2.59724 2.1506 2.64745 1.82103 2.81224L0.905833 3.26984C0.708209 3.36865 0.583374 3.57064 0.583374 3.79159C0.583374 4.01254 0.708209 4.21452 0.905833 4.31333L1.82103 4.77094C2.1506 4.93572 2.24544 4.98593 2.32459 5.04694C2.40398 5.10814 2.47515 5.17931 2.53635 5.2587C2.59736 5.33785 2.64758 5.43269 2.81236 5.76226L3.26996 6.67746C3.36877 6.87508 3.57076 6.99992 3.79171 6.99992C4.01266 6.99992 4.21465 6.87508 4.31346 6.67746L4.77106 5.76226C4.93584 5.43269 4.98605 5.33786 5.04707 5.2587C5.10826 5.17931 5.17943 5.10814 5.25883 5.04694C5.33798 4.98593 5.43282 4.93572 5.76238 4.77094L6.67758 4.31333C6.87521 4.21452 7.00004 4.01254 7.00004 3.79159C7.00004 3.57064 6.87521 3.36865 6.67758 3.26984L5.76238 2.81224C5.43282 2.64745 5.33798 2.59724 5.25883 2.53623C5.17943 2.47503 5.10826 2.40386 5.04707 2.32447C4.98605 2.24532 4.93584 2.15048 4.77106 1.82091L4.31346 0.905711Z" fill="#444CE7" />
|
||||
<path d="M11.375 1.74992C11.375 1.42775 11.1139 1.16659 10.7917 1.16659C10.4695 1.16659 10.2084 1.42775 10.2084 1.74992V2.62492H9.33337C9.01121 2.62492 8.75004 2.88609 8.75004 3.20825C8.75004 3.53042 9.01121 3.79159 9.33337 3.79159H10.2084V4.66659C10.2084 4.98875 10.4695 5.24992 10.7917 5.24992C11.1139 5.24992 11.375 4.98875 11.375 4.66659V3.79159H12.25C12.5722 3.79159 12.8334 3.53042 12.8334 3.20825C12.8334 2.88609 12.5722 2.62492 12.25 2.62492H11.375V1.74992Z" fill="#444CE7" />
|
||||
<path d="M3.79171 9.33325C3.79171 9.01109 3.53054 8.74992 3.20837 8.74992C2.88621 8.74992 2.62504 9.01109 2.62504 9.33325V10.2083H1.75004C1.42787 10.2083 1.16671 10.4694 1.16671 10.7916C1.16671 11.1138 1.42787 11.3749 1.75004 11.3749H2.62504V12.2499C2.62504 12.5721 2.88621 12.8333 3.20837 12.8333C3.53054 12.8333 3.79171 12.5721 3.79171 12.2499V11.3749H4.66671C4.98887 11.3749 5.25004 11.1138 5.25004 10.7916C5.25004 10.4694 4.98887 10.2083 4.66671 10.2083H3.79171V9.33325Z" fill="#444CE7" />
|
||||
<path d="M10.4385 6.73904C10.3396 6.54142 10.1377 6.41659 9.91671 6.41659C9.69576 6.41659 9.49377 6.54142 9.39496 6.73904L8.84014 7.84869C8.67535 8.17826 8.62514 8.27309 8.56413 8.35225C8.50293 8.43164 8.43176 8.50281 8.35237 8.56401C8.27322 8.62502 8.17838 8.67523 7.84881 8.84001L6.73917 9.39484C6.54154 9.49365 6.41671 9.69564 6.41671 9.91659C6.41671 10.1375 6.54154 10.3395 6.73917 10.4383L7.84881 10.9932C8.17838 11.1579 8.27322 11.2082 8.35237 11.2692C8.43176 11.3304 8.50293 11.4015 8.56413 11.4809C8.62514 11.5601 8.67535 11.6549 8.84014 11.9845L9.39496 13.0941C9.49377 13.2918 9.69576 13.4166 9.91671 13.4166C10.1377 13.4166 10.3396 13.2918 10.4385 13.0941L10.9933 11.9845C11.1581 11.6549 11.2083 11.5601 11.2693 11.4809C11.3305 11.4015 11.4017 11.3304 11.481 11.2692C11.5602 11.2082 11.655 11.1579 11.9846 10.9932L13.0942 10.4383C13.2919 10.3395 13.4167 10.1375 13.4167 9.91659C13.4167 9.69564 13.2919 9.49365 13.0942 9.39484L11.9846 8.84001C11.655 8.67523 11.5602 8.62502 11.481 8.56401C11.4017 8.50281 11.3305 8.43164 11.2693 8.35225C11.2083 8.27309 11.1581 8.17826 10.9933 7.84869L10.4385 6.73904Z" fill="#444CE7" />
|
||||
</svg>
|
||||
)
|
||||
const AutomaticBtn: FC<IAutomaticBtnProps> = ({
|
||||
|
|
@ -25,7 +25,7 @@ const AutomaticBtn: FC<IAutomaticBtnProps> = ({
|
|||
onClick={onClick}
|
||||
>
|
||||
{leftIcon}
|
||||
<span className='text-xs font-semibold text-indigo-600 uppercase'>{t('appDebug.operation.automatic')}</span>
|
||||
<span className='text-xs font-semibold text-indigo-600'>{t('appDebug.operation.automatic')}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 108 KiB |
|
|
@ -6,6 +6,7 @@
|
|||
height: 360px;
|
||||
background: center center no-repeat;
|
||||
background-size: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.wrap:hover .preview {
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
}
|
||||
|
||||
.openingStatementPreview {
|
||||
background-image: url(./preview-imgs/opening-statement.svg);
|
||||
background-image: url(./preview-imgs/opening-statement.png);
|
||||
}
|
||||
|
||||
.suggestedQuestionsAfterAnswerPreview {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { useContext } from 'use-context-selector'
|
|||
import produce from 'immer'
|
||||
import { useBoolean, useScroll } from 'ahooks'
|
||||
import DatasetConfig from '../dataset-config'
|
||||
import Tools from '../tools'
|
||||
import ChatGroup from '../features/chat-group'
|
||||
import ExperienceEnchanceGroup from '../features/experience-enchance-group'
|
||||
import Toolbox from '../toolbox'
|
||||
|
|
@ -15,11 +14,12 @@ import useAnnotationConfig from '../toolbox/annotation/use-annotation-config'
|
|||
import AddFeatureBtn from './feature/add-feature-btn'
|
||||
import ChooseFeature from './feature/choose-feature'
|
||||
import useFeature from './feature/use-feature'
|
||||
import AgentTools from './agent/agent-tools'
|
||||
import AdvancedModeWaring from '@/app/components/app/configuration/prompt-mode/advanced-mode-waring'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
||||
import ConfigVar from '@/app/components/app/configuration/config-var'
|
||||
import type { CitationConfig, ModelConfig, ModerationConfig, MoreLikeThisConfig, PromptVariable, SpeechToTextConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
|
||||
import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, PromptMode, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
|
||||
import { AppType, ModelModeType } from '@/types/app'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal'
|
||||
|
|
@ -32,11 +32,15 @@ const Config: FC = () => {
|
|||
mode,
|
||||
isAdvancedMode,
|
||||
modelModeType,
|
||||
isAgent,
|
||||
canReturnToSimpleMode,
|
||||
setPromptMode,
|
||||
hasSetBlockStatus,
|
||||
showHistoryModal,
|
||||
introduction,
|
||||
setIntroduction,
|
||||
suggestedQuestions,
|
||||
setSuggestedQuestions,
|
||||
modelConfig,
|
||||
setModelConfig,
|
||||
setPrevPromptConfig,
|
||||
|
|
@ -187,12 +191,12 @@ const Config: FC = () => {
|
|||
<>
|
||||
<div
|
||||
ref={wrapRef}
|
||||
className="relative px-6 pb-[50px] overflow-y-auto h-full"
|
||||
className="grow h-0 relative px-6 pb-[50px] overflow-y-auto"
|
||||
>
|
||||
<AddFeatureBtn toBottomHeight={toBottomHeight} onClick={showChooseFeatureTrue} />
|
||||
{
|
||||
(isAdvancedMode && canReturnToSimpleMode) && (
|
||||
<AdvancedModeWaring />
|
||||
(isAdvancedMode && canReturnToSimpleMode && !isAgent) && (
|
||||
<AdvancedModeWaring onReturnToSimpleMode={() => setPromptMode(PromptMode.simple)} />
|
||||
)
|
||||
}
|
||||
{showChooseFeature && (
|
||||
|
|
@ -223,8 +227,10 @@ const Config: FC = () => {
|
|||
{/* Dataset */}
|
||||
<DatasetConfig />
|
||||
|
||||
<Tools />
|
||||
|
||||
{/* Tools */}
|
||||
{(isAgent && isChatApp) && (
|
||||
<AgentTools />
|
||||
)}
|
||||
<ConfigVision />
|
||||
|
||||
{/* Chat History */}
|
||||
|
|
@ -244,6 +250,8 @@ const Config: FC = () => {
|
|||
{
|
||||
value: introduction,
|
||||
onChange: setIntroduction,
|
||||
suggestedQuestions,
|
||||
onSuggestedQuestionsChange: setSuggestedQuestions,
|
||||
}
|
||||
}
|
||||
isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ const DatasetConfig: FC = () => {
|
|||
modelConfig,
|
||||
setModelConfig,
|
||||
showSelectDataSet,
|
||||
isAgent,
|
||||
} = useContext(ConfigContext)
|
||||
|
||||
const hasData = dataSet.length > 0
|
||||
|
|
@ -72,7 +73,7 @@ const DatasetConfig: FC = () => {
|
|||
title={t('appDebug.feature.dataSet.title')}
|
||||
headerRight={
|
||||
<div className='flex items-center gap-1'>
|
||||
<ParamsConfig />
|
||||
{!isAgent && <ParamsConfig />}
|
||||
<OperationBtn type="add" onClick={showSelectDataSet} />
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
|||
<div className={labelClass}>
|
||||
{t('datasetSettings.form.desc')}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='w-full'>
|
||||
<textarea
|
||||
value={localeCurrentDataset.description || ''}
|
||||
onChange={e => handleValueChange('description', e.target.value)}
|
||||
|
|
@ -173,12 +173,12 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
|||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.permissions')}</div>
|
||||
</div>
|
||||
<div className='w-full sm:w-[480px]'>
|
||||
<div className='w-full'>
|
||||
<PermissionsRadio
|
||||
disable={!localeCurrentDataset?.embedding_available}
|
||||
value={localeCurrentDataset.permission}
|
||||
onChange={v => handleValueChange('permission', v!)}
|
||||
itemClassName='sm:!w-[227px]'
|
||||
itemClassName='sm:!w-[280px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -192,7 +192,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
|||
disable={!localeCurrentDataset?.embedding_available}
|
||||
value={indexMethod}
|
||||
onChange={v => setIndexMethod(v!)}
|
||||
itemClassName='sm:!w-[227px]'
|
||||
itemClassName='sm:!w-[280px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -201,7 +201,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
|
|||
<div className={labelClass}>
|
||||
{t('datasetSettings.form.embeddingModel')}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='w-full'>
|
||||
<div className='w-full h-9 rounded-lg bg-gray-100 opacity-60'>
|
||||
<ModelSelector
|
||||
readonly
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import useSWR from 'swr'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import produce from 'immer'
|
||||
import produce, { setAutoFreeze } from 'immer'
|
||||
import { useBoolean, useGetState } from 'ahooks'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import dayjs from 'dayjs'
|
||||
|
|
@ -12,7 +12,7 @@ import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
|
|||
import FormattingChanged from '../base/warning-mask/formatting-changed'
|
||||
import GroupName from '../base/group-name'
|
||||
import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset'
|
||||
import { AppType, ModelModeType, TransferMethod } from '@/types/app'
|
||||
import { AgentStrategy, AppType, ModelModeType, TransferMethod } from '@/types/app'
|
||||
import PromptValuePanel, { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
|
||||
import type { IChatItem } from '@/app/components/app/chat/type'
|
||||
import Chat from '@/app/components/app/chat'
|
||||
|
|
@ -44,6 +44,8 @@ const Debug: FC<IDebug> = ({
|
|||
const {
|
||||
appId,
|
||||
mode,
|
||||
isFunctionCall,
|
||||
collectionList,
|
||||
modelModeType,
|
||||
hasSetBlockStatus,
|
||||
isAdvancedMode,
|
||||
|
|
@ -51,6 +53,7 @@ const Debug: FC<IDebug> = ({
|
|||
chatPromptConfig,
|
||||
completionPromptConfig,
|
||||
introduction,
|
||||
suggestedQuestions,
|
||||
suggestedQuestionsAfterAnswerConfig,
|
||||
speechToTextConfig,
|
||||
citationConfig,
|
||||
|
|
@ -66,7 +69,6 @@ const Debug: FC<IDebug> = ({
|
|||
completionParams,
|
||||
hasSetContextVar,
|
||||
datasetConfigs,
|
||||
externalDataToolsConfig,
|
||||
visionConfig,
|
||||
annotationConfig,
|
||||
} = useContext(ConfigContext)
|
||||
|
|
@ -74,6 +76,13 @@ const Debug: FC<IDebug> = ({
|
|||
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
|
||||
const chatListDomRef = useRef<HTMLDivElement>(null)
|
||||
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
|
||||
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
|
||||
useEffect(() => {
|
||||
setAutoFreeze(false)
|
||||
return () => {
|
||||
setAutoFreeze(true)
|
||||
}
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
// scroll to bottom
|
||||
if (chatListDomRef.current)
|
||||
|
|
@ -88,9 +97,10 @@ const Debug: FC<IDebug> = ({
|
|||
content: getIntroduction(),
|
||||
isAnswer: true,
|
||||
isOpeningStatement: true,
|
||||
suggestedQuestions,
|
||||
}])
|
||||
}
|
||||
}, [introduction, modelConfig.configs.prompt_variables, inputs])
|
||||
}, [introduction, suggestedQuestions, modelConfig.configs.prompt_variables, inputs])
|
||||
|
||||
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||
|
|
@ -117,6 +127,7 @@ const Debug: FC<IDebug> = ({
|
|||
content: getIntroduction(),
|
||||
isAnswer: true,
|
||||
isOpeningStatement: true,
|
||||
suggestedQuestions,
|
||||
}]
|
||||
: [])
|
||||
setIsShowSuggestion(false)
|
||||
|
|
@ -150,7 +161,9 @@ const Debug: FC<IDebug> = ({
|
|||
}
|
||||
}
|
||||
let hasEmptyInput = ''
|
||||
const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required }) => {
|
||||
const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required, type }) => {
|
||||
if (type === 'api')
|
||||
return false
|
||||
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
|
||||
return res
|
||||
}) // compatible with old version
|
||||
|
|
@ -178,6 +191,7 @@ const Debug: FC<IDebug> = ({
|
|||
|
||||
const doShowSuggestion = isShowSuggestion && !isResponsing
|
||||
const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
|
||||
const [userQuery, setUserQuery] = useState('')
|
||||
const onSend = async (message: string, files?: VisionFile[]) => {
|
||||
if (isResponsing) {
|
||||
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
|
||||
|
|
@ -196,7 +210,28 @@ const Debug: FC<IDebug> = ({
|
|||
},
|
||||
}))
|
||||
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
|
||||
const updateCurrentQA = ({
|
||||
responseItem,
|
||||
questionId,
|
||||
placeholderAnswerId,
|
||||
questionItem,
|
||||
}: {
|
||||
responseItem: IChatItem
|
||||
questionId: string
|
||||
placeholderAnswerId: string
|
||||
questionItem: IChatItem
|
||||
}) => {
|
||||
// closesure new list is outdated.
|
||||
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)
|
||||
}
|
||||
const postModelConfig: BackendModelConfig = {
|
||||
pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '',
|
||||
prompt_type: promptMode,
|
||||
|
|
@ -212,10 +247,9 @@ const Debug: FC<IDebug> = ({
|
|||
speech_to_text: speechToTextConfig,
|
||||
retriever_resource: citationConfig,
|
||||
sensitive_word_avoidance: moderationConfig,
|
||||
external_data_tools: externalDataToolsConfig,
|
||||
agent_mode: {
|
||||
enabled: true,
|
||||
tools: [...postDatasets],
|
||||
...modelConfig.agentConfig,
|
||||
strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
|
||||
},
|
||||
model: {
|
||||
provider: modelConfig.provider,
|
||||
|
|
@ -223,7 +257,12 @@ const Debug: FC<IDebug> = ({
|
|||
mode: modelConfig.mode,
|
||||
completion_params: completionParams as any,
|
||||
},
|
||||
dataset_configs: datasetConfigs,
|
||||
dataset_configs: {
|
||||
...datasetConfigs,
|
||||
datasets: {
|
||||
datasets: [...postDatasets],
|
||||
} as any,
|
||||
},
|
||||
file_upload: {
|
||||
image: visionConfig,
|
||||
},
|
||||
|
|
@ -273,12 +312,17 @@ const Debug: FC<IDebug> = ({
|
|||
const newList = [...getChatList(), questionItem, placeholderAnswerItem]
|
||||
setChatList(newList)
|
||||
|
||||
let isAgentMode = false
|
||||
|
||||
// answer
|
||||
const responseItem: IChatItem = {
|
||||
id: `${Date.now()}`,
|
||||
content: '',
|
||||
agent_thoughts: [],
|
||||
message_files: [],
|
||||
isAnswer: true,
|
||||
}
|
||||
let hasSetResponseId = false
|
||||
|
||||
let _newConversationId: null | string = null
|
||||
|
||||
|
|
@ -290,25 +334,32 @@ const Debug: FC<IDebug> = ({
|
|||
setAbortController(abortController)
|
||||
},
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||
responseItem.content = responseItem.content + message
|
||||
// console.log('onData', message)
|
||||
if (!isAgentMode) {
|
||||
responseItem.content = responseItem.content + message
|
||||
}
|
||||
else {
|
||||
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
|
||||
if (lastThought)
|
||||
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
|
||||
}
|
||||
if (messageId && !hasSetResponseId) {
|
||||
responseItem.id = messageId
|
||||
hasSetResponseId = true
|
||||
}
|
||||
|
||||
if (isFirstMessage && newConversationId) {
|
||||
setConversationId(newConversationId)
|
||||
_newConversationId = newConversationId
|
||||
}
|
||||
setMessageTaskId(taskId)
|
||||
if (messageId)
|
||||
responseItem.id = messageId
|
||||
|
||||
// closesure new list is outdated.
|
||||
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)
|
||||
updateCurrentQA({
|
||||
responseItem,
|
||||
questionId,
|
||||
placeholderAnswerId,
|
||||
questionItem,
|
||||
})
|
||||
},
|
||||
async onCompleted(hasError?: boolean) {
|
||||
setResponsingFalse()
|
||||
|
|
@ -346,6 +397,45 @@ const Debug: FC<IDebug> = ({
|
|||
setIsShowSuggestion(true)
|
||||
}
|
||||
},
|
||||
onFile(file) {
|
||||
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
|
||||
if (lastThought)
|
||||
responseItem.agent_thoughts![responseItem.agent_thoughts!.length - 1].message_files = [...(lastThought as any).message_files, file]
|
||||
|
||||
updateCurrentQA({
|
||||
responseItem,
|
||||
questionId,
|
||||
placeholderAnswerId,
|
||||
questionItem,
|
||||
})
|
||||
},
|
||||
onThought(thought) {
|
||||
isAgentMode = true
|
||||
const response = responseItem as any
|
||||
if (thought.message_id && !hasSetResponseId)
|
||||
response.id = thought.message_id
|
||||
if (response.agent_thoughts.length === 0) {
|
||||
response.agent_thoughts.push(thought)
|
||||
}
|
||||
else {
|
||||
const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
|
||||
// thought changed but still the same thought, so update.
|
||||
if (lastThought.id === thought.id) {
|
||||
thought.thought = lastThought.thought
|
||||
thought.message_files = lastThought.message_files
|
||||
responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
|
||||
}
|
||||
else {
|
||||
responseItem.agent_thoughts!.push(thought)
|
||||
}
|
||||
}
|
||||
updateCurrentQA({
|
||||
responseItem,
|
||||
questionId,
|
||||
placeholderAnswerId,
|
||||
questionItem,
|
||||
})
|
||||
},
|
||||
onMessageEnd: (messageEnd) => {
|
||||
if (messageEnd.metadata?.annotation_reply) {
|
||||
responseItem.id = messageEnd.id
|
||||
|
|
@ -435,19 +525,23 @@ const Debug: FC<IDebug> = ({
|
|||
speech_to_text: speechToTextConfig,
|
||||
retriever_resource: citationConfig,
|
||||
sensitive_word_avoidance: moderationConfig,
|
||||
external_data_tools: externalDataToolsConfig,
|
||||
more_like_this: moreLikeThisConfig,
|
||||
agent_mode: {
|
||||
enabled: true,
|
||||
tools: [...postDatasets],
|
||||
},
|
||||
model: {
|
||||
provider: modelConfig.provider,
|
||||
name: modelConfig.model_id,
|
||||
mode: modelConfig.mode,
|
||||
completion_params: completionParams as any,
|
||||
},
|
||||
dataset_configs: datasetConfigs,
|
||||
agent_mode: {
|
||||
enabled: false,
|
||||
tools: [],
|
||||
},
|
||||
dataset_configs: {
|
||||
...datasetConfigs,
|
||||
datasets: {
|
||||
datasets: [...postDatasets],
|
||||
} as any,
|
||||
},
|
||||
file_upload: {
|
||||
image: visionConfig,
|
||||
},
|
||||
|
|
@ -506,6 +600,14 @@ const Debug: FC<IDebug> = ({
|
|||
}
|
||||
})
|
||||
|
||||
const allToolIcons = (() => {
|
||||
const icons: Record<string, any> = {}
|
||||
modelConfig.agentConfig.tools?.forEach((item: any) => {
|
||||
icons[item.tool_name] = collectionList.find((collection: any) => collection.id === item.provider_id)?.icon
|
||||
})
|
||||
return icons
|
||||
})()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="shrink-0">
|
||||
|
|
@ -539,6 +641,8 @@ const Debug: FC<IDebug> = ({
|
|||
<div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}>
|
||||
<Chat
|
||||
chatList={chatList}
|
||||
query={userQuery}
|
||||
onQueryChange={setUserQuery}
|
||||
onSend={onSend}
|
||||
checkCanSend={checkCanSend}
|
||||
feedbackDisabled
|
||||
|
|
@ -563,6 +667,7 @@ const Debug: FC<IDebug> = ({
|
|||
supportAnnotation
|
||||
appId={appId}
|
||||
onChatListChange={setChatList}
|
||||
allToolIcons={allToolIcons}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useContext } from 'use-context-selector'
|
|||
import produce from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { ReactSortable } from 'react-sortablejs'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
|
@ -15,11 +16,16 @@ import { getInputKeys } from '@/app/components/base/block-input'
|
|||
import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var'
|
||||
import { getNewVar } from '@/utils/var'
|
||||
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
|
||||
import { Plus, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
const MAX_QUESTION_NUM = 3
|
||||
|
||||
export type IOpeningStatementProps = {
|
||||
value: string
|
||||
readonly?: boolean
|
||||
onChange?: (value: string) => void
|
||||
suggestedQuestions?: string[]
|
||||
onSuggestedQuestionsChange?: (value: string[]) => void
|
||||
}
|
||||
|
||||
// regex to match the {{}} and replace it with a span
|
||||
|
|
@ -29,6 +35,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
value = '',
|
||||
readonly,
|
||||
onChange,
|
||||
suggestedQuestions = [],
|
||||
onSuggestedQuestionsChange = () => { },
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
|
|
@ -42,6 +50,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
const inputRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const [isFocus, { setTrue: didSetFocus, setFalse: setBlur }] = useBoolean(false)
|
||||
|
||||
const setFocus = () => {
|
||||
didSetFocus()
|
||||
setTimeout(() => {
|
||||
|
|
@ -58,6 +67,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
setTempValue(value || '')
|
||||
}, [value])
|
||||
|
||||
const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(suggestedQuestions || [])
|
||||
|
||||
const coloredContent = (tempValue || '')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
|
|
@ -75,6 +86,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
const handleCancel = () => {
|
||||
setBlur()
|
||||
setTempValue(value)
|
||||
setTempSuggestedQuestions(suggestedQuestions)
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
|
|
@ -97,6 +109,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
}
|
||||
setBlur()
|
||||
onChange?.(tempValue)
|
||||
onSuggestedQuestionsChange(tempSuggestedQuestions)
|
||||
}
|
||||
|
||||
const cancelAutoAddVar = () => {
|
||||
|
|
@ -107,7 +120,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
|
||||
const autoAddVar = () => {
|
||||
const newModelConfig = produce(modelConfig, (draft) => {
|
||||
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))]
|
||||
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key, 'string'))]
|
||||
})
|
||||
onChange?.(tempValue)
|
||||
setModelConfig(newModelConfig)
|
||||
|
|
@ -116,12 +129,99 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
}
|
||||
|
||||
const headerRight = !readonly ? (
|
||||
<OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} />
|
||||
isFocus ? (
|
||||
<div className='flex items-center space-x-1'>
|
||||
<div className='px-3 leading-[18px] text-xs font-medium text-gray-700 cursor-pointer' onClick={handleCancel}>{t('common.operation.cancel')}</div>
|
||||
<Button className='!h-8 !px-3 text-xs' onClick={handleConfirm} type="primary">{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
) : (
|
||||
<OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} />
|
||||
)
|
||||
) : null
|
||||
|
||||
const renderQuestions = () => {
|
||||
return isFocus ? (
|
||||
<div>
|
||||
<div className='flex items-center py-2'>
|
||||
<div className='shrink-0 flex space-x-0.5 leading-[18px] text-xs font-medium text-gray-500'>
|
||||
<div className='uppercase'>{t('appDebug.openingStatement.openingQuestion')}</div>
|
||||
<div>·</div>
|
||||
<div>{tempSuggestedQuestions.length}/{MAX_QUESTION_NUM}</div>
|
||||
</div>
|
||||
<div className='ml-3 grow w-0 h-px bg-[#243, 244, 246]'></div>
|
||||
</div>
|
||||
<ReactSortable
|
||||
className="space-y-1"
|
||||
list={tempSuggestedQuestions.map((name, index) => {
|
||||
return {
|
||||
id: index,
|
||||
name,
|
||||
}
|
||||
})}
|
||||
setList={list => setTempSuggestedQuestions(list.map(item => item.name))}
|
||||
handle='.handle'
|
||||
ghostClass="opacity-50"
|
||||
animation={150}
|
||||
>
|
||||
{tempSuggestedQuestions.map((question, index) => {
|
||||
return (
|
||||
<div className='group relative rounded-lg border border-gray-200 flex items-center pl-2.5 hover:border-gray-300 hover:bg-white' key={index}>
|
||||
<div className='handle flex items-center justify-center w-4 h-4 cursor-grab'>
|
||||
<svg width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M1 2C1.55228 2 2 1.55228 2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2ZM1 6C1.55228 6 2 5.55228 2 5C2 4.44772 1.55228 4 1 4C0.447715 4 0 4.44772 0 5C0 5.55228 0.447715 6 1 6ZM6 1C6 1.55228 5.55228 2 5 2C4.44772 2 4 1.55228 4 1C4 0.447715 4.44772 0 5 0C5.55228 0 6 0.447715 6 1ZM5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6ZM2 9C2 9.55229 1.55228 10 1 10C0.447715 10 0 9.55229 0 9C0 8.44771 0.447715 8 1 8C1.55228 8 2 8.44771 2 9ZM5 10C5.55228 10 6 9.55229 6 9C6 8.44771 5.55228 8 5 8C4.44772 8 4 8.44771 4 9C4 9.55229 4.44772 10 5 10Z" fill="#98A2B3" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="input"
|
||||
value={question || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => {
|
||||
if (index === i)
|
||||
return value
|
||||
|
||||
return item
|
||||
}))
|
||||
}}
|
||||
className={'w-full overflow-x-auto pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer rounded-lg'}
|
||||
/>
|
||||
|
||||
<div
|
||||
className='block absolute top-1/2 translate-y-[-50%] right-1.5 p-1 rounded-md cursor-pointer hover:bg-[#FEE4E2] hover:text-[#D92D20]'
|
||||
onClick={() => {
|
||||
setTempSuggestedQuestions(tempSuggestedQuestions.filter((_, i) => index !== i))
|
||||
}}
|
||||
>
|
||||
<Trash03 className='w-3.5 h-3.5' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}</ReactSortable>
|
||||
{tempSuggestedQuestions.length < MAX_QUESTION_NUM && (
|
||||
<div
|
||||
onClick={() => { setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }}
|
||||
className='mt-1 flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100 hover:bg-gray-200'>
|
||||
<Plus className='w-4 h-4'></Plus>
|
||||
<div className='text-gray-500 text-[13px]'>{t('appDebug.variableConig.addOption')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className='mt-1.5 flex flex-wrap'>
|
||||
{tempSuggestedQuestions.map((question, index) => {
|
||||
return (
|
||||
<div key={index} className='mt-1 mr-1 max-w-full truncate last:mr-0 shrink-0 leading-8 items-center px-2.5 rounded-lg border border-gray-200 shadow-xs bg-white text-[13px] font-normal text-gray-900 cursor-pointer'>
|
||||
{question}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
className={cn(isShowConfirmAddVar && 'h-[220px]', 'relative mt-4')}
|
||||
className={cn(isShowConfirmAddVar && 'h-[220px]', 'relative mt-4 !bg-gray-25')}
|
||||
title={t('appDebug.openingStatement.title')}
|
||||
headerIcon={
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
|
@ -137,34 +237,24 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
|||
<>
|
||||
{isFocus
|
||||
? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={tempValue}
|
||||
rows={3}
|
||||
onChange={e => setTempValue(e.target.value)}
|
||||
className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none "
|
||||
placeholder={t('appDebug.openingStatement.placeholder') as string}
|
||||
>
|
||||
</textarea>
|
||||
<div>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={tempValue}
|
||||
rows={3}
|
||||
onChange={e => setTempValue(e.target.value)}
|
||||
className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none "
|
||||
placeholder={t('appDebug.openingStatement.placeholder') as string}
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div dangerouslySetInnerHTML={{
|
||||
__html: coloredContent,
|
||||
}}></div>
|
||||
)}
|
||||
|
||||
{/* Operation Bar */}
|
||||
{isFocus && (
|
||||
<div className='mt-2 flex items-center justify-between'>
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<Button className='!h-8 text-sm' onClick={handleCancel}>{t('common.operation.cancel')}</Button>
|
||||
<Button className='!h-8 text-sm' onClick={handleConfirm} type="primary">{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{renderQuestions()}
|
||||
</>) : (
|
||||
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import { clone, isEqual } from 'lodash-es'
|
|||
import { CodeBracketIcon } from '@heroicons/react/20/solid'
|
||||
import Button from '../../base/button'
|
||||
import Loading from '../../base/loading'
|
||||
import s from './style.module.css'
|
||||
import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config'
|
||||
import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal'
|
||||
import AssistantTypePicker from './config/assistant-type-picker'
|
||||
import type {
|
||||
AnnotationReplyConfig,
|
||||
DatasetConfigs,
|
||||
|
|
@ -37,10 +37,9 @@ import { fetchAppDetail, updateAppModelConfig } from '@/service/apps'
|
|||
import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config'
|
||||
import { fetchDatasets } from '@/service/datasets'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app'
|
||||
import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app'
|
||||
import { PromptMode } from '@/models/debug'
|
||||
import { ANNOTATION_DEFAULT, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
|
||||
import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
|
||||
import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
|
||||
import I18n from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
|
|
@ -49,6 +48,8 @@ import Drawer from '@/app/components/base/drawer'
|
|||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { fetchCollectionList } from '@/service/tools'
|
||||
import { type Collection } from '@/app/components/tools/types'
|
||||
|
||||
type PublichConfig = {
|
||||
modelConfig: ModelConfig
|
||||
|
|
@ -75,6 +76,7 @@ const Configuration: FC = () => {
|
|||
const [isShowDebugPanel, { setTrue: showDebugPanel, setFalse: hideDebugPanel }] = useBoolean(false)
|
||||
|
||||
const [introduction, setIntroduction] = useState<string>('')
|
||||
const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([])
|
||||
const [controlClearChatMessage, setControlClearChatMessage] = useState(0)
|
||||
const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({
|
||||
prompt_template: '',
|
||||
|
|
@ -141,8 +143,23 @@ const Configuration: FC = () => {
|
|||
retriever_resource: null,
|
||||
sensitive_word_avoidance: null,
|
||||
dataSets: [],
|
||||
agentConfig: DEFAULT_AGENT_SETTING,
|
||||
})
|
||||
const isChatApp = mode === AppType.chat
|
||||
const isAgent = modelConfig.agentConfig?.enabled
|
||||
const setIsAgent = (value: boolean) => {
|
||||
const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
|
||||
draft.agentConfig.enabled = value
|
||||
})
|
||||
doSetModelConfig(newModelConfig)
|
||||
}
|
||||
const isOpenAI = modelConfig.provider === 'openai'
|
||||
const isFunctionCall = isOpenAI && modelConfig.mode === ModelModeType.chat
|
||||
|
||||
const [collectionList, setCollectionList] = useState<Collection[]>([])
|
||||
useEffect(() => {
|
||||
|
||||
}, [])
|
||||
const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({
|
||||
retrieval_model: RETRIEVE_TYPE.oneWay,
|
||||
reranking_model: {
|
||||
|
|
@ -152,6 +169,9 @@ const Configuration: FC = () => {
|
|||
top_k: 2,
|
||||
score_threshold_enabled: false,
|
||||
score_threshold: 0.7,
|
||||
datasets: {
|
||||
datasets: [],
|
||||
},
|
||||
})
|
||||
|
||||
const setModelConfig = (newModelConfig: ModelConfig) => {
|
||||
|
|
@ -165,7 +185,7 @@ const Configuration: FC = () => {
|
|||
}, [modelModeType])
|
||||
|
||||
const [dataSets, setDataSets] = useState<DataSet[]>([])
|
||||
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
|
||||
const contextVar = modelConfig.configs.prompt_variables.find((item: any) => item.is_context_var)?.key
|
||||
const hasSetContextVar = !!contextVar
|
||||
const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false)
|
||||
const selectedIds = dataSets.map(item => item.id)
|
||||
|
|
@ -335,83 +355,131 @@ const Configuration: FC = () => {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => {
|
||||
setMode(res.mode)
|
||||
const modelConfig = res.model_config
|
||||
const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple
|
||||
doSetPromptMode(promptMode)
|
||||
if (promptMode === PromptMode.advanced) {
|
||||
setChatPromptConfig(modelConfig.chat_prompt_config || clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
|
||||
setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any)
|
||||
setCanReturnToSimpleMode(false)
|
||||
}
|
||||
(async () => {
|
||||
const collectionList = await fetchCollectionList() as Collection[]
|
||||
setCollectionList(collectionList)
|
||||
fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => {
|
||||
setMode(res.mode)
|
||||
const modelConfig = res.model_config
|
||||
const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple
|
||||
doSetPromptMode(promptMode)
|
||||
if (promptMode === PromptMode.advanced) {
|
||||
setChatPromptConfig(modelConfig.chat_prompt_config || clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
|
||||
setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any)
|
||||
setCanReturnToSimpleMode(false)
|
||||
}
|
||||
|
||||
const model = res.model_config.model
|
||||
const model = res.model_config.model
|
||||
|
||||
let datasets: any = null
|
||||
if (modelConfig.agent_mode?.enabled)
|
||||
datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
|
||||
let datasets: any = null
|
||||
// old dataset struct
|
||||
if (modelConfig.agent_mode?.tools?.find(({ dataset }: any) => dataset?.enabled))
|
||||
datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
|
||||
// new dataset struct
|
||||
else if (modelConfig.dataset_configs.datasets?.datasets?.length > 0)
|
||||
datasets = modelConfig.dataset_configs?.datasets?.datasets
|
||||
|
||||
if (dataSets && datasets?.length && datasets?.length > 0) {
|
||||
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } })
|
||||
datasets = dataSetsWithDetail
|
||||
setDataSets(datasets)
|
||||
}
|
||||
if (dataSets && datasets?.length && datasets?.length > 0) {
|
||||
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } })
|
||||
datasets = dataSetsWithDetail
|
||||
setDataSets(datasets)
|
||||
}
|
||||
|
||||
setIntroduction(modelConfig.opening_statement)
|
||||
if (modelConfig.more_like_this)
|
||||
setMoreLikeThisConfig(modelConfig.more_like_this)
|
||||
setIntroduction(modelConfig.opening_statement)
|
||||
setSuggestedQuestions(modelConfig.suggested_questions || [])
|
||||
if (modelConfig.more_like_this)
|
||||
setMoreLikeThisConfig(modelConfig.more_like_this)
|
||||
|
||||
if (modelConfig.suggested_questions_after_answer)
|
||||
setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer)
|
||||
if (modelConfig.suggested_questions_after_answer)
|
||||
setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer)
|
||||
|
||||
if (modelConfig.speech_to_text)
|
||||
setSpeechToTextConfig(modelConfig.speech_to_text)
|
||||
if (modelConfig.speech_to_text)
|
||||
setSpeechToTextConfig(modelConfig.speech_to_text)
|
||||
|
||||
if (modelConfig.retriever_resource)
|
||||
setCitationConfig(modelConfig.retriever_resource)
|
||||
if (modelConfig.retriever_resource)
|
||||
setCitationConfig(modelConfig.retriever_resource)
|
||||
|
||||
if (modelConfig.annotation_reply)
|
||||
setAnnotationConfig(modelConfig.annotation_reply, true)
|
||||
if (modelConfig.annotation_reply)
|
||||
setAnnotationConfig(modelConfig.annotation_reply, true)
|
||||
|
||||
if (modelConfig.sensitive_word_avoidance)
|
||||
setModerationConfig(modelConfig.sensitive_word_avoidance)
|
||||
if (modelConfig.sensitive_word_avoidance)
|
||||
setModerationConfig(modelConfig.sensitive_word_avoidance)
|
||||
|
||||
if (modelConfig.external_data_tools)
|
||||
setExternalDataToolsConfig(modelConfig.external_data_tools)
|
||||
if (modelConfig.external_data_tools)
|
||||
setExternalDataToolsConfig(modelConfig.external_data_tools)
|
||||
|
||||
const config = {
|
||||
modelConfig: {
|
||||
provider: model.provider,
|
||||
model_id: model.name,
|
||||
mode: model.mode,
|
||||
configs: {
|
||||
prompt_template: modelConfig.pre_prompt,
|
||||
prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form, modelConfig.dataset_query_variable),
|
||||
const config = {
|
||||
modelConfig: {
|
||||
provider: model.provider,
|
||||
model_id: model.name,
|
||||
mode: model.mode,
|
||||
configs: {
|
||||
prompt_template: modelConfig.pre_prompt,
|
||||
prompt_variables: userInputsFormToPromptVariables(
|
||||
[
|
||||
...modelConfig.user_input_form,
|
||||
...(
|
||||
modelConfig.external_data_tools?.length
|
||||
? modelConfig.external_data_tools.map((item: any) => {
|
||||
return {
|
||||
external_data_tool: {
|
||||
variable: item.variable as string,
|
||||
label: item.label as string,
|
||||
enabled: item.enabled,
|
||||
type: item.type as string,
|
||||
config: item.config,
|
||||
required: true,
|
||||
icon: item.icon,
|
||||
icon_background: item.icon_background,
|
||||
},
|
||||
}
|
||||
})
|
||||
: []
|
||||
),
|
||||
],
|
||||
modelConfig.dataset_query_variable,
|
||||
),
|
||||
},
|
||||
opening_statement: modelConfig.opening_statement,
|
||||
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,
|
||||
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
|
||||
external_data_tools: modelConfig.external_data_tools,
|
||||
dataSets: datasets || [],
|
||||
// eslint-disable-next-line multiline-ternary
|
||||
agentConfig: res.is_agent ? {
|
||||
max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
|
||||
...modelConfig.agent_mode,
|
||||
// remove dataset
|
||||
enabled: true, // modelConfig.agent_mode?.enabled is not correct. old app: the value of app with dataset's is always true
|
||||
tools: modelConfig.agent_mode?.tools.filter((tool: any) => {
|
||||
return !tool.dataset
|
||||
}).map((tool: any) => {
|
||||
return {
|
||||
...tool,
|
||||
isDeleted: res.deleted_tools?.includes(tool.tool_name),
|
||||
notAuthor: collectionList.find(c => tool.provider_id === c.id)?.is_team_authorization === false,
|
||||
}
|
||||
}),
|
||||
} : DEFAULT_AGENT_SETTING,
|
||||
},
|
||||
opening_statement: modelConfig.opening_statement,
|
||||
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,
|
||||
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
|
||||
external_data_tools: modelConfig.external_data_tools,
|
||||
dataSets: datasets || [],
|
||||
},
|
||||
completionParams: model.completion_params,
|
||||
}
|
||||
completionParams: model.completion_params,
|
||||
}
|
||||
|
||||
if (modelConfig.file_upload)
|
||||
setVisionConfig(modelConfig.file_upload.image, true)
|
||||
if (modelConfig.file_upload)
|
||||
setVisionConfig(modelConfig.file_upload.image, true)
|
||||
|
||||
syncToPublishedConfig(config)
|
||||
setPublishedConfig(config)
|
||||
setDatasetConfigs({
|
||||
retrieval_model: RETRIEVE_TYPE.oneWay,
|
||||
...modelConfig.dataset_configs,
|
||||
syncToPublishedConfig(config)
|
||||
setPublishedConfig(config)
|
||||
setDatasetConfigs({
|
||||
retrieval_model: RETRIEVE_TYPE.oneWay,
|
||||
...modelConfig.dataset_configs,
|
||||
})
|
||||
setHasFetchedDetail(true)
|
||||
})
|
||||
setHasFetchedDetail(true)
|
||||
})
|
||||
})()
|
||||
}, [appId])
|
||||
|
||||
const promptEmpty = (() => {
|
||||
|
|
@ -420,7 +488,7 @@ const Configuration: FC = () => {
|
|||
|
||||
if (isAdvancedMode) {
|
||||
if (modelModeType === ModelModeType.chat)
|
||||
return chatPromptConfig.prompt.every(({ text }) => !text)
|
||||
return chatPromptConfig.prompt.every(({ text }: any) => !text)
|
||||
|
||||
else
|
||||
return !completionPromptConfig.prompt.text
|
||||
|
|
@ -487,15 +555,15 @@ const Configuration: FC = () => {
|
|||
user_input_form: promptVariablesToUserInputsForm(promptVariables),
|
||||
dataset_query_variable: contextVar || '',
|
||||
opening_statement: introduction || '',
|
||||
suggested_questions: suggestedQuestions || [],
|
||||
more_like_this: moreLikeThisConfig,
|
||||
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
|
||||
speech_to_text: speechToTextConfig,
|
||||
retriever_resource: citationConfig,
|
||||
sensitive_word_avoidance: moderationConfig,
|
||||
external_data_tools: externalDataToolsConfig,
|
||||
agent_mode: {
|
||||
enabled: true,
|
||||
tools: [...postDatasets],
|
||||
...modelConfig.agentConfig,
|
||||
strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
|
||||
},
|
||||
model: {
|
||||
provider: modelConfig.provider,
|
||||
|
|
@ -503,7 +571,12 @@ const Configuration: FC = () => {
|
|||
mode: modelConfig.mode,
|
||||
completion_params: completionParams as any,
|
||||
},
|
||||
dataset_configs: datasetConfigs,
|
||||
dataset_configs: {
|
||||
...datasetConfigs,
|
||||
datasets: {
|
||||
datasets: [...postDatasets],
|
||||
} as any,
|
||||
},
|
||||
file_upload: {
|
||||
image: visionConfig,
|
||||
},
|
||||
|
|
@ -558,6 +631,10 @@ const Configuration: FC = () => {
|
|||
modelModeType,
|
||||
promptMode,
|
||||
isAdvancedMode,
|
||||
isAgent,
|
||||
isOpenAI,
|
||||
isFunctionCall,
|
||||
collectionList,
|
||||
setPromptMode,
|
||||
canReturnToSimpleMode,
|
||||
setCanReturnToSimpleMode,
|
||||
|
|
@ -572,6 +649,8 @@ const Configuration: FC = () => {
|
|||
conversationId,
|
||||
introduction,
|
||||
setIntroduction,
|
||||
suggestedQuestions,
|
||||
setSuggestedQuestions,
|
||||
setConversationId,
|
||||
controlClearChatMessage,
|
||||
setControlClearChatMessage,
|
||||
|
|
@ -614,70 +693,73 @@ const Configuration: FC = () => {
|
|||
>
|
||||
<>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className='flex items-center justify-between px-6 shrink-0 py-3 flex-wrap gap-y-2'>
|
||||
<div className='flex items-end'>
|
||||
<div className={s.promptTitle}></div>
|
||||
<div className='flex items-center h-[14px] space-x-1 text-xs'>
|
||||
{/* modelModeType missing can not load template */}
|
||||
{(!isAdvancedMode && modelModeType) && (
|
||||
<div
|
||||
onClick={() => setPromptMode(PromptMode.advanced)}
|
||||
className={'cursor-pointer text-indigo-600'}
|
||||
>
|
||||
{t('appDebug.promptMode.simple')}
|
||||
</div>
|
||||
)}
|
||||
{isAdvancedMode && (
|
||||
<div className='flex items-center space-x-2'>
|
||||
<div className={cn(locale === 'en' && 'italic', `${s.advancedPromptMode} text-indigo-600`)}>{t('appDebug.promptMode.advanced')}</div>
|
||||
{canReturnToSimpleMode && (
|
||||
<div
|
||||
onClick={() => setPromptMode(PromptMode.simple)}
|
||||
className='flex items-center h-6 px-2 bg-indigo-600 shadow-xs border border-gray-200 rounded-lg text-white text-xs font-semibold cursor-pointer space-x-1'
|
||||
>
|
||||
<FlipBackward className='w-3 h-3 text-white' />
|
||||
<div className='text-xs font-semibold uppercase'>{t('appDebug.promptMode.switchBack')}</div>
|
||||
</div>
|
||||
<div className='flex grow h-[200px]'>
|
||||
<div className="w-full sm:w-1/2 shrink-0 flex flex-col h-full">
|
||||
{/* Header Left */}
|
||||
<div className='flex justify-between items-center px-6 h-14'>
|
||||
<div className='flex items-center'>
|
||||
<div className='leading-6 text-base font-semibold text-gray-900'>{t('appDebug.orchestrate')}</div>
|
||||
<div className='flex items-center h-[14px] space-x-1 text-xs'>
|
||||
{isAdvancedMode && (
|
||||
<div className='ml-1 flex items-center h-5 px-1.5 border border-gray-100 rounded-md text-[11px] font-medium text-gray-500 uppercase'>{t('appDebug.promptMode.advanced')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isChatApp && (
|
||||
<AssistantTypePicker
|
||||
value={isAgent ? 'agent' : 'assistant'}
|
||||
disabled={isAdvancedMode && !canReturnToSimpleMode}
|
||||
onChange={(value: string) => {
|
||||
setIsAgent(value === 'agent')
|
||||
if (value === 'agent')
|
||||
setPromptMode(PromptMode.simple)
|
||||
}}
|
||||
isFunctionCall={isFunctionCall}
|
||||
isChatModel={modelConfig.mode === ModelModeType.chat}
|
||||
agentConfig={modelConfig.agentConfig}
|
||||
onAgentSettingChange={(config) => {
|
||||
const nextConfig = produce(modelConfig, (draft: ModelConfig) => {
|
||||
draft.agentConfig = config
|
||||
})
|
||||
setModelConfig(nextConfig)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center flex-wrap gap-y-2 gap-x-2'>
|
||||
{/* Model and Parameters */}
|
||||
<ModelParameterModal
|
||||
isAdvancedMode={isAdvancedMode}
|
||||
mode={mode}
|
||||
provider={modelConfig.provider}
|
||||
completionParams={completionParams}
|
||||
modelId={modelConfig.model_id}
|
||||
setModel={setModel as any}
|
||||
onCompletionParamsChange={(newParams: FormValue) => {
|
||||
setCompletionParams(newParams)
|
||||
}}
|
||||
/>
|
||||
<div className='w-[1px] h-[14px] bg-gray-200'></div>
|
||||
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
|
||||
{isMobile && (
|
||||
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
|
||||
<span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
|
||||
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
|
||||
</Button>
|
||||
)}
|
||||
<Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex grow h-[200px]'>
|
||||
<div className="w-full sm:w-1/2 shrink-0">
|
||||
<Config />
|
||||
</div>
|
||||
{!isMobile && <div className="relative w-1/2 grow h-full overflow-y-auto py-4 px-6 bg-gray-50 flex flex-col rounded-tl-2xl border-t border-l" style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||
<Debug
|
||||
hasSetAPIKEY={hasSettedApiKey}
|
||||
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
||||
inputs={inputs}
|
||||
/>
|
||||
{!isMobile && <div className="relative w-1/2 h-full overflow-y-auto flex flex-col " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||
{/* Header Right */}
|
||||
<div className='flex justify-end items-center flex-wrap px-6 h-14 space-x-2'>
|
||||
{/* Model and Parameters */}
|
||||
<ModelParameterModal
|
||||
isAdvancedMode={isAdvancedMode}
|
||||
mode={mode}
|
||||
provider={modelConfig.provider}
|
||||
completionParams={completionParams}
|
||||
modelId={modelConfig.model_id}
|
||||
setModel={setModel as any}
|
||||
onCompletionParamsChange={(newParams: FormValue) => {
|
||||
setCompletionParams(newParams)
|
||||
}}
|
||||
/>
|
||||
<div className='w-[1px] h-[14px] bg-gray-200'></div>
|
||||
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
|
||||
{isMobile && (
|
||||
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
|
||||
<span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
|
||||
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
|
||||
</Button>
|
||||
)}
|
||||
<Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
|
||||
</div>
|
||||
<div className='flex flex-col grow h-0 px-6 py-4 rounded-tl-2xl border-t border-l bg-gray-50 '>
|
||||
<Debug
|
||||
hasSetAPIKEY={hasSettedApiKey}
|
||||
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
|
||||
inputs={inputs}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,15 @@ import React from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import I18n from '@/context/i18n'
|
||||
import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
const AdvancedModeWarning: FC = () => {
|
||||
type Props = {
|
||||
onReturnToSimpleMode: () => void
|
||||
}
|
||||
|
||||
const AdvancedModeWarning: FC<Props> = ({
|
||||
onReturnToSimpleMode,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const [show, setShow] = React.useState(true)
|
||||
|
|
@ -26,10 +33,20 @@ const AdvancedModeWarning: FC = () => {
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className='flex items-center h-6 px-2 rounded-md bg-[#fff] border border-gray-200 shadow-xs text-xs font-medium text-primary-600 cursor-pointer'
|
||||
onClick={() => setShow(false)}
|
||||
>{t('appDebug.promptMode.advancedWarning.ok')}</div>
|
||||
<div className='flex items-center space-x-1'>
|
||||
<div
|
||||
onClick={onReturnToSimpleMode}
|
||||
className='shrink-0 flex items-center h-6 px-2 bg-indigo-600 shadow-xs border border-gray-200 rounded-lg text-white text-xs font-semibold cursor-pointer space-x-1'
|
||||
>
|
||||
<FlipBackward className='w-3 h-3 text-white' />
|
||||
<div className='text-xs font-semibold uppercase'>{t('appDebug.promptMode.switchBack')}</div>
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center h-6 px-2 rounded-md bg-[#fff] border border-gray-200 shadow-xs text-xs font-medium text-primary-600 cursor-pointer'
|
||||
onClick={() => setShow(false)}
|
||||
>{t('appDebug.promptMode.advancedWarning.ok')}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
.promptTitle {
|
||||
width: 72px;
|
||||
height: 31px;
|
||||
background: url(./images/prompt.svg) no-repeat 0 0;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.advancedPromptMode {
|
||||
position: relative;
|
||||
}
|
||||
|
|
@ -18,4 +11,4 @@
|
|||
height: 3px;
|
||||
background-color: rgba(68, 76, 231, 0.18);
|
||||
transform: skewX(-30deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next'
|
|||
import FormGeneration from '../toolbox/moderation/form-generation'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import EmojiPicker from '@/app/components/base/emoji-picker'
|
||||
import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector'
|
||||
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
|
||||
|
|
@ -18,6 +17,7 @@ import type {
|
|||
ExternalDataTool,
|
||||
} from '@/models/common'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
|
||||
const systemTypes = ['api']
|
||||
type ExternalDataToolModalProps = {
|
||||
|
|
@ -184,7 +184,8 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
|
|||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={() => {}}
|
||||
onClose={() => { }}
|
||||
wrapperClassName='z-[101]'
|
||||
className='!p-8 !pb-6 !max-w-none !w-[640px]'
|
||||
>
|
||||
<div className='mb-2 text-xl font-semibold text-gray-900'>
|
||||
|
|
@ -285,6 +286,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
|
|||
{
|
||||
showEmojiPicker && (
|
||||
<EmojiPicker
|
||||
className='!z-[200]'
|
||||
onSelect={(icon, icon_background) => {
|
||||
handleValueChange({ icon, icon_background })
|
||||
setShowEmojiPicker(false)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// abandoned
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import copy from 'copy-to-clipboard'
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const Filter: FC<IFilterProps> = ({ appId, queryParams, setQueryParams }: IFilte
|
|||
type="text"
|
||||
name="query"
|
||||
className="block w-[240px] bg-gray-100 shadow-sm rounded-md border-0 py-1.5 pl-10 text-gray-900 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none sm:text-sm sm:leading-6"
|
||||
placeholder={t('common.operation.search')}
|
||||
placeholder={t('common.operation.search')!}
|
||||
value={queryParams.keyword}
|
||||
onChange={(e) => {
|
||||
setQueryParams({ ...queryParams, keyword: e.target.value })
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/comp
|
|||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import TextGeneration from '@/app/components/app/text-generate/item'
|
||||
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||
|
||||
type IConversationList = {
|
||||
logs?: ChatConversationsResponse | CompletionConversationsResponse
|
||||
|
|
@ -80,15 +81,17 @@ const getFormattedChatList = (messages: ChatMessage[]) => {
|
|||
content: item.inputs.query || item.inputs.default_input || item.query, // text generation: item.inputs.query; chat: item.query
|
||||
isAnswer: false,
|
||||
log: item.message as any,
|
||||
message_files: item.message_files,
|
||||
message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
|
||||
})
|
||||
newChatList.push({
|
||||
id: item.id,
|
||||
content: item.answer,
|
||||
agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
|
||||
feedback: item.feedbacks.find(item => item.from_source === 'user'), // user feedback
|
||||
adminFeedback: item.feedbacks.find(item => item.from_source === 'admin'), // admin feedback
|
||||
feedbackDisabled: false,
|
||||
isAnswer: true,
|
||||
message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
|
||||
more: {
|
||||
time: dayjs.unix(item.created_at).format('hh:mm A'),
|
||||
tokens: item.answer_tokens + item.message_tokens,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue