Commit 927fac87 authored by Joel's avatar Joel

feat: show thought

parent 22966a36
...@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' ...@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { UserCircleIcon } from '@heroicons/react/24/solid' import { UserCircleIcon } from '@heroicons/react/24/solid'
import cn from 'classnames' import cn from 'classnames'
import type { DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '../type' import type { DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc, ThoughtItem } from '../type'
import { randomString } from '../../../app-sidebar/basic' import { randomString } from '../../../app-sidebar/basic'
import OperationBtn from '../operation' import OperationBtn from '../operation'
import LoadingAnim from '../loading-anim' import LoadingAnim from '../loading-anim'
...@@ -13,13 +13,13 @@ import { EditIcon, EditIconSolid, OpeningStatementIcon, RatingIcon } from '../ic ...@@ -13,13 +13,13 @@ import { EditIcon, EditIconSolid, OpeningStatementIcon, RatingIcon } from '../ic
import s from '../style.module.css' import s from '../style.module.css'
import MoreInfo from '../more-info' import MoreInfo from '../more-info'
import CopyBtn from '../copy-btn' import CopyBtn from '../copy-btn'
import Thought from '../thought'
import type { Annotation, MessageRating } from '@/models/log' import type { Annotation, MessageRating } from '@/models/log'
import AppContext from '@/context/app-context' import AppContext from '@/context/app-context'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import { Markdown } from '@/app/components/base/markdown' import { Markdown } from '@/app/components/base/markdown'
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea' import AutoHeightTextarea from '@/app/components/base/auto-height-textarea'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
const Divider: FC<{ name: string }> = ({ name }) => { const Divider: FC<{ name: string }> = ({ name }) => {
const { t } = useTranslation() const { t } = useTranslation()
return <div className='flex items-center my-2'> return <div className='flex items-center my-2'>
...@@ -43,9 +43,10 @@ export type IAnswerProps = { ...@@ -43,9 +43,10 @@ export type IAnswerProps = {
displayScene: DisplayScene displayScene: DisplayScene
isResponsing?: boolean isResponsing?: boolean
answerIconClassName?: string answerIconClassName?: string
thoughts?: ThoughtItem[]
} }
// The component needs to maintain its own state to control whether to display input component // The component needs to maintain its own state to control whether to display input component
const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName }) => { const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName, thoughts }) => {
const { id, content, more, feedback, adminFeedback, annotation: initAnnotation } = item const { id, content, more, feedback, adminFeedback, annotation: initAnnotation } = item
const [showEdit, setShowEdit] = useState(false) const [showEdit, setShowEdit] = useState(false)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
...@@ -54,6 +55,23 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba ...@@ -54,6 +55,23 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
const [localAdminFeedback, setLocalAdminFeedback] = useState<Feedbacktype | undefined | null>(adminFeedback) const [localAdminFeedback, setLocalAdminFeedback] = useState<Feedbacktype | undefined | null>(adminFeedback)
const { userProfile } = useContext(AppContext) const { userProfile } = useContext(AppContext)
const { t } = useTranslation() const { t } = useTranslation()
// const thoughtList = [
// {
// id: '1',
// tool: 'google_search',
// thought: 'Searching Wikipedia with Donald Trump..',
// },
// {
// id: '2',
// tool: 'google_search',
// thought: 'Searching Wikipedia with Donald Trump..',
// },
// {
// id: '3',
// tool: 'google_search',
// thought: 'Searching Wikipedia with Donald Trump..',
// },
// ]
/** /**
* Render feedback results (distinguish between users and administrators) * Render feedback results (distinguish between users and administrators)
...@@ -184,7 +202,12 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba ...@@ -184,7 +202,12 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
</div> </div>
) )
: ( : (
<div>
{(thoughts && thoughts.length > 0) && (
<Thought list={thoughts || []}/>
)}
<Markdown content={content} /> <Markdown content={content} />
</div>
)} )}
{!showEdit {!showEdit
? (annotation?.content ? (annotation?.content
......
...@@ -155,6 +155,7 @@ const Chat: FC<IChatProps> = ({ ...@@ -155,6 +155,7 @@ const Chat: FC<IChatProps> = ({
{chatList.map((item) => { {chatList.map((item) => {
if (item.isAnswer) { if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1].id const isLast = item.id === chatList[chatList.length - 1].id
const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]')
return <Answer return <Answer
key={item.id} key={item.id}
item={item} item={item}
...@@ -165,6 +166,7 @@ const Chat: FC<IChatProps> = ({ ...@@ -165,6 +166,7 @@ const Chat: FC<IChatProps> = ({
displayScene={displayScene ?? 'web'} displayScene={displayScene ?? 'web'}
isResponsing={isResponsing && isLast} isResponsing={isResponsing && isLast}
answerIconClassName={answerIconClassName} answerIconClassName={answerIconClassName}
thoughts={thoughts}
/> />
} }
return <Question key={item.id} id={item.id} content={item.content} more={item.more} useCurrentUserAvatar={useCurrentUserAvatar} /> return <Question key={item.id} id={item.id} content={item.content} more={item.more} useCurrentUserAvatar={useCurrentUserAvatar} />
...@@ -201,7 +203,7 @@ const Chat: FC<IChatProps> = ({ ...@@ -201,7 +203,7 @@ const Chat: FC<IChatProps> = ({
{/* has scrollbar would hide part of first item */} {/* has scrollbar would hide part of first item */}
<div ref={suggestionListRef} className={cn(!hasScrollbar && 'justify-center', 'flex overflow-x-auto pb-2')}> <div ref={suggestionListRef} className={cn(!hasScrollbar && 'justify-center', 'flex overflow-x-auto pb-2')}>
{suggestionList?.map((item, index) => ( {suggestionList?.map((item, index) => (
<div className='shrink-0 flex justify-center mr-2'> <div key={item} className='shrink-0 flex justify-center mr-2'>
<Button <Button
key={index} key={index}
onClick={() => setQuery(item)} onClick={() => setQuery(item)}
......
'use client'
import type { FC } from 'react'
import React from 'react'
import cn from 'classnames'
import type { ThoughtItem } from '../type'
import s from './style.module.css'
export type IThoughtProps = {
list: ThoughtItem[]
}
const Thought: FC<IThoughtProps> = ({
list,
}) => {
const renderItem = (item: ThoughtItem) => (
<div className='flex space-x-1' key={item.id}>
<div className='shrink-0'>{item.tool}</div>
<div>{item.thought}</div>
</div>
)
// const [showMOre]
return (
<div className={cn(s.wrap, 'mb-2 px-2 py-0.5 rounded-md')} >
<div className='flex items-center h-8 space-x-1'>
<div>Show the process of thinking</div>
</div>
<div>
{list.map(item => renderItem(item))}
</div>
</div>
)
}
export default React.memo(Thought)
.wrap {
background-color: rgba(255, 255, 255, 0.92);
}
\ No newline at end of file
...@@ -16,9 +16,15 @@ export type SubmitAnnotationFunc = (messageId: string, content: string) => Promi ...@@ -16,9 +16,15 @@ export type SubmitAnnotationFunc = (messageId: string, content: string) => Promi
export type DisplayScene = 'web' | 'console' export type DisplayScene = 'web' | 'console'
export type ThoughtItem = {
id: string
tool: string // plugin or dataset
thought: string
}
export type IChatItem = { export type IChatItem = {
id: string id: string
content: string content: string
agent_thoughts?: ThoughtItem[]
/** /**
* Specific message type * Specific message type
*/ */
......
...@@ -27,7 +27,7 @@ import { ...@@ -27,7 +27,7 @@ import {
} from '@/service/universal-chat' } from '@/service/universal-chat'
import type { ConversationItem, SiteInfo } from '@/models/share' import type { ConversationItem, SiteInfo } from '@/models/share'
import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import type { Feedbacktype, IChatItem } from '@/app/components/app/chat' import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type'
import Chat from '@/app/components/app/chat' import Chat from '@/app/components/app/chat'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
...@@ -38,7 +38,6 @@ import type { DataSet } from '@/models/datasets' ...@@ -38,7 +38,6 @@ import type { DataSet } from '@/models/datasets'
import ConfigSummary from '@/app/components/explore/universal-chat/config-view/summary' import ConfigSummary from '@/app/components/explore/universal-chat/config-view/summary'
import ConfigDetail from '@/app/components/explore/universal-chat/config-view/detail' import ConfigDetail from '@/app/components/explore/universal-chat/config-view/detail'
import { fetchDatasets } from '@/service/datasets' import { fetchDatasets } from '@/service/datasets'
const APP_ID = 'universal-chat' const APP_ID = 'universal-chat'
const DEFAULT_MODEL_ID = 'claude-2' // gpt-4, claude-2 const DEFAULT_MODEL_ID = 'claude-2' // gpt-4, claude-2
const DEFAULT_PLUGIN = { const DEFAULT_PLUGIN = {
...@@ -177,7 +176,7 @@ const Main: FC<IMainProps> = () => { ...@@ -177,7 +176,7 @@ const Main: FC<IMainProps> = () => {
if (!isNewConversation) { if (!isNewConversation) {
const item = allConversationList.find(item => item.id === currConversationId) as any const item = allConversationList.find(item => item.id === currConversationId) as any
notSyncToStateInputs = item?.inputs || {} notSyncToStateInputs = item?.inputs || {}
setCurrInputs(notSyncToStateInputs) // setCurrInputs(notSyncToStateInputs)
notSyncToStateIntroduction = item?.introduction || '' notSyncToStateIntroduction = item?.introduction || ''
setExistConversationInfo({ setExistConversationInfo({
name: item?.name || '', name: item?.name || '',
...@@ -188,7 +187,7 @@ const Main: FC<IMainProps> = () => { ...@@ -188,7 +187,7 @@ const Main: FC<IMainProps> = () => {
setModeId(modelConfig.model_id) setModeId(modelConfig.model_id)
const pluginConfig: Record<string, boolean> = {} const pluginConfig: Record<string, boolean> = {}
const datasetIds: string[] = [] const datasetIds: string[] = []
modelConfig.agent_mode.tools.forEach((item) => { modelConfig.agent_mode.tools.forEach((item: any) => {
const pluginName = Object.keys(item)[0] const pluginName = Object.keys(item)[0]
if (pluginName === 'dataset') if (pluginName === 'dataset')
datasetIds.push(item.dataset.id) datasetIds.push(item.dataset.id)
...@@ -228,6 +227,7 @@ const Main: FC<IMainProps> = () => { ...@@ -228,6 +227,7 @@ const Main: FC<IMainProps> = () => {
isAnswer: false, isAnswer: false,
}) })
newChatList.push({ newChatList.push({
...item,
id: item.id, id: item.id,
content: item.answer, content: item.answer,
feedback: item.feedback, feedback: item.feedback,
...@@ -272,6 +272,7 @@ const Main: FC<IMainProps> = () => { ...@@ -272,6 +272,7 @@ const Main: FC<IMainProps> = () => {
if (chatListDomRef.current) if (chatListDomRef.current)
chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
}, [chatList, currConversationId]) }, [chatList, currConversationId])
// user can not edit inputs if user had send message // user can not edit inputs if user had send message
const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation
const createNewChat = async () => { const createNewChat = async () => {
...@@ -289,6 +290,9 @@ const Main: FC<IMainProps> = () => { ...@@ -289,6 +290,9 @@ const Main: FC<IMainProps> = () => {
introduction: conversationIntroduction, introduction: conversationIntroduction,
}) })
})) }))
setModeId(DEFAULT_MODEL_ID)
setPlugins(DEFAULT_PLUGIN)
setDateSets([])
} }
// sometime introduction is not applied to state // sometime introduction is not applied to state
...@@ -435,6 +439,7 @@ const Main: FC<IMainProps> = () => { ...@@ -435,6 +439,7 @@ const Main: FC<IMainProps> = () => {
const questionItem = { const questionItem = {
id: questionId, id: questionId,
content: message, content: message,
agent_thoughts: [],
isAnswer: false, isAnswer: false,
} }
...@@ -449,9 +454,10 @@ const Main: FC<IMainProps> = () => { ...@@ -449,9 +454,10 @@ const Main: FC<IMainProps> = () => {
setChatList(newList) setChatList(newList)
// answer // answer
const responseItem = { const responseItem: IChatItem = {
id: `${Date.now()}`, id: `${Date.now()}`,
content: '', content: '',
agent_thoughts: [],
isAnswer: true, isAnswer: true,
} }
...@@ -466,6 +472,7 @@ const Main: FC<IMainProps> = () => { ...@@ -466,6 +472,7 @@ const Main: FC<IMainProps> = () => {
setAbortController(abortController) setAbortController(abortController)
}, },
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
// console.log('get message...')
responseItem.content = responseItem.content + message responseItem.content = responseItem.content + message
responseItem.id = messageId responseItem.id = messageId
if (isFirstMessage && newConversationId) if (isFirstMessage && newConversationId)
...@@ -502,6 +509,11 @@ const Main: FC<IMainProps> = () => { ...@@ -502,6 +509,11 @@ const Main: FC<IMainProps> = () => {
setIsShowSuggestion(true) setIsShowSuggestion(true)
} }
}, },
onThought(thought) {
// thought then start to return message
// console.log('thought...');
(responseItem as any).agent_thoughts.push(thought)
},
onError() { onError() {
setResponsingFalse() setResponsingFalse()
// role back placeholder answer // role back placeholder answer
......
...@@ -30,6 +30,7 @@ export type IOnDataMoreInfo = { ...@@ -30,6 +30,7 @@ export type IOnDataMoreInfo = {
} }
export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void
export type IOnThought = (though: { id: string; tool: string; thought: string }) => void
export type IOnCompleted = (hasError?: boolean) => void export type IOnCompleted = (hasError?: boolean) => void
export type IOnError = (msg: string) => void export type IOnError = (msg: string) => void
...@@ -39,6 +40,7 @@ type IOtherOptions = { ...@@ -39,6 +40,7 @@ type IOtherOptions = {
needAllResponseContent?: boolean needAllResponseContent?: boolean
deleteContentType?: boolean deleteContentType?: boolean
onData?: IOnData // for stream onData?: IOnData // for stream
onThought?: IOnThought
onError?: IOnError onError?: IOnError
onCompleted?: IOnCompleted // for stream onCompleted?: IOnCompleted // for stream
getAbortController?: (abortController: AbortController) => void getAbortController?: (abortController: AbortController) => void
...@@ -61,7 +63,7 @@ export function format(text: string) { ...@@ -61,7 +63,7 @@ export function format(text: string) {
return res.replaceAll('\n', '<br/>').replaceAll('```', '') return res.replaceAll('\n', '<br/>').replaceAll('```', '')
} }
const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted) => { const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted, onThought?: IOnThought) => {
if (!response.ok) if (!response.ok)
throw new Error('Network response was not ok') throw new Error('Network response was not ok')
...@@ -114,7 +116,7 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted ...@@ -114,7 +116,7 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
isFirstMessage = false isFirstMessage = false
} }
else if (bufferObj.event === 'agent_thought') { else if (bufferObj.event === 'agent_thought') {
console.log(bufferObj) onThought?.(bufferObj as any)
} }
} }
}) })
...@@ -306,7 +308,7 @@ export const upload = (options: any): Promise<any> => { ...@@ -306,7 +308,7 @@ export const upload = (options: any): Promise<any> => {
}) })
} }
export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, onData, onCompleted, onError, getAbortController }: IOtherOptions) => { export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, onData, onCompleted, onThought, onError, getAbortController }: IOtherOptions) => {
const abortController = new AbortController() const abortController = new AbortController()
const options = Object.assign({}, baseOptions, { const options = Object.assign({}, baseOptions, {
...@@ -348,7 +350,7 @@ export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, o ...@@ -348,7 +350,7 @@ export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, o
return return
} }
onData?.(str, isFirstMessage, moreInfo) onData?.(str, isFirstMessage, moreInfo)
}, onCompleted) }, onCompleted, onThought)
}).catch((e) => { }).catch((e) => {
if (e.toString() !== 'AbortError: The user aborted a request.') if (e.toString() !== 'AbortError: The user aborted a request.')
Toast.notify({ type: 'error', message: e }) Toast.notify({ type: 'error', message: e })
......
...@@ -3,7 +3,7 @@ import { ...@@ -3,7 +3,7 @@ import {
del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost, del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost,
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost, delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
} from './base' } from './base'
import type { Feedbacktype } from '@/app/components/app/chat' import type { Feedbacktype } from '@/app/components/app/chat/type'
function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) { function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) {
switch (action) { switch (action) {
......
import type { IOnCompleted, IOnData, IOnError } from './base' import type { IOnCompleted, IOnData, IOnError, IOnThought } from './base'
import { import {
del, get, patch, post, ssePost, del, get, patch, post, ssePost,
} from './base' } from './base'
import type { Feedbacktype } from '@/app/components/app/chat' import type { Feedbacktype } from '@/app/components/app/chat/type'
const baseUrl = 'universal-chat' const baseUrl = 'universal-chat'
...@@ -10,10 +10,11 @@ function getUrl(url: string) { ...@@ -10,10 +10,11 @@ function getUrl(url: string) {
return `${baseUrl}/${url.startsWith('/') ? url.slice(1) : url}` return `${baseUrl}/${url.startsWith('/') ? url.slice(1) : url}`
} }
export const sendChatMessage = async (body: Record<string, any>, { onData, onCompleted, onError, getAbortController }: { export const sendChatMessage = async (body: Record<string, any>, { onData, onCompleted, onError, onThought, getAbortController }: {
onData: IOnData onData: IOnData
onCompleted: IOnCompleted onCompleted: IOnCompleted
onError: IOnError onError: IOnError
onThought: IOnThought
getAbortController?: (abortController: AbortController) => void getAbortController?: (abortController: AbortController) => void
}) => { }) => {
return ssePost(getUrl('messages'), { return ssePost(getUrl('messages'), {
...@@ -21,7 +22,7 @@ export const sendChatMessage = async (body: Record<string, any>, { onData, onCom ...@@ -21,7 +22,7 @@ export const sendChatMessage = async (body: Record<string, any>, { onData, onCom
...body, ...body,
response_mode: 'streaming', response_mode: 'streaming',
}, },
}, { onData, onCompleted, onError, getAbortController }) }, { onData, onCompleted, onThought, onError, getAbortController })
} }
export const stopChatMessageResponding = async (appId: string, taskId: string) => { export const stopChatMessageResponding = async (appId: string, taskId: string) => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment