Commit 927fac87 authored by Joel's avatar Joel

feat: show thought

parent 22966a36
......@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { UserCircleIcon } from '@heroicons/react/24/solid'
import cn from 'classnames'
import type { DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '../type'
import type { DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc, ThoughtItem } from '../type'
import { randomString } from '../../../app-sidebar/basic'
import OperationBtn from '../operation'
import LoadingAnim from '../loading-anim'
......@@ -13,13 +13,13 @@ import { EditIcon, EditIconSolid, OpeningStatementIcon, RatingIcon } from '../ic
import s from '../style.module.css'
import MoreInfo from '../more-info'
import CopyBtn from '../copy-btn'
import Thought from '../thought'
import type { Annotation, MessageRating } from '@/models/log'
import AppContext from '@/context/app-context'
import Tooltip from '@/app/components/base/tooltip'
import { Markdown } from '@/app/components/base/markdown'
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea'
import Button from '@/app/components/base/button'
const Divider: FC<{ name: string }> = ({ name }) => {
const { t } = useTranslation()
return <div className='flex items-center my-2'>
......@@ -43,9 +43,10 @@ export type IAnswerProps = {
displayScene: DisplayScene
isResponsing?: boolean
answerIconClassName?: string
thoughts?: ThoughtItem[]
}
// 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 [showEdit, setShowEdit] = useState(false)
const [loading, setLoading] = useState(false)
......@@ -54,6 +55,23 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
const [localAdminFeedback, setLocalAdminFeedback] = useState<Feedbacktype | undefined | null>(adminFeedback)
const { userProfile } = useContext(AppContext)
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)
......@@ -184,7 +202,12 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
</div>
)
: (
<Markdown content={content} />
<div>
{(thoughts && thoughts.length > 0) && (
<Thought list={thoughts || []}/>
)}
<Markdown content={content} />
</div>
)}
{!showEdit
? (annotation?.content
......
......@@ -155,6 +155,7 @@ const Chat: FC<IChatProps> = ({
{chatList.map((item) => {
if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1].id
const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]')
return <Answer
key={item.id}
item={item}
......@@ -165,6 +166,7 @@ const Chat: FC<IChatProps> = ({
displayScene={displayScene ?? 'web'}
isResponsing={isResponsing && isLast}
answerIconClassName={answerIconClassName}
thoughts={thoughts}
/>
}
return <Question key={item.id} id={item.id} content={item.content} more={item.more} useCurrentUserAvatar={useCurrentUserAvatar} />
......@@ -201,7 +203,7 @@ const Chat: FC<IChatProps> = ({
{/* has scrollbar would hide part of first item */}
<div ref={suggestionListRef} className={cn(!hasScrollbar && 'justify-center', 'flex overflow-x-auto pb-2')}>
{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
key={index}
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
export type DisplayScene = 'web' | 'console'
export type ThoughtItem = {
id: string
tool: string // plugin or dataset
thought: string
}
export type IChatItem = {
id: string
content: string
agent_thoughts?: ThoughtItem[]
/**
* Specific message type
*/
......
......@@ -27,7 +27,7 @@ import {
} from '@/service/universal-chat'
import type { ConversationItem, SiteInfo } from '@/models/share'
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 useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Loading from '@/app/components/base/loading'
......@@ -38,7 +38,6 @@ import type { DataSet } from '@/models/datasets'
import ConfigSummary from '@/app/components/explore/universal-chat/config-view/summary'
import ConfigDetail from '@/app/components/explore/universal-chat/config-view/detail'
import { fetchDatasets } from '@/service/datasets'
const APP_ID = 'universal-chat'
const DEFAULT_MODEL_ID = 'claude-2' // gpt-4, claude-2
const DEFAULT_PLUGIN = {
......@@ -177,7 +176,7 @@ const Main: FC<IMainProps> = () => {
if (!isNewConversation) {
const item = allConversationList.find(item => item.id === currConversationId) as any
notSyncToStateInputs = item?.inputs || {}
setCurrInputs(notSyncToStateInputs)
// setCurrInputs(notSyncToStateInputs)
notSyncToStateIntroduction = item?.introduction || ''
setExistConversationInfo({
name: item?.name || '',
......@@ -188,7 +187,7 @@ const Main: FC<IMainProps> = () => {
setModeId(modelConfig.model_id)
const pluginConfig: Record<string, boolean> = {}
const datasetIds: string[] = []
modelConfig.agent_mode.tools.forEach((item) => {
modelConfig.agent_mode.tools.forEach((item: any) => {
const pluginName = Object.keys(item)[0]
if (pluginName === 'dataset')
datasetIds.push(item.dataset.id)
......@@ -228,6 +227,7 @@ const Main: FC<IMainProps> = () => {
isAnswer: false,
})
newChatList.push({
...item,
id: item.id,
content: item.answer,
feedback: item.feedback,
......@@ -272,6 +272,7 @@ const Main: FC<IMainProps> = () => {
if (chatListDomRef.current)
chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
}, [chatList, currConversationId])
// user can not edit inputs if user had send message
const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation
const createNewChat = async () => {
......@@ -289,6 +290,9 @@ const Main: FC<IMainProps> = () => {
introduction: conversationIntroduction,
})
}))
setModeId(DEFAULT_MODEL_ID)
setPlugins(DEFAULT_PLUGIN)
setDateSets([])
}
// sometime introduction is not applied to state
......@@ -435,6 +439,7 @@ const Main: FC<IMainProps> = () => {
const questionItem = {
id: questionId,
content: message,
agent_thoughts: [],
isAnswer: false,
}
......@@ -449,9 +454,10 @@ const Main: FC<IMainProps> = () => {
setChatList(newList)
// answer
const responseItem = {
const responseItem: IChatItem = {
id: `${Date.now()}`,
content: '',
agent_thoughts: [],
isAnswer: true,
}
......@@ -466,6 +472,7 @@ const Main: FC<IMainProps> = () => {
setAbortController(abortController)
},
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
// console.log('get message...')
responseItem.content = responseItem.content + message
responseItem.id = messageId
if (isFirstMessage && newConversationId)
......@@ -502,6 +509,11 @@ const Main: FC<IMainProps> = () => {
setIsShowSuggestion(true)
}
},
onThought(thought) {
// thought then start to return message
// console.log('thought...');
(responseItem as any).agent_thoughts.push(thought)
},
onError() {
setResponsingFalse()
// role back placeholder answer
......
......@@ -30,6 +30,7 @@ export type IOnDataMoreInfo = {
}
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 IOnError = (msg: string) => void
......@@ -39,6 +40,7 @@ type IOtherOptions = {
needAllResponseContent?: boolean
deleteContentType?: boolean
onData?: IOnData // for stream
onThought?: IOnThought
onError?: IOnError
onCompleted?: IOnCompleted // for stream
getAbortController?: (abortController: AbortController) => void
......@@ -61,7 +63,7 @@ export function format(text: string) {
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)
throw new Error('Network response was not ok')
......@@ -114,7 +116,7 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
isFirstMessage = false
}
else if (bufferObj.event === 'agent_thought') {
console.log(bufferObj)
onThought?.(bufferObj as 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 options = Object.assign({}, baseOptions, {
......@@ -348,7 +350,7 @@ export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, o
return
}
onData?.(str, isFirstMessage, moreInfo)
}, onCompleted)
}, onCompleted, onThought)
}).catch((e) => {
if (e.toString() !== 'AbortError: The user aborted a request.')
Toast.notify({ type: 'error', message: e })
......
......@@ -3,7 +3,7 @@ import {
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,
} 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) {
switch (action) {
......
import type { IOnCompleted, IOnData, IOnError } from './base'
import type { IOnCompleted, IOnData, IOnError, IOnThought } from './base'
import {
del, get, patch, post, ssePost,
} from './base'
import type { Feedbacktype } from '@/app/components/app/chat'
import type { Feedbacktype } from '@/app/components/app/chat/type'
const baseUrl = 'universal-chat'
......@@ -10,10 +10,11 @@ function getUrl(url: string) {
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
onCompleted: IOnCompleted
onError: IOnError
onThought: IOnThought
getAbortController?: (abortController: AbortController) => void
}) => {
return ssePost(getUrl('messages'), {
......@@ -21,7 +22,7 @@ export const sendChatMessage = async (body: Record<string, any>, { onData, onCom
...body,
response_mode: 'streaming',
},
}, { onData, onCompleted, onError, getAbortController })
}, { onData, onCompleted, onThought, onError, getAbortController })
}
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