Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: regenerate history switch navigation #8749

Open
wants to merge 53 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
f458bae
feat(wip): regenerate icon
xuzuodong Aug 22, 2024
3fb46a4
feat: regenerate chat message for and app
xuzuodong Aug 26, 2024
7719abc
chore: lint python code
xuzuodong Aug 26, 2024
2d4e9c9
Merge branch 'main' into feat/chat-regenerate
xuzuodong Aug 26, 2024
f15004c
chore: adapt new Tooltip component
xuzuodong Aug 26, 2024
9c394b2
chore: finish basic logic
xuzuodong Aug 27, 2024
45d540c
lint python code
xuzuodong Aug 27, 2024
b03403c
chore: remove debug code
xuzuodong Aug 27, 2024
80fd810
chore: add line break
xuzuodong Aug 27, 2024
19e01d3
chore: allow getting message list by desc to avoid breaking change to…
xuzuodong Aug 27, 2024
37ea507
chore: add missing field to related endpoint
xuzuodong Aug 27, 2024
8b59da0
chore: hide regenerate button in log view
xuzuodong Aug 29, 2024
a304d89
Merge branch 'main' into feat/chat-regenerate
xuzuodong Aug 29, 2024
62f5236
chore: update db migrate script
xuzuodong Aug 29, 2024
6ec8755
lint python code and remove unnecessary code
xuzuodong Aug 29, 2024
fcd178e
fix: generation api error
xuzuodong Aug 29, 2024
3475486
fix: regenerate not working in chatflow and embed app
xuzuodong Aug 30, 2024
e7c58c1
chore: minor change
xuzuodong Aug 30, 2024
53e0865
chore: add field to all non-first messages
xuzuodong Sep 1, 2024
863e595
chor: update comments
xuzuodong Sep 2, 2024
193b5d9
Merge remote-tracking branch 'origin/main' into feat/chat-regenerate
xuzuodong Sep 4, 2024
3a5c0b3
chore: update db mirgration code
xuzuodong Sep 4, 2024
823c592
chore: lint python code
xuzuodong Sep 4, 2024
62caaf9
Merge branch 'feat/chat-regenerate' into feat/message-tree
xuzuodong Sep 4, 2024
f89ca21
refactor: use only
xuzuodong Sep 7, 2024
c32f273
style: regenerate icon
xuzuodong Sep 7, 2024
5b6a03c
chore: lint python code
xuzuodong Sep 7, 2024
9b45334
Merge branch 'feat/chat-regenerate' into feat/message-tree
xuzuodong Sep 7, 2024
ab6ad76
staging
xuzuodong Sep 10, 2024
8cb70e7
staging
xuzuodong Sep 10, 2024
8a28713
finish
xuzuodong Sep 10, 2024
dc54187
fix test and use snapshot
xuzuodong Sep 10, 2024
9aed525
getThreadMessages
xuzuodong Sep 11, 2024
a7b5742
sibling switching works
xuzuodong Sep 11, 2024
849685b
chore: update test snapshot
xuzuodong Sep 11, 2024
1a407f1
Merge remote-tracking branch 'origin/main' into feat/chat-regenerate
xuzuodong Sep 11, 2024
c585fae
chore: lint python code
xuzuodong Sep 11, 2024
c7aa2b3
chore: db migrate version
xuzuodong Sep 11, 2024
1d68b58
chore: lint python code
xuzuodong Sep 11, 2024
0514597
Merge branch 'feat/chat-regenerate' into feat/message-tree
xuzuodong Sep 12, 2024
31276c5
fix: algorithm
xuzuodong Sep 13, 2024
fc5aff3
feat: chatflow log supports viewing thread messages
xuzuodong Sep 13, 2024
d05c370
fix: minor issue
xuzuodong Sep 13, 2024
5606c17
chore: change icon of regenerate button
xuzuodong Sep 14, 2024
c4a13c7
chore: only show latest thread messages in log list and chatflow run …
xuzuodong Sep 14, 2024
421569a
Merge remote-tracking branch 'origin/main' into feat/chat-regenerate
xuzuodong Sep 14, 2024
e07b550
chore: lint python code
xuzuodong Sep 14, 2024
2dfda58
Revert "chore: only show latest thread messages in log list and chatf…
xuzuodong Sep 22, 2024
7438f32
Merge branch 'feat/chat-regenerate' into feat/message-tree
xuzuodong Sep 22, 2024
b68c176
Revert "chore: only show latest thread messages in log list and chatf…
xuzuodong Sep 22, 2024
94c7b10
fix: api not returning parent_message_id
xuzuodong Sep 22, 2024
fecd227
Merge branch 'main' into feat/message-tree
xuzuodong Sep 25, 2024
4a8ce56
fix: message list order not correct
xuzuodong Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/controllers/console/app/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def get(self, app_model):
if rest_count > 0:
has_more = True

history_messages = list(reversed(history_messages))

return InfiniteScrollPagination(data=history_messages, limit=args["limit"], has_more=has_more)


Expand Down
214 changes: 107 additions & 107 deletions web/app/components/app/log/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import timezone from 'dayjs/plugin/timezone'
import { createContext, useContext } from 'use-context-selector'
import { useShallow } from 'zustand/react/shallow'
import { useTranslation } from 'react-i18next'
import { UUID_NIL } from '../../base/chat/constants'
import type { ChatItemInTree } from '../../base/chat/types'
import s from './style.module.css'
import VarPanel from './var-panel'
import cn from '@/utils/classnames'
Expand All @@ -42,6 +42,7 @@ import { useAppContext } from '@/context/app-context'
import useTimestamp from '@/hooks/use-timestamp'
import Tooltip from '@/app/components/base/tooltip'
import { CopyIcon } from '@/app/components/base/copy-icon'
import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils'

dayjs.extend(utc)
dayjs.extend(timezone)
Expand Down Expand Up @@ -82,92 +83,73 @@ const PARAM_MAP = {
frequency_penalty: 'Frequency Penalty',
}

function appendQAToChatList(newChatList: IChatItem[], item: any, conversationId: string, timezone: string, format: string) {
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: any) => item.from_source === 'user'), // user feedback
adminFeedback: item.feedbacks.find((item: any) => item.from_source === 'admin'), // admin feedback
feedbackDisabled: false,
isAnswer: true,
message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
log: [
...item.message,
...(item.message[item.message.length - 1]?.role !== 'assistant'
? [
{
role: 'assistant',
text: item.answer,
files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
},
]
: []),
],
workflow_run_id: item.workflow_run_id,
conversationId,
input: {
inputs: item.inputs,
query: item.query,
},
more: {
time: dayjs.unix(item.created_at).tz(timezone).format(format),
tokens: item.answer_tokens + item.message_tokens,
latency: item.provider_response_latency.toFixed(2),
},
citation: item.metadata?.retriever_resources,
annotation: (() => {
if (item.annotation_hit_history) {
return {
id: item.annotation_hit_history.annotation_id,
authorName: item.annotation_hit_history.annotation_create_account?.name || 'N/A',
created_at: item.annotation_hit_history.created_at,
const getFormattedChatList = (messages: ChatMessage[], conversationId: string, timezone: string, format: string) => {
const newChatList: IChatItem[] = []
messages.forEach((item: ChatMessage) => {
newChatList.push({
id: `question-${item.id}`,
content: item.inputs.query || item.inputs.default_input || item.query, // text generation: item.inputs.query; chat: item.query
isAnswer: false,
message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
parentMessageId: item.parent_message_id || undefined,
})
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') || [],
log: [
...item.message,
...(item.message[item.message.length - 1]?.role !== 'assistant'
? [
{
role: 'assistant',
text: item.answer,
files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
},
]
: []),
],
workflow_run_id: item.workflow_run_id,
conversationId,
input: {
inputs: item.inputs,
query: item.query,
},
more: {
time: dayjs.unix(item.created_at).tz(timezone).format(format),
tokens: item.answer_tokens + item.message_tokens,
latency: item.provider_response_latency.toFixed(2),
},
citation: item.metadata?.retriever_resources,
annotation: (() => {
if (item.annotation_hit_history) {
return {
id: item.annotation_hit_history.annotation_id,
authorName: item.annotation_hit_history.annotation_create_account?.name || 'N/A',
created_at: item.annotation_hit_history.created_at,
}
}
}

if (item.annotation) {
return {
id: item.annotation.id,
authorName: item.annotation.account.name,
logAnnotation: item.annotation,
created_at: 0,
if (item.annotation) {
return {
id: item.annotation.id,
authorName: item.annotation.account.name,
logAnnotation: item.annotation,
created_at: 0,
}
}
}

return undefined
})(),
parentMessageId: `question-${item.id}`,
})
newChatList.push({
id: `question-${item.id}`,
content: item.inputs.query || item.inputs.default_input || item.query, // text generation: item.inputs.query; chat: item.query
isAnswer: false,
message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
parentMessageId: item.parent_message_id || undefined,
return undefined
})(),
parentMessageId: `question-${item.id}`,
})
})
}

const getFormattedChatList = (messages: ChatMessage[], conversationId: string, timezone: string, format: string) => {
const newChatList: IChatItem[] = []
let nextMessageId = null
for (const item of messages) {
if (!item.parent_message_id) {
appendQAToChatList(newChatList, item, conversationId, timezone, format)
break
}

if (!nextMessageId) {
appendQAToChatList(newChatList, item, conversationId, timezone, format)
nextMessageId = item.parent_message_id
}
else {
if (item.id === nextMessageId || nextMessageId === UUID_NIL) {
appendQAToChatList(newChatList, item, conversationId, timezone, format)
nextMessageId = item.parent_message_id
}
}
}
return newChatList.reverse()
return newChatList
}

// const displayedParams = CompletionParams.slice(0, -2)
Expand All @@ -191,50 +173,66 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
currentLogModalActiveTab: state.currentLogModalActiveTab,
})))
const { t } = useTranslation()
const [items, setItems] = React.useState<IChatItem[]>([])
const fetchedMessages = useRef<ChatMessage[]>([])
const [hasMore, setHasMore] = useState(true)
const [varValues, setVarValues] = useState<Record<string, string>>({})
const fetchData = async () => {

const [allChatItems, setAllChatItems] = useState<IChatItem[]>([])
const [chatItemTree, setChatItemTree] = useState<ChatItemInTree[]>([])
const [threadChatItems, setThreadChatItems] = useState<IChatItem[]>([])

const fetchData = useCallback(async () => {
try {
if (!hasMore)
return

const params: ChatMessagesRequest = {
conversation_id: detail.id,
limit: 10,
}
if (items?.[0]?.id)
params.first_id = items?.[0]?.id.replace('question-', '')

if (allChatItems.at(-1)?.id)
params.first_id = allChatItems.at(-1)?.id.replace('question-', '')
const messageRes = await fetchChatMessages({
url: `/apps/${appDetail?.id}/chat-messages`,
params,
})
if (messageRes.data.length > 0) {
const varValues = messageRes.data[0].inputs
const varValues = messageRes.data.at(-1)!.inputs
setVarValues(varValues)
}
fetchedMessages.current = [...fetchedMessages.current, ...messageRes.data]
const newItems = getFormattedChatList(fetchedMessages.current, detail.id, timezone!, t('appLog.dateTimeFormat') as string)
setHasMore(messageRes.has_more)

const newAllChatItems = [
...getFormattedChatList(messageRes.data, detail.id, timezone!, t('appLog.dateTimeFormat') as string),
...allChatItems,
]
setAllChatItems(newAllChatItems)

let tree = buildChatItemTree(newAllChatItems)
if (messageRes.has_more === false && detail?.model_config?.configs?.introduction) {
newItems.unshift({
tree = [{
id: 'introduction',
isAnswer: true,
isOpeningStatement: true,
content: detail?.model_config?.configs?.introduction ?? 'hello',
feedbackDisabled: true,
})
children: tree,
}]
}
setItems(newItems)
setHasMore(messageRes.has_more)
setChatItemTree(tree)

setThreadChatItems(getThreadMessages(tree, newAllChatItems.at(-1)?.id))
}
catch (err) {
console.error(err)
}
}
}, [allChatItems, detail.id, hasMore, timezone, t, appDetail, detail?.model_config?.configs?.introduction])

const switchSibling = useCallback((siblingMessageId: string) => {
setThreadChatItems(getThreadMessages(chatItemTree, siblingMessageId))
}, [chatItemTree])

const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
setItems(items.map((item, i) => {
setAllChatItems(allChatItems.map((item, i) => {
if (i === index - 1) {
return {
...item,
Expand All @@ -255,9 +253,9 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
}
return item
}))
}, [items])
}, [allChatItems])
const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => {
setItems(items.map((item, i) => {
setAllChatItems(allChatItems.map((item, i) => {
if (i === index - 1) {
return {
...item,
Expand Down Expand Up @@ -285,9 +283,9 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
}
return item
}))
}, [items])
}, [allChatItems])
const handleAnnotationRemoved = useCallback((index: number) => {
setItems(items.map((item, i) => {
setAllChatItems(allChatItems.map((item, i) => {
if (i === index) {
return {
...item,
Expand All @@ -297,12 +295,12 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
}
return item
}))
}, [items])
}, [allChatItems])

useEffect(() => {
if (appDetail?.id && detail.id && appDetail?.mode !== 'completion')
fetchData()
}, [appDetail?.id, detail.id, appDetail?.mode])
}, [appDetail?.id, detail.id, appDetail?.mode, fetchData])

const isChatMode = appDetail?.mode !== 'completion'
const isAdvanced = appDetail?.mode === 'advanced-chat'
Expand Down Expand Up @@ -458,7 +456,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
siteInfo={null}
/>
</div>
: (items.length < 8 && !hasMore)
: threadChatItems.length < 8
? <div className="pt-4 mb-4">
<Chat
config={{
Expand All @@ -472,7 +470,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
},
supportFeedback: true,
} as any}
chatList={items}
chatList={threadChatItems}
onAnnotationAdded={handleAnnotationAdded}
onAnnotationEdited={handleAnnotationEdited}
onAnnotationRemoved={handleAnnotationRemoved}
Expand All @@ -481,6 +479,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
showPromptLog
hideProcessDetail
chatContainerInnerClassName='px-6'
switchSibling={switchSibling}
/>
</div>
: <div
Expand All @@ -495,7 +494,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
{/* Put the scroll bar always on the bottom */}
<InfiniteScroll
scrollableTarget="scrollableDiv"
dataLength={items.length}
dataLength={threadChatItems.length}
next={fetchData}
hasMore={hasMore}
loader={<div className='text-center text-gray-400 text-xs'>{t('appLog.detail.loading')}...</div>}
Expand Down Expand Up @@ -526,7 +525,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
},
supportFeedback: true,
} as any}
chatList={items}
chatList={threadChatItems}
onAnnotationAdded={handleAnnotationAdded}
onAnnotationEdited={handleAnnotationEdited}
onAnnotationRemoved={handleAnnotationRemoved}
Expand All @@ -535,6 +534,7 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
showPromptLog
hideProcessDetail
chatContainerInnerClassName='px-6'
switchSibling={switchSibling}
/>
</InfiniteScroll>
</div>
Expand Down
Loading