From f458bae14623d709f8442e308f5645f12b251f80 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Thu, 22 Aug 2024 16:19:06 +0800 Subject: [PATCH 01/44] feat(wip): regenerate icon --- api/services/message_service.py | 3 +- .../chat/chat-with-history/chat-wrapper.tsx | 19 +++++++++++ .../base/chat/chat/answer/operation.tsx | 7 +++- web/app/components/base/chat/chat/context.tsx | 3 ++ web/app/components/base/chat/chat/hooks.ts | 2 +- web/app/components/base/chat/chat/index.tsx | 4 +++ web/app/components/base/chat/types.ts | 2 ++ .../line/general/MaterialSymbolsRefresh.svg | 1 + .../line/general/MaterialSymbolsRefresh.json | 25 ++++++++++++++ .../line/general/MaterialSymbolsRefresh.tsx | 16 +++++++++ .../icons/src/vender/line/general/index.ts | 1 + .../components/base/regenerate-btn/index.tsx | 34 +++++++++++++++++++ web/i18n/en-US/app-api.ts | 1 + web/i18n/zh-Hans/app-api.ts | 1 + 14 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg create mode 100644 web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json create mode 100644 web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.tsx create mode 100644 web/app/components/base/regenerate-btn/index.tsx diff --git a/api/services/message_service.py b/api/services/message_service.py index 491a914c77638..b4cd506ce979d 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -70,7 +70,8 @@ def pagination_by_first_id(cls, app_model: App, user: Optional[Union[Account, En if rest_count > 0: has_more = True - history_messages = list(reversed(history_messages)) + # history_messages = list(reversed(history_messages)) + history_messages = list(history_messages) return InfiniteScrollPagination( data=history_messages, diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index 8eda66c52a9dd..533bf13518bc7 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo } from 'react' import Chat from '../chat' import type { ChatConfig, + ChatItem, OnSend, } from '../types' import { useChat } from '../chat/hooks' @@ -43,6 +44,7 @@ const ChatWrapper = () => { }, [appParams, currentConversationItem?.introduction, currentConversationId]) const { chatList, + handleUpdateChatList, handleSend, handleStop, isResponding, @@ -91,6 +93,22 @@ const ChatWrapper = () => { isInstalledApp, appId, ]) + + const doRegenerate = useCallback((chatItem: ChatItem) => { + const index = chatList.findIndex(item => item.id === chatItem.id) + if (index === -1) + return + + const prevMessages = chatList.slice(0, index) + const prevMessage = prevMessages.pop() + + if (!prevMessage) + return + + handleUpdateChatList(prevMessages) + doSend(prevMessage.content, prevMessage.message_files) + }, [chatList, handleUpdateChatList, doSend]) + const chatNode = useMemo(() => { if (inputsForms.length) { return ( @@ -138,6 +156,7 @@ const ChatWrapper = () => { chatFooterClassName='pb-4' chatFooterInnerClassName={`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`} onSend={doSend} + onRegenerate={doRegenerate} onStopResponding={handleStop} chatNode={chatNode} allToolIcons={appMeta?.tool_icons || {}} diff --git a/web/app/components/base/chat/chat/answer/operation.tsx b/web/app/components/base/chat/chat/answer/operation.tsx index 8ec5c0f3b2389..473a2d35a419a 100644 --- a/web/app/components/base/chat/chat/answer/operation.tsx +++ b/web/app/components/base/chat/chat/answer/operation.tsx @@ -7,6 +7,7 @@ import { import { useTranslation } from 'react-i18next' import type { ChatItem } from '../../types' import { useChatContext } from '../context' +import RegenerateBtn from '@/app/components/base/regenerate-btn' import cn from '@/utils/classnames' import CopyBtn from '@/app/components/base/copy-btn' import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' @@ -45,6 +46,7 @@ const Operation: FC = ({ onAnnotationEdited, onAnnotationRemoved, onFeedback, + onRegenerate, } = useChatContext() const [isShowReplyModal, setIsShowReplyModal] = useState(false) const { @@ -159,9 +161,12 @@ const Operation: FC = ({ ) } + { + !isOpeningStatement && onRegenerate?.(item)} /> + } { config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement && ( -
+
void noChatInput?: boolean onSend?: OnSend + onRegenerate?: OnRegenerate chatContainerClassName?: string chatContainerInnerClassName?: string chatFooterClassName?: string @@ -66,6 +68,7 @@ const Chat: FC = ({ appData, config, onSend, + onRegenerate, chatList, isResponding, noStopResponding, @@ -184,6 +187,7 @@ const Chat: FC = ({ answerIcon={answerIcon} allToolIcons={allToolIcons} onSend={onSend} + onRegenerate={onRegenerate} onAnnotationAdded={onAnnotationAdded} onAnnotationEdited={onAnnotationEdited} onAnnotationRemoved={onAnnotationRemoved} diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts index baffe42843ffc..216e122838f71 100644 --- a/web/app/components/base/chat/types.ts +++ b/web/app/components/base/chat/types.ts @@ -65,6 +65,8 @@ export type ChatItem = IChatItem & { export type OnSend = (message: string, files?: VisionFile[]) => void +export type OnRegenerate = (chatItem: ChatItem) => void + export type Callback = { onSuccess: () => void } diff --git a/web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg b/web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg new file mode 100644 index 0000000000000..ebfcb925db9e2 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json b/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json new file mode 100644 index 0000000000000..cf6668ebd89b9 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json @@ -0,0 +1,25 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "width": "24", + "height": "24", + "viewBox": "0 0 24 24" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "fill": "currentColor", + "d": "M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z" + }, + "children": [] + } + ] + }, + "name": "MaterialSymbolsRefresh" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.tsx b/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.tsx new file mode 100644 index 0000000000000..d9b57eae62c1f --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './MaterialSymbolsRefresh.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'MaterialSymbolsRefresh' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/general/index.ts b/web/app/components/base/icons/src/vender/line/general/index.ts index c1af2e49948a4..4833eb685f7fd 100644 --- a/web/app/components/base/icons/src/vender/line/general/index.ts +++ b/web/app/components/base/icons/src/vender/line/general/index.ts @@ -1,3 +1,4 @@ +export { default as MaterialSymbolsRefresh } from './MaterialSymbolsRefresh' export { default as AtSign } from './AtSign' export { default as Bookmark } from './Bookmark' export { default as CheckDone01 } from './CheckDone01' diff --git a/web/app/components/base/regenerate-btn/index.tsx b/web/app/components/base/regenerate-btn/index.tsx new file mode 100644 index 0000000000000..eb46931760aa3 --- /dev/null +++ b/web/app/components/base/regenerate-btn/index.tsx @@ -0,0 +1,34 @@ +'use client' +import { useRef } from 'react' +import { t } from 'i18next' +import { MaterialSymbolsRefresh } from '../icons/src/vender/line/general' +import Tooltip from '@/app/components/base/tooltip' +import { randomString } from '@/utils' + +type Props = { + className?: string + onClick?: () => void +} + +const RegenerateBtn = ({ className, onClick }: Props) => { + const selector = useRef(`copy-tooltip-${randomString(4)}`) + + return ( +
+ +
onClick?.()} + > + +
+
+
+ ) +} + +export default RegenerateBtn diff --git a/web/i18n/en-US/app-api.ts b/web/i18n/en-US/app-api.ts index f36708c1d0898..f7ac01e8673c8 100644 --- a/web/i18n/en-US/app-api.ts +++ b/web/i18n/en-US/app-api.ts @@ -6,6 +6,7 @@ const translation = { ok: 'In Service', copy: 'Copy', copied: 'Copied', + regenerate: 'Regenerate', play: 'Play', pause: 'Pause', playing: 'Playing', diff --git a/web/i18n/zh-Hans/app-api.ts b/web/i18n/zh-Hans/app-api.ts index f8f6ab70833f5..a50c0764eb66f 100644 --- a/web/i18n/zh-Hans/app-api.ts +++ b/web/i18n/zh-Hans/app-api.ts @@ -6,6 +6,7 @@ const translation = { ok: '运行中', copy: '复制', copied: '已复制', + regenerate: '重新生成', play: '播放', pause: '暂停', playing: '播放中', From 3fb46a42d57c1890d95618aa152f925cab72280c Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Mon, 26 Aug 2024 17:55:09 +0800 Subject: [PATCH 02/44] feat: regenerate chat message for and app --- api/controllers/console/app/completion.py | 1 + api/controllers/console/explore/completion.py | 1 + api/controllers/web/completion.py | 1 + api/core/agent/base_agent_runner.py | 6 +++- api/core/app/apps/agent_chat/app_generator.py | 1 + api/core/app/apps/chat/app_generator.py | 1 + .../app/apps/message_based_app_generator.py | 1 + api/core/app/entities/app_invoke_entities.py | 2 ++ api/core/memory/token_buffer_memory.py | 11 ++++++-- api/core/prompt/utils/prompt_message_util.py | 17 +++++++++++ ..._23_0304-028767889ee3_message_parent_id.py | 28 +++++++++++++++++++ api/models/model.py | 1 + api/services/message_service.py | 5 ++-- .../debug/debug-with-single-model/index.tsx | 25 +++++++++++++++-- .../chat/chat-with-history/chat-wrapper.tsx | 10 ++++--- web/app/components/base/chat/types.ts | 2 +- 16 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index 61582536fdbe1..9beb32f2eb459 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -112,6 +112,7 @@ def post(self, app_model): parser.add_argument('files', type=list, required=False, location='json') parser.add_argument('model_config', type=dict, required=True, location='json') parser.add_argument('conversation_id', type=uuid_value, location='json') + parser.add_argument('parent_message_id', type=uuid_value, required=False, location='json') parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json') parser.add_argument('retriever_from', type=str, required=False, default='dev', location='json') args = parser.parse_args() diff --git a/api/controllers/console/explore/completion.py b/api/controllers/console/explore/completion.py index 869b56e13bf93..37f350d6da5f8 100644 --- a/api/controllers/console/explore/completion.py +++ b/api/controllers/console/explore/completion.py @@ -105,6 +105,7 @@ def post(self, installed_app): parser.add_argument('query', type=str, required=True, location='json') parser.add_argument('files', type=list, required=False, location='json') parser.add_argument('conversation_id', type=uuid_value, location='json') + parser.add_argument('parent_message_id', type=uuid_value, required=False, location='json') parser.add_argument('retriever_from', type=str, required=False, default='explore_app', location='json') args = parser.parse_args() diff --git a/api/controllers/web/completion.py b/api/controllers/web/completion.py index 948d5fabb5328..0d84b919cc82b 100644 --- a/api/controllers/web/completion.py +++ b/api/controllers/web/completion.py @@ -99,6 +99,7 @@ def post(self, app_model, end_user): parser.add_argument('files', type=list, required=False, location='json') parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json') parser.add_argument('conversation_id', type=uuid_value, location='json') + parser.add_argument('parent_message_id', type=uuid_value, required=False, location='json') parser.add_argument('retriever_from', type=str, required=False, default='web_app', location='json') args = parser.parse_args() diff --git a/api/core/agent/base_agent_runner.py b/api/core/agent/base_agent_runner.py index d8290ca608b0c..bbb3a7a55a57b 100644 --- a/api/core/agent/base_agent_runner.py +++ b/api/core/agent/base_agent_runner.py @@ -427,7 +427,11 @@ def organize_agent_history(self, prompt_messages: list[PromptMessage]) -> list[P messages: list[Message] = db.session.query(Message).filter( Message.conversation_id == self.message.conversation_id, - ).order_by(Message.created_at.asc()).all() + ).order_by(Message.created_at.desc()).all() + + from core.prompt.utils.prompt_message_util import PromptMessageUtil + thread_messages = PromptMessageUtil.extract_thread_messages(messages) + messages = list(reversed(thread_messages)) for message in messages: if message.id == self.message.id: diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index 53780bdfb003b..4d16ab1a97f70 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -122,6 +122,7 @@ def generate(self, app_model: App, inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, + parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, invoke_from=invoke_from, diff --git a/api/core/app/apps/chat/app_generator.py b/api/core/app/apps/chat/app_generator.py index 5b896e2845534..c867f48634c02 100644 --- a/api/core/app/apps/chat/app_generator.py +++ b/api/core/app/apps/chat/app_generator.py @@ -119,6 +119,7 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, + parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, invoke_from=invoke_from, diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index fceed95b91e63..7d0e5fa48718f 100644 --- a/api/core/app/apps/message_based_app_generator.py +++ b/api/core/app/apps/message_based_app_generator.py @@ -214,6 +214,7 @@ def _init_generate_records(self, answer_tokens=0, answer_unit_price=0, answer_price_unit=0, + parent_message_id=application_generate_entity.parent_message_id or None, provider_response_latency=0, total_price=0, currency='USD', diff --git a/api/core/app/entities/app_invoke_entities.py b/api/core/app/entities/app_invoke_entities.py index 6a1ab230416d0..21e2ad84f0f4c 100644 --- a/api/core/app/entities/app_invoke_entities.py +++ b/api/core/app/entities/app_invoke_entities.py @@ -117,6 +117,7 @@ class ChatAppGenerateEntity(EasyUIBasedAppGenerateEntity): Chat Application Generate Entity. """ conversation_id: Optional[str] = None + parent_message_id: Optional[str] = None class CompletionAppGenerateEntity(EasyUIBasedAppGenerateEntity): @@ -131,6 +132,7 @@ class AgentChatAppGenerateEntity(EasyUIBasedAppGenerateEntity): Agent Chat Application Generate Entity. """ conversation_id: Optional[str] = None + parent_message_id: Optional[str] = None class AdvancedChatAppGenerateEntity(AppGenerateEntity): diff --git a/api/core/memory/token_buffer_memory.py b/api/core/memory/token_buffer_memory.py index b33d4dd7cb342..ef975381926c8 100644 --- a/api/core/memory/token_buffer_memory.py +++ b/api/core/memory/token_buffer_memory.py @@ -36,10 +36,10 @@ def get_history_prompt_messages(self, max_token_limit: int = 2000, Message.query, Message.answer, Message.created_at, - Message.workflow_run_id + Message.workflow_run_id, + Message.parent_message_id, ).filter( Message.conversation_id == self.conversation.id, - Message.answer != '' ).order_by(Message.created_at.desc()) if message_limit and message_limit > 0: @@ -49,7 +49,12 @@ def get_history_prompt_messages(self, max_token_limit: int = 2000, messages = query.limit(message_limit).all() - messages = list(reversed(messages)) + # instead of all messages from the conversation, we only need to extract messages that belong to the thread of last message + from core.prompt.utils.prompt_message_util import PromptMessageUtil + thread_messages = PromptMessageUtil.extract_thread_messages(messages) + thread_messages.pop(0) + messages = list(reversed(thread_messages)) + message_file_parser = MessageFileParser( tenant_id=app_record.tenant_id, app_id=app_record.id diff --git a/api/core/prompt/utils/prompt_message_util.py b/api/core/prompt/utils/prompt_message_util.py index befdceeda505f..ffd48ebe5c7b0 100644 --- a/api/core/prompt/utils/prompt_message_util.py +++ b/api/core/prompt/utils/prompt_message_util.py @@ -101,3 +101,20 @@ def prompt_messages_to_prompt_for_saving(model_mode: str, prompt_messages: list[ prompts.append(params) return prompts + + @staticmethod + def extract_thread_messages(messages: list[dict]) -> list[dict]: + thread_messages = [] + next_message = None + + for message in messages: + if not next_message: + thread_messages.append(message) + if message.parent_message_id: + next_message = message.parent_message_id + else: + if message.id == next_message: + thread_messages.append(message) + next_message = message.parent_message_id + + return thread_messages \ No newline at end of file diff --git a/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py b/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py new file mode 100644 index 0000000000000..f3c6bf30daf80 --- /dev/null +++ b/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py @@ -0,0 +1,28 @@ +"""message parent id + +Revision ID: 028767889ee3 +Revises: 2dbe42621d96 +Create Date: 2024-08-23 03:04:36.148579 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '028767889ee3' +down_revision = '2dbe42621d96' +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table('messages', schema=None) as batch_op: + batch_op.add_column(sa.Column('parent_message_id', models.types.StringUUID(), nullable=True)) + + +def downgrade(): + with op.batch_alter_table('messages', schema=None) as batch_op: + batch_op.drop_column('parent_message_id') + diff --git a/api/models/model.py b/api/models/model.py index 73016297713ca..8faa2678a7665 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -655,6 +655,7 @@ class Message(db.Model): answer_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0')) answer_unit_price = db.Column(db.Numeric(10, 4), nullable=False) answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001')) + parent_message_id = db.Column(StringUUID, nullable=True) provider_response_latency = db.Column(db.Float, nullable=False, server_default=db.text('0')) total_price = db.Column(db.Numeric(10, 7)) currency = db.Column(db.String(255), nullable=False) diff --git a/api/services/message_service.py b/api/services/message_service.py index b4cd506ce979d..0392199f1443a 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -70,8 +70,9 @@ def pagination_by_first_id(cls, app_model: App, user: Optional[Union[Account, En if rest_count > 0: has_more = True - # history_messages = list(reversed(history_messages)) - history_messages = list(history_messages) + from core.prompt.utils.prompt_message_util import PromptMessageUtil + thread_messages = PromptMessageUtil.extract_thread_messages(history_messages) + history_messages = list(reversed(thread_messages)) return InfiniteScrollPagination( data=history_messages, diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx index ea15c1a4ce695..cf93efad2c4c9 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx @@ -12,7 +12,7 @@ import { import Chat from '@/app/components/base/chat/chat' import { useChat } from '@/app/components/base/chat/chat/hooks' import { useDebugConfigurationContext } from '@/context/debug-configuration' -import type { OnSend } from '@/app/components/base/chat/types' +import type { ChatItem, OnSend } from '@/app/components/base/chat/types' import { useProviderContext } from '@/context/provider-context' import { fetchConversationMessages, @@ -49,6 +49,7 @@ const DebugWithSingleModel = forwardRef { + const doSend: OnSend = useCallback((message, files, last_answer) => { if (checkCanSend && !checkCanSend()) return const currentProvider = textGenerationModelList.find(item => item.provider === modelConfig.provider) @@ -85,6 +86,7 @@ const DebugWithSingleModel = forwardRef fetchSuggestedQuestions(appId, responseItemId, getAbortController), }, ) - }, [appId, checkCanSend, completionParams, config, handleSend, inputs, modelConfig, textGenerationModelList, visionConfig.enabled]) + }, [appId, checkCanSend, completionParams, chatList, config, handleSend, inputs, modelConfig, textGenerationModelList, visionConfig.enabled]) + + const doRegenerate = useCallback((chatItem: ChatItem) => { + const index = chatList.findIndex(item => item.id === chatItem.id) + if (index === -1) + return + + const prevMessages = chatList.slice(0, index) + const question = prevMessages.pop() + const lastAnswer = prevMessages.at(-1) + + if (!question || !lastAnswer) + return + + handleUpdateChatList(prevMessages) + doSend(question.content, question.message_files, lastAnswer.isOpeningStatement ? undefined : lastAnswer) + }, [chatList, handleUpdateChatList, doSend]) const allToolIcons = useMemo(() => { const icons: Record = {} @@ -123,6 +141,7 @@ const DebugWithSingleModel = forwardRef} diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index 533bf13518bc7..6caf2771f7d2f 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -64,11 +64,12 @@ const ChatWrapper = () => { currentChatInstanceRef.current.handleStop = handleStop }, []) - const doSend: OnSend = useCallback((message, files) => { + const doSend: OnSend = useCallback((message, files, last_answer) => { const data: any = { query: message, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, + parent_message_id: last_answer?.id, } if (appConfig?.file_upload?.image.enabled && files?.length) @@ -100,13 +101,14 @@ const ChatWrapper = () => { return const prevMessages = chatList.slice(0, index) - const prevMessage = prevMessages.pop() + const question = prevMessages.pop() + const lastAnswer = prevMessages.at(-1) - if (!prevMessage) + if (!question || !lastAnswer) return handleUpdateChatList(prevMessages) - doSend(prevMessage.content, prevMessage.message_files) + doSend(question.content, question.message_files, lastAnswer.isOpeningStatement ? undefined : lastAnswer) }, [chatList, handleUpdateChatList, doSend]) const chatNode = useMemo(() => { diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts index 216e122838f71..ec4c39743be10 100644 --- a/web/app/components/base/chat/types.ts +++ b/web/app/components/base/chat/types.ts @@ -63,7 +63,7 @@ export type ChatItem = IChatItem & { conversationId?: string } -export type OnSend = (message: string, files?: VisionFile[]) => void +export type OnSend = (message: string, files?: VisionFile[], last_answer?: ChatItem) => void export type OnRegenerate = (chatItem: ChatItem) => void From 7719abcb5e86f3f9b3875ff9105313c43c523df1 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Mon, 26 Aug 2024 17:56:16 +0800 Subject: [PATCH 03/44] chore: lint python code --- .../2024_08_23_0304-028767889ee3_message_parent_id.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py b/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py index f3c6bf30daf80..4f4a77f39ba3c 100644 --- a/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py +++ b/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py @@ -5,10 +5,10 @@ Create Date: 2024-08-23 03:04:36.148579 """ -from alembic import op -import models as models import sqlalchemy as sa +from alembic import op +import models as models # revision identifiers, used by Alembic. revision = '028767889ee3' From f15004c6c8d0dcc5d6f7dd06c7b8782a55f324e4 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Mon, 26 Aug 2024 18:10:24 +0800 Subject: [PATCH 04/44] chore: adapt new Tooltip component --- web/app/components/base/regenerate-btn/index.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/web/app/components/base/regenerate-btn/index.tsx b/web/app/components/base/regenerate-btn/index.tsx index eb46931760aa3..7428f8ccccad1 100644 --- a/web/app/components/base/regenerate-btn/index.tsx +++ b/web/app/components/base/regenerate-btn/index.tsx @@ -1,9 +1,7 @@ 'use client' -import { useRef } from 'react' import { t } from 'i18next' import { MaterialSymbolsRefresh } from '../icons/src/vender/line/general' import Tooltip from '@/app/components/base/tooltip' -import { randomString } from '@/utils' type Props = { className?: string @@ -11,14 +9,10 @@ type Props = { } const RegenerateBtn = ({ className, onClick }: Props) => { - const selector = useRef(`copy-tooltip-${randomString(4)}`) - return (
Date: Tue, 27 Aug 2024 14:54:51 +0800 Subject: [PATCH 05/44] chore: finish basic logic --- api/controllers/console/app/completion.py | 1 + api/controllers/console/explore/completion.py | 1 + api/controllers/web/completion.py | 1 + api/core/agent/base_agent_runner.py | 5 +- api/core/app/apps/agent_chat/app_generator.py | 3 +- .../app/apps/message_based_app_generator.py | 1 + api/core/app/entities/app_invoke_entities.py | 2 + api/core/memory/token_buffer_memory.py | 5 +- .../prompt/utils/extract_thread_messages.py | 20 +++++++ api/core/prompt/utils/prompt_message_util.py | 17 ------ api/fields/message_fields.py | 2 + ...7_0231-b194ab4e3c69_message_regenerate.py} | 19 ++++--- api/models/model.py | 1 + api/services/message_service.py | 4 -- .../debug/debug-with-single-model/index.tsx | 9 +-- .../chat/chat-with-history/chat-wrapper.tsx | 9 +-- .../base/chat/chat-with-history/hooks.tsx | 56 +++++++++++++------ web/app/components/base/chat/types.ts | 2 +- 18 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 api/core/prompt/utils/extract_thread_messages.py rename api/migrations/versions/{2024_08_23_0304-028767889ee3_message_parent_id.py => 2024_08_27_0231-b194ab4e3c69_message_regenerate.py} (52%) diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index a8ec2ff870e2a..ba265a36fb339 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -107,6 +107,7 @@ def post(self, app_model): parser.add_argument("files", type=list, required=False, location="json") parser.add_argument("model_config", type=dict, required=True, location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") + parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") parser.add_argument("response_mode", type=str, choices=["blocking", "streaming"], location="json") parser.add_argument("retriever_from", type=str, required=False, default="dev", location="json") diff --git a/api/controllers/console/explore/completion.py b/api/controllers/console/explore/completion.py index f865ff443159e..8c31ca20c4e3a 100644 --- a/api/controllers/console/explore/completion.py +++ b/api/controllers/console/explore/completion.py @@ -100,6 +100,7 @@ def post(self, installed_app): parser.add_argument("query", type=str, required=True, location="json") parser.add_argument("files", type=list, required=False, location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") + parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") parser.add_argument("retriever_from", type=str, required=False, default="explore_app", location="json") args = parser.parse_args() diff --git a/api/controllers/web/completion.py b/api/controllers/web/completion.py index f019879fae619..a9e8cf5f507dc 100644 --- a/api/controllers/web/completion.py +++ b/api/controllers/web/completion.py @@ -94,6 +94,7 @@ def post(self, app_model, end_user): parser.add_argument("files", type=list, required=False, location="json") parser.add_argument("response_mode", type=str, choices=["blocking", "streaming"], location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") + parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") parser.add_argument("retriever_from", type=str, required=False, default="web_app", location="json") diff --git a/api/core/agent/base_agent_runner.py b/api/core/agent/base_agent_runner.py index bbb3a7a55a57b..0ba6c2fa36fa4 100644 --- a/api/core/agent/base_agent_runner.py +++ b/api/core/agent/base_agent_runner.py @@ -31,6 +31,7 @@ from core.model_runtime.entities.model_entities import ModelFeature from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.model_runtime.utils.encoders import jsonable_encoder +from core.prompt.utils.extract_thread_messages import extract_thread_messages from core.tools.entities.tool_entities import ( ToolParameter, ToolRuntimeVariablePool, @@ -429,9 +430,7 @@ def organize_agent_history(self, prompt_messages: list[PromptMessage]) -> list[P Message.conversation_id == self.message.conversation_id, ).order_by(Message.created_at.desc()).all() - from core.prompt.utils.prompt_message_util import PromptMessageUtil - thread_messages = PromptMessageUtil.extract_thread_messages(messages) - messages = list(reversed(thread_messages)) + messages = list(reversed(extract_thread_messages(messages))) for message in messages: if message.id == self.message.id: diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index 4d16ab1a97f70..ee6f665e60ab9 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -112,7 +112,7 @@ def generate(self, app_model: App, # get tracing instance user_id = user.id if isinstance(user, Account) else user.session_id trace_manager = TraceQueueManager(app_model.id, user_id) - + print('----------', args.get('is_regenerate')) # init application generate entity application_generate_entity = AgentChatAppGenerateEntity( task_id=str(uuid.uuid4()), @@ -122,6 +122,7 @@ def generate(self, app_model: App, inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, + is_regenerate=args.get('is_regenerate', False), parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index 7d0e5fa48718f..7e2a224479b1a 100644 --- a/api/core/app/apps/message_based_app_generator.py +++ b/api/core/app/apps/message_based_app_generator.py @@ -214,6 +214,7 @@ def _init_generate_records(self, answer_tokens=0, answer_unit_price=0, answer_price_unit=0, + is_regenerated=application_generate_entity.is_regenerate, parent_message_id=application_generate_entity.parent_message_id or None, provider_response_latency=0, total_price=0, diff --git a/api/core/app/entities/app_invoke_entities.py b/api/core/app/entities/app_invoke_entities.py index 21e2ad84f0f4c..4fa7c6b923033 100644 --- a/api/core/app/entities/app_invoke_entities.py +++ b/api/core/app/entities/app_invoke_entities.py @@ -117,6 +117,7 @@ class ChatAppGenerateEntity(EasyUIBasedAppGenerateEntity): Chat Application Generate Entity. """ conversation_id: Optional[str] = None + is_regenerate: bool parent_message_id: Optional[str] = None @@ -132,6 +133,7 @@ class AgentChatAppGenerateEntity(EasyUIBasedAppGenerateEntity): Agent Chat Application Generate Entity. """ conversation_id: Optional[str] = None + is_regenerate: bool parent_message_id: Optional[str] = None diff --git a/api/core/memory/token_buffer_memory.py b/api/core/memory/token_buffer_memory.py index ef975381926c8..c5735eff63391 100644 --- a/api/core/memory/token_buffer_memory.py +++ b/api/core/memory/token_buffer_memory.py @@ -11,6 +11,7 @@ TextPromptMessageContent, UserPromptMessage, ) +from core.prompt.utils.extract_thread_messages import extract_thread_messages from extensions.ext_database import db from models.model import AppMode, Conversation, Message, MessageFile from models.workflow import WorkflowRun @@ -37,6 +38,7 @@ def get_history_prompt_messages(self, max_token_limit: int = 2000, Message.answer, Message.created_at, Message.workflow_run_id, + Message.is_regenerated, Message.parent_message_id, ).filter( Message.conversation_id == self.conversation.id, @@ -50,8 +52,7 @@ def get_history_prompt_messages(self, max_token_limit: int = 2000, messages = query.limit(message_limit).all() # instead of all messages from the conversation, we only need to extract messages that belong to the thread of last message - from core.prompt.utils.prompt_message_util import PromptMessageUtil - thread_messages = PromptMessageUtil.extract_thread_messages(messages) + thread_messages = extract_thread_messages(messages) thread_messages.pop(0) messages = list(reversed(thread_messages)) diff --git a/api/core/prompt/utils/extract_thread_messages.py b/api/core/prompt/utils/extract_thread_messages.py new file mode 100644 index 0000000000000..ed89b6034e9b3 --- /dev/null +++ b/api/core/prompt/utils/extract_thread_messages.py @@ -0,0 +1,20 @@ +def extract_thread_messages(messages: list[dict]) -> list[dict]: + thread_messages = [] + next_message = None + + for message in messages: + if message.is_regenerated and not message.parent_message_id: + # If the message is regenerated and does not have a parent message, it is the start of a new thread + thread_messages.append(message) + break + + if not next_message: + thread_messages.append(message) + if message.parent_message_id: + next_message = message.parent_message_id + else: + if message.id == next_message: + thread_messages.append(message) + next_message = message.parent_message_id + + return thread_messages \ No newline at end of file diff --git a/api/core/prompt/utils/prompt_message_util.py b/api/core/prompt/utils/prompt_message_util.py index ffd48ebe5c7b0..befdceeda505f 100644 --- a/api/core/prompt/utils/prompt_message_util.py +++ b/api/core/prompt/utils/prompt_message_util.py @@ -101,20 +101,3 @@ def prompt_messages_to_prompt_for_saving(model_mode: str, prompt_messages: list[ prompts.append(params) return prompts - - @staticmethod - def extract_thread_messages(messages: list[dict]) -> list[dict]: - thread_messages = [] - next_message = None - - for message in messages: - if not next_message: - thread_messages.append(message) - if message.parent_message_id: - next_message = message.parent_message_id - else: - if message.id == next_message: - thread_messages.append(message) - next_message = message.parent_message_id - - return thread_messages \ No newline at end of file diff --git a/api/fields/message_fields.py b/api/fields/message_fields.py index 3d2df87afb9b1..31392bd41b758 100644 --- a/api/fields/message_fields.py +++ b/api/fields/message_fields.py @@ -62,6 +62,8 @@ message_fields = { "id": fields.String, "conversation_id": fields.String, + "is_regenerated": fields.Boolean, + "parent_message_id": fields.String, "inputs": fields.Raw, "query": fields.String, "answer": fields.String(attribute="re_sign_file_url_answer"), diff --git a/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py b/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py similarity index 52% rename from api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py rename to api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py index 4f4a77f39ba3c..93c622203e935 100644 --- a/api/migrations/versions/2024_08_23_0304-028767889ee3_message_parent_id.py +++ b/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py @@ -1,28 +1,33 @@ -"""message parent id +"""message regenerate -Revision ID: 028767889ee3 +Revision ID: b194ab4e3c69 Revises: 2dbe42621d96 -Create Date: 2024-08-23 03:04:36.148579 +Create Date: 2024-08-27 02:31:24.186377 """ -import sqlalchemy as sa from alembic import op - import models as models +import sqlalchemy as sa + # revision identifiers, used by Alembic. -revision = '028767889ee3' +revision = 'b194ab4e3c69' down_revision = '2dbe42621d96' branch_labels = None depends_on = None def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('messages', schema=None) as batch_op: + batch_op.add_column(sa.Column('is_regenerated', sa.Boolean(), server_default=sa.text('false'), nullable=False)) batch_op.add_column(sa.Column('parent_message_id', models.types.StringUUID(), nullable=True)) + # ### end Alembic commands ### def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('messages', schema=None) as batch_op: batch_op.drop_column('parent_message_id') - + batch_op.drop_column('is_regenerated') + # ### end Alembic commands ### diff --git a/api/models/model.py b/api/models/model.py index 8faa2678a7665..ff89bab2bd81d 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -655,6 +655,7 @@ class Message(db.Model): answer_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0')) answer_unit_price = db.Column(db.Numeric(10, 4), nullable=False) answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001')) + is_regenerated = db.Column(db.Boolean, nullable=False, server_default=db.text('false')) parent_message_id = db.Column(StringUUID, nullable=True) provider_response_latency = db.Column(db.Float, nullable=False, server_default=db.text('0')) total_price = db.Column(db.Numeric(10, 7)) diff --git a/api/services/message_service.py b/api/services/message_service.py index bb5b4539af752..8adc13ed7c3bc 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -91,10 +91,6 @@ def pagination_by_first_id( if rest_count > 0: has_more = True - from core.prompt.utils.prompt_message_util import PromptMessageUtil - thread_messages = PromptMessageUtil.extract_thread_messages(history_messages) - history_messages = list(reversed(thread_messages)) - return InfiniteScrollPagination(data=history_messages, limit=limit, has_more=has_more) @classmethod diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx index cf93efad2c4c9..fa87b3e637592 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx @@ -65,7 +65,7 @@ const DebugWithSingleModel = forwardRef { + const doSend: OnSend = useCallback((message, files, is_regenerate, last_answer) => { if (checkCanSend && !checkCanSend()) return const currentProvider = textGenerationModelList.find(item => item.provider === modelConfig.provider) @@ -86,6 +86,7 @@ const DebugWithSingleModel = forwardRef fetchSuggestedQuestions(appId, responseItemId, getAbortController), }, ) - }, [appId, checkCanSend, completionParams, chatList, config, handleSend, inputs, modelConfig, textGenerationModelList, visionConfig.enabled]) + }, [appId, checkCanSend, completionParams, config, handleSend, inputs, modelConfig, textGenerationModelList, visionConfig.enabled]) const doRegenerate = useCallback((chatItem: ChatItem) => { const index = chatList.findIndex(item => item.id === chatItem.id) @@ -111,11 +112,11 @@ const DebugWithSingleModel = forwardRef { diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index 6caf2771f7d2f..babacc4de68df 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -64,12 +64,13 @@ const ChatWrapper = () => { currentChatInstanceRef.current.handleStop = handleStop }, []) - const doSend: OnSend = useCallback((message, files, last_answer) => { + const doSend: OnSend = useCallback((message, files, is_regenerate, last_answer) => { const data: any = { query: message, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, - parent_message_id: last_answer?.id, + is_regenerate: !!is_regenerate, + parent_message_id: last_answer?.id || null, } if (appConfig?.file_upload?.image.enabled && files?.length) @@ -104,11 +105,11 @@ const ChatWrapper = () => { const question = prevMessages.pop() const lastAnswer = prevMessages.at(-1) - if (!question || !lastAnswer) + if (!question) return handleUpdateChatList(prevMessages) - doSend(question.content, question.message_files, lastAnswer.isOpeningStatement ? undefined : lastAnswer) + doSend(question.content, question.message_files, true, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) }, [chatList, handleUpdateChatList, doSend]) const chatNode = useMemo(() => { diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index 624cc53a18360..3061bf41b3a7c 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -39,6 +39,25 @@ import { useToastContext } from '@/app/components/base/toast' import { changeLanguage } from '@/i18n/i18next-config' import { useAppFavicon } from '@/hooks/use-app-favicon' +function appendQAToChatList(chatList: ChatItem[], item: any) { + // we append answer first and then question since will reverse the whole chatList later + chatList.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.feedback, + isAnswer: true, + citation: item.retriever_resources, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], + }) + chatList.push({ + id: `question-${item.id}`, + content: item.query, + isAnswer: false, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], + }) +} + export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo) @@ -112,23 +131,26 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const chatList: ChatItem[] = [] if (currentConversationId && data.length) { - data.forEach((item: any) => { - chatList.push({ - id: `question-${item.id}`, - content: item.query, - isAnswer: false, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], - }) - chatList.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.feedback, - isAnswer: true, - citation: item.retriever_resources, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], - }) - }) + let nextMessageId = null + for (const item of data) { + if (item.is_regenerated && !item.parent_message_id) { + appendQAToChatList(chatList, item) + break + } + + if (!nextMessageId) { + appendQAToChatList(chatList, item) + if (item.parent_message_id) + nextMessageId = item.parent_message_id + } + else { + if (item.id === nextMessageId) { + appendQAToChatList(chatList, item) + nextMessageId = item.parent_message_id + } + } + } + chatList.reverse() } return chatList diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts index ec4c39743be10..bc097bc9dff89 100644 --- a/web/app/components/base/chat/types.ts +++ b/web/app/components/base/chat/types.ts @@ -63,7 +63,7 @@ export type ChatItem = IChatItem & { conversationId?: string } -export type OnSend = (message: string, files?: VisionFile[], last_answer?: ChatItem) => void +export type OnSend = (message: string, files?: VisionFile[], isRegenerate?: boolean, last_answer?: ChatItem) => void export type OnRegenerate = (chatItem: ChatItem) => void From 45d540c47a80c9f1110a803e621047dd1b6a8560 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 27 Aug 2024 14:55:35 +0800 Subject: [PATCH 06/44] lint python code --- .../2024_08_27_0231-b194ab4e3c69_message_regenerate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py b/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py index 93c622203e935..780a2f545e895 100644 --- a/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py +++ b/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py @@ -5,10 +5,10 @@ Create Date: 2024-08-27 02:31:24.186377 """ -from alembic import op -import models as models import sqlalchemy as sa +from alembic import op +import models as models # revision identifiers, used by Alembic. revision = 'b194ab4e3c69' From b03403c7160ff288682e2f3c1a66e993aa30fd6b Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 27 Aug 2024 14:57:16 +0800 Subject: [PATCH 07/44] chore: remove debug code --- api/core/app/apps/agent_chat/app_generator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index ee6f665e60ab9..6a7e59a1c8070 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -112,7 +112,6 @@ def generate(self, app_model: App, # get tracing instance user_id = user.id if isinstance(user, Account) else user.session_id trace_manager = TraceQueueManager(app_model.id, user_id) - print('----------', args.get('is_regenerate')) # init application generate entity application_generate_entity = AgentChatAppGenerateEntity( task_id=str(uuid.uuid4()), From 80fd81010ec068be3d64345793cac65dc2936b91 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 27 Aug 2024 14:57:57 +0800 Subject: [PATCH 08/44] chore: add line break --- api/core/app/apps/agent_chat/app_generator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index 6a7e59a1c8070..23dd12135c558 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -112,6 +112,7 @@ def generate(self, app_model: App, # get tracing instance user_id = user.id if isinstance(user, Account) else user.session_id trace_manager = TraceQueueManager(app_model.id, user_id) + # init application generate entity application_generate_entity = AgentChatAppGenerateEntity( task_id=str(uuid.uuid4()), From 19e01d30eaa50b5d7209f43d807c49135629c35c Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 27 Aug 2024 15:15:27 +0800 Subject: [PATCH 09/44] chore: allow getting message list by desc to avoid breaking change to API --- api/controllers/console/explore/message.py | 2 +- api/controllers/web/message.py | 2 +- api/services/message_service.py | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/controllers/console/explore/message.py b/api/controllers/console/explore/message.py index f5eb18517258d..e839d64548060 100644 --- a/api/controllers/console/explore/message.py +++ b/api/controllers/console/explore/message.py @@ -51,7 +51,7 @@ def get(self, installed_app): try: return MessageService.pagination_by_first_id( - app_model, current_user, args["conversation_id"], args["first_id"], args["limit"] + app_model, current_user, args["conversation_id"], args["first_id"], args["limit"], "desc" ) except services.errors.conversation.ConversationNotExistsError: raise NotFound("Conversation Not Exists.") diff --git a/api/controllers/web/message.py b/api/controllers/web/message.py index 56aaaa930a4a8..a2cc4ed11c64a 100644 --- a/api/controllers/web/message.py +++ b/api/controllers/web/message.py @@ -89,7 +89,7 @@ def get(self, app_model, end_user): try: return MessageService.pagination_by_first_id( - app_model, end_user, args["conversation_id"], args["first_id"], args["limit"] + app_model, end_user, args["conversation_id"], args["first_id"], args["limit"], "desc" ) except services.errors.conversation.ConversationNotExistsError: raise NotFound("Conversation Not Exists.") diff --git a/api/services/message_service.py b/api/services/message_service.py index 8adc13ed7c3bc..f432a77c80e51 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -34,6 +34,7 @@ def pagination_by_first_id( conversation_id: str, first_id: Optional[str], limit: int, + order: str = "asc", ) -> InfiniteScrollPagination: if not user: return InfiniteScrollPagination(data=[], limit=limit, has_more=False) @@ -91,6 +92,9 @@ def pagination_by_first_id( if rest_count > 0: has_more = True + if order == "asc": + history_messages = list(reversed(history_messages)) + return InfiniteScrollPagination(data=history_messages, limit=limit, has_more=has_more) @classmethod From 37ea507a2c929884bedbfd3cea0548fdae21d04c Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 27 Aug 2024 16:10:40 +0800 Subject: [PATCH 10/44] chore: add missing field to related endpoint --- api/controllers/service_api/app/message.py | 2 ++ api/controllers/web/message.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/api/controllers/service_api/app/message.py b/api/controllers/service_api/app/message.py index b39aaf7dd804d..97ce8cabf2f32 100644 --- a/api/controllers/service_api/app/message.py +++ b/api/controllers/service_api/app/message.py @@ -54,6 +54,8 @@ class MessageListApi(Resource): message_fields = { "id": fields.String, "conversation_id": fields.String, + "is_regenerated": fields.Boolean, + "parent_message_id": fields.String, "inputs": fields.Raw, "query": fields.String, "answer": fields.String(attribute="re_sign_file_url_answer"), diff --git a/api/controllers/web/message.py b/api/controllers/web/message.py index a2cc4ed11c64a..7736489e01f22 100644 --- a/api/controllers/web/message.py +++ b/api/controllers/web/message.py @@ -57,6 +57,8 @@ class MessageListApi(WebApiResource): message_fields = { "id": fields.String, "conversation_id": fields.String, + "is_regenerated": fields.Boolean, + "parent_message_id": fields.String, "inputs": fields.Raw, "query": fields.String, "answer": fields.String(attribute="re_sign_file_url_answer"), From 8b59da094b9d8627170a9a3b05edb77b2282f48a Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Thu, 29 Aug 2024 14:10:45 +0800 Subject: [PATCH 11/44] chore: hide regenerate button in log view --- web/app/components/base/chat/chat/answer/index.tsx | 3 +++ web/app/components/base/chat/chat/answer/operation.tsx | 4 +++- web/app/components/base/chat/chat/index.tsx | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 78a084259569f..d5afb5fa3dd59 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -35,6 +35,7 @@ type AnswerProps = { chatAnswerContainerInner?: string hideProcessDetail?: boolean appData?: AppData + noChatInput?: boolean } const Answer: FC = ({ item, @@ -48,6 +49,7 @@ const Answer: FC = ({ chatAnswerContainerInner, hideProcessDetail, appData, + noChatInput, }) => { const { t } = useTranslation() const { @@ -132,6 +134,7 @@ const Answer: FC = ({ question={question} index={index} showPromptLog={showPromptLog} + noChatInput={noChatInput} /> ) } diff --git a/web/app/components/base/chat/chat/answer/operation.tsx b/web/app/components/base/chat/chat/answer/operation.tsx index d8eca8155de98..b838a3e0b7210 100644 --- a/web/app/components/base/chat/chat/answer/operation.tsx +++ b/web/app/components/base/chat/chat/answer/operation.tsx @@ -29,6 +29,7 @@ type OperationProps = { maxSize: number contentWidth: number hasWorkflowProcess: boolean + noChatInput?: boolean } const Operation: FC = ({ item, @@ -38,6 +39,7 @@ const Operation: FC = ({ maxSize, contentWidth, hasWorkflowProcess, + noChatInput, }) => { const { t } = useTranslation() const { @@ -162,7 +164,7 @@ const Operation: FC = ({ ) } { - !isOpeningStatement && onRegenerate?.(item)} /> + !isOpeningStatement && !noChatInput && onRegenerate?.(item)} /> } { config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement && ( diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index 02fef333b7d37..e591731a97363 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -221,6 +221,7 @@ const Chat: FC = ({ showPromptLog={showPromptLog} chatAnswerContainerInner={chatAnswerContainerInner} hideProcessDetail={hideProcessDetail} + noChatInput={noChatInput} /> ) } From 62f52367a4dd1a6353d1729aab58638159dd0f56 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Thu, 29 Aug 2024 17:47:38 +0800 Subject: [PATCH 12/44] chore: update db migrate script --- ...27_0231-b194ab4e3c69_message_regenerate.py | 33 ------------- ...29_0945-8c563fdfc838_message_regenerate.py | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+), 33 deletions(-) delete mode 100644 api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py create mode 100644 api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py diff --git a/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py b/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py deleted file mode 100644 index 780a2f545e895..0000000000000 --- a/api/migrations/versions/2024_08_27_0231-b194ab4e3c69_message_regenerate.py +++ /dev/null @@ -1,33 +0,0 @@ -"""message regenerate - -Revision ID: b194ab4e3c69 -Revises: 2dbe42621d96 -Create Date: 2024-08-27 02:31:24.186377 - -""" -import sqlalchemy as sa -from alembic import op - -import models as models - -# revision identifiers, used by Alembic. -revision = 'b194ab4e3c69' -down_revision = '2dbe42621d96' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('messages', schema=None) as batch_op: - batch_op.add_column(sa.Column('is_regenerated', sa.Boolean(), server_default=sa.text('false'), nullable=False)) - batch_op.add_column(sa.Column('parent_message_id', models.types.StringUUID(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('messages', schema=None) as batch_op: - batch_op.drop_column('parent_message_id') - batch_op.drop_column('is_regenerated') - # ### end Alembic commands ### diff --git a/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py b/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py new file mode 100644 index 0000000000000..e9d8de35eb9b0 --- /dev/null +++ b/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py @@ -0,0 +1,47 @@ +"""message regenerate + +Revision ID: 8c563fdfc838 +Revises: d0187d6a88dd +Create Date: 2024-08-29 09:45:44.006183 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8c563fdfc838' +down_revision = 'd0187d6a88dd' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('messages', schema=None) as batch_op: + batch_op.add_column(sa.Column('is_regenerated', sa.Boolean(), server_default=sa.text('false'), nullable=False)) + batch_op.add_column(sa.Column('parent_message_id', models.types.StringUUID(), nullable=True)) + + with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op: + batch_op.drop_index('workflow__conversation_variables_app_id_idx') + batch_op.drop_index('workflow__conversation_variables_created_at_idx') + batch_op.create_index(batch_op.f('workflow_conversation_variables_app_id_idx'), ['app_id'], unique=False) + batch_op.create_index(batch_op.f('workflow_conversation_variables_created_at_idx'), ['created_at'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('workflow_conversation_variables_created_at_idx')) + batch_op.drop_index(batch_op.f('workflow_conversation_variables_app_id_idx')) + batch_op.create_index('workflow__conversation_variables_created_at_idx', ['created_at'], unique=False) + batch_op.create_index('workflow__conversation_variables_app_id_idx', ['app_id'], unique=False) + + with op.batch_alter_table('messages', schema=None) as batch_op: + batch_op.drop_column('parent_message_id') + batch_op.drop_column('is_regenerated') + + # ### end Alembic commands ### From 6ec8755536d9938c3803580c71501978f2fcd779 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Thu, 29 Aug 2024 17:49:23 +0800 Subject: [PATCH 13/44] lint python code and remove unnecessary code --- ...8_29_0945-8c563fdfc838_message_regenerate.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py b/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py index e9d8de35eb9b0..f6c382d01cdee 100644 --- a/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py +++ b/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py @@ -5,10 +5,10 @@ Create Date: 2024-08-29 09:45:44.006183 """ -from alembic import op -import models as models import sqlalchemy as sa +from alembic import op +import models as models # revision identifiers, used by Alembic. revision = '8c563fdfc838' @@ -23,23 +23,10 @@ def upgrade(): batch_op.add_column(sa.Column('is_regenerated', sa.Boolean(), server_default=sa.text('false'), nullable=False)) batch_op.add_column(sa.Column('parent_message_id', models.types.StringUUID(), nullable=True)) - with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op: - batch_op.drop_index('workflow__conversation_variables_app_id_idx') - batch_op.drop_index('workflow__conversation_variables_created_at_idx') - batch_op.create_index(batch_op.f('workflow_conversation_variables_app_id_idx'), ['app_id'], unique=False) - batch_op.create_index(batch_op.f('workflow_conversation_variables_created_at_idx'), ['created_at'], unique=False) - # ### end Alembic commands ### def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op: - batch_op.drop_index(batch_op.f('workflow_conversation_variables_created_at_idx')) - batch_op.drop_index(batch_op.f('workflow_conversation_variables_app_id_idx')) - batch_op.create_index('workflow__conversation_variables_created_at_idx', ['created_at'], unique=False) - batch_op.create_index('workflow__conversation_variables_app_id_idx', ['app_id'], unique=False) - with op.batch_alter_table('messages', schema=None) as batch_op: batch_op.drop_column('parent_message_id') batch_op.drop_column('is_regenerated') From fcd178e62da11b460e455300f8e17f2e7daa1d9e Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Thu, 29 Aug 2024 19:16:36 +0800 Subject: [PATCH 14/44] fix: generation api error --- api/core/app/apps/message_based_app_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index 7e2a224479b1a..d968f45fc569b 100644 --- a/api/core/app/apps/message_based_app_generator.py +++ b/api/core/app/apps/message_based_app_generator.py @@ -214,8 +214,8 @@ def _init_generate_records(self, answer_tokens=0, answer_unit_price=0, answer_price_unit=0, - is_regenerated=application_generate_entity.is_regenerate, - parent_message_id=application_generate_entity.parent_message_id or None, + is_regenerated=getattr(application_generate_entity, 'is_regenerate', False), + parent_message_id=getattr(application_generate_entity, 'parent_message_id', None), provider_response_latency=0, total_price=0, currency='USD', From 34754862cf646eab31ff362af4d2aec916a50fe5 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Fri, 30 Aug 2024 16:27:30 +0800 Subject: [PATCH 15/44] fix: regenerate not working in chatflow and embed app --- .cursorignore | 1 + api/controllers/console/app/workflow.py | 3 +++ .../app/apps/advanced_chat/app_generator.py | 2 ++ api/core/app/entities/app_invoke_entities.py | 2 ++ .../chat/embedded-chatbot/chat-wrapper.tsx | 24 ++++++++++++++++++- .../panel/debug-and-preview/chat-wrapper.tsx | 24 +++++++++++++++++-- .../workflow/panel/debug-and-preview/hooks.ts | 1 + 7 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 .cursorignore diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000000000..6f9f00ff49135 --- /dev/null +++ b/.cursorignore @@ -0,0 +1 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index e44820f6345c4..45ec05b77bfa4 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -166,6 +166,9 @@ def post(self, app_model: App): parser.add_argument("query", type=str, required=True, location="json", default="") parser.add_argument("files", type=list, location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") + parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") + parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") + args = parser.parse_args() try: diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index e7c9ebe09749d..33d835327e82f 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -132,6 +132,8 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, + is_regenerate=args.get('is_regenerate', False), + parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, invoke_from=invoke_from, diff --git a/api/core/app/entities/app_invoke_entities.py b/api/core/app/entities/app_invoke_entities.py index 4fa7c6b923033..d555f90812808 100644 --- a/api/core/app/entities/app_invoke_entities.py +++ b/api/core/app/entities/app_invoke_entities.py @@ -145,6 +145,8 @@ class AdvancedChatAppGenerateEntity(AppGenerateEntity): app_config: WorkflowUIBasedAppConfig conversation_id: Optional[str] = None + is_regenerate: bool + parent_message_id: Optional[str] = None query: str class SingleIterationRunEntity(BaseModel): diff --git a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx index 68646a17dbe6a..ec0fe1955cbb7 100644 --- a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx +++ b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo } from 'react' import Chat from '../chat' import type { ChatConfig, + ChatItem, OnSend, } from '../types' import { useChat } from '../chat/hooks' @@ -49,6 +50,7 @@ const ChatWrapper = () => { handleStop, isResponding, suggestedQuestions, + handleUpdateChatList, } = useChat( appConfig, { @@ -64,11 +66,13 @@ const ChatWrapper = () => { currentChatInstanceRef.current.handleStop = handleStop }, []) - const doSend: OnSend = useCallback((message, files) => { + const doSend: OnSend = useCallback((message, files, is_regenerate, last_answer) => { const data: any = { query: message, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, + is_regenerate: !!is_regenerate, + parent_message_id: last_answer?.id || null, } if (appConfig?.file_upload?.image.enabled && files?.length) @@ -93,6 +97,23 @@ const ChatWrapper = () => { isInstalledApp, appId, ]) + + const doRegenerate = useCallback((chatItem: ChatItem) => { + const index = chatList.findIndex(item => item.id === chatItem.id) + if (index === -1) + return + + const prevMessages = chatList.slice(0, index) + const question = prevMessages.pop() + const lastAnswer = prevMessages.at(-1) + + if (!question) + return + + handleUpdateChatList(prevMessages) + doSend(question.content, question.message_files, true, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) + }, [chatList, handleUpdateChatList, doSend]) + const chatNode = useMemo(() => { if (inputsForms.length) { return ( @@ -124,6 +145,7 @@ const ChatWrapper = () => { chatFooterClassName='pb-4' chatFooterInnerClassName={cn('mx-auto w-full max-w-[720px] tablet:px-4', isMobile && 'px-4')} onSend={doSend} + onRegenerate={doRegenerate} onStopResponding={handleStop} chatNode={chatNode} allToolIcons={appMeta?.tool_icons || {}} diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index 465594003757e..834ad94311e6e 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -18,7 +18,7 @@ import ConversationVariableModal from './conversation-variable-modal' import { useChat } from './hooks' import type { ChatWrapperRefType } from './index' import Chat from '@/app/components/base/chat/chat' -import type { OnSend } from '@/app/components/base/chat/types' +import type { ChatItem, OnSend } from '@/app/components/base/chat/types' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { fetchSuggestedQuestions, @@ -58,6 +58,7 @@ const ChatWrapper = forwardRef(({ showConv const { conversationId, chatList, + handleUpdateChatList, handleStop, isResponding, suggestedQuestions, @@ -73,13 +74,15 @@ const ChatWrapper = forwardRef(({ showConv taskId => stopChatMessageResponding(appDetail!.id, taskId), ) - const doSend = useCallback((query, files) => { + const doSend = useCallback((query, files, is_regenerate, last_answer) => { handleSend( { query, files, inputs: workflowStore.getState().inputs, conversation_id: conversationId, + is_regenerate: !!is_regenerate, + parent_message_id: last_answer?.id || null, }, { onGetSuggestedQuestions: (messageId, getAbortController) => fetchSuggestedQuestions(appDetail!.id, messageId, getAbortController), @@ -87,6 +90,22 @@ const ChatWrapper = forwardRef(({ showConv ) }, [conversationId, handleSend, workflowStore, appDetail]) + const doRegenerate = useCallback((chatItem: ChatItem) => { + const index = chatList.findIndex(item => item.id === chatItem.id) + if (index === -1) + return + + const prevMessages = chatList.slice(0, index) + const question = prevMessages.pop() + const lastAnswer = prevMessages.at(-1) + + if (!question) + return + + handleUpdateChatList(prevMessages) + doSend(question.content, question.message_files, true, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) + }, [chatList, handleUpdateChatList, doSend]) + useImperativeHandle(ref, () => { return { handleRestart, @@ -107,6 +126,7 @@ const ChatWrapper = forwardRef(({ showConv chatFooterClassName='px-4 rounded-bl-2xl' chatFooterInnerClassName='pb-4' onSend={doSend} + onRegenerate={doRegenerate} onStopResponding={handleStop} chatNode={( <> diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 155d2a84ac152..56fd9c7b50d06 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -405,6 +405,7 @@ export const useChat = ( return { conversationId: conversationId.current, chatList, + handleUpdateChatList, handleSend, handleStop, handleRestart, From e7c58c1dd7727d1d5e1d4e5cdb19478eb1f5128d Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Fri, 30 Aug 2024 16:58:50 +0800 Subject: [PATCH 16/44] chore: minor change --- .cursorignore | 1 - api/core/app/apps/chat/app_generator.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .cursorignore diff --git a/.cursorignore b/.cursorignore deleted file mode 100644 index 6f9f00ff49135..0000000000000 --- a/.cursorignore +++ /dev/null @@ -1 +0,0 @@ -# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) diff --git a/api/core/app/apps/chat/app_generator.py b/api/core/app/apps/chat/app_generator.py index d26fea59c0f53..89d672afe20f0 100644 --- a/api/core/app/apps/chat/app_generator.py +++ b/api/core/app/apps/chat/app_generator.py @@ -137,6 +137,7 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, + is_regenerate=args.get('is_regenerate', False), parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, From 53e0865c2325dfa20bf0c2e8d9f79b44750c1e8e Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sun, 1 Sep 2024 23:55:58 +0800 Subject: [PATCH 17/44] chore: add field to all non-first messages --- .../debug-with-multiple-model/chat-item.tsx | 5 +- .../debug/debug-with-single-model/index.tsx | 9 +-- .../chat/chat-with-history/chat-wrapper.tsx | 8 ++- .../base/chat/chat-with-history/hooks.tsx | 57 +++--------------- web/app/components/base/chat/chat/hooks.ts | 1 + .../chat/embedded-chatbot/chat-wrapper.tsx | 8 ++- .../base/chat/embedded-chatbot/hooks.tsx | 38 +++--------- web/app/components/base/chat/utils.ts | 60 ++++++++++++++++++- .../panel/debug-and-preview/chat-wrapper.tsx | 9 +-- .../workflow/panel/debug-and-preview/hooks.ts | 1 + web/hooks/use-app-favicon.ts | 6 +- 11 files changed, 103 insertions(+), 99 deletions(-) diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx index e79cdf4793972..0e189b9036178 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx @@ -46,6 +46,7 @@ const ChatItem: FC = ({ const config = useConfigFromDebugContext() const { chatList, + chatListRef, isResponding, handleSend, suggestedQuestions, @@ -80,6 +81,8 @@ const ChatItem: FC = ({ query: message, inputs, model_config: configData, + is_regenerate: false, + parent_message_id: chatListRef.current.at(-1)?.id || null, } if (visionConfig.enabled && files?.length && supportVision) @@ -93,7 +96,7 @@ const ChatItem: FC = ({ onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController), }, ) - }, [appId, config, handleSend, inputs, modelAndParameter, textGenerationModelList, visionConfig.enabled]) + }, [appId, config, handleSend, inputs, modelAndParameter, textGenerationModelList, visionConfig.enabled, chatListRef]) const { eventEmitter } = useEventEmitterContextContext() eventEmitter?.useSubscription((v: any) => { diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx index fa87b3e637592..2bc68330960ef 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx @@ -45,6 +45,7 @@ const DebugWithSingleModel = forwardRef { + const doSend: OnSend = useCallback((message, files, is_regenerate = false, last_answer) => { if (checkCanSend && !checkCanSend()) return const currentProvider = textGenerationModelList.find(item => item.provider === modelConfig.provider) @@ -86,8 +87,8 @@ const DebugWithSingleModel = forwardRef fetchSuggestedQuestions(appId, responseItemId, getAbortController), }, ) - }, [appId, checkCanSend, completionParams, config, handleSend, inputs, modelConfig, textGenerationModelList, visionConfig.enabled]) + }, [chatListRef, appId, checkCanSend, completionParams, config, handleSend, inputs, modelConfig, textGenerationModelList, visionConfig.enabled]) const doRegenerate = useCallback((chatItem: ChatItem) => { const index = chatList.findIndex(item => item.id === chatItem.id) diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index babacc4de68df..e40dc1861e291 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -44,6 +44,7 @@ const ChatWrapper = () => { }, [appParams, currentConversationItem?.introduction, currentConversationId]) const { chatList, + chatListRef, handleUpdateChatList, handleSend, handleStop, @@ -64,13 +65,13 @@ const ChatWrapper = () => { currentChatInstanceRef.current.handleStop = handleStop }, []) - const doSend: OnSend = useCallback((message, files, is_regenerate, last_answer) => { + const doSend: OnSend = useCallback((message, files, is_regenerate = false, last_answer) => { const data: any = { query: message, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, - is_regenerate: !!is_regenerate, - parent_message_id: last_answer?.id || null, + is_regenerate, + parent_message_id: last_answer?.id || chatListRef.current.at(-1)?.id || null, } if (appConfig?.file_upload?.image.enabled && files?.length) @@ -86,6 +87,7 @@ const ChatWrapper = () => { }, ) }, [ + chatListRef, appConfig, currentConversationId, currentConversationItem, diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index 3061bf41b3a7c..a4f36c84305d3 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -12,10 +12,10 @@ import produce from 'immer' import type { Callback, ChatConfig, - ChatItem, Feedback, } from '../types' import { CONVERSATION_ID_INFO } from '../constants' +import { getPrevChatList } from '../utils' import { delConversation, fetchAppInfo, @@ -34,30 +34,10 @@ import type { AppData, ConversationItem, } from '@/models/share' -import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' import { useToastContext } from '@/app/components/base/toast' import { changeLanguage } from '@/i18n/i18next-config' import { useAppFavicon } from '@/hooks/use-app-favicon' -function appendQAToChatList(chatList: ChatItem[], item: any) { - // we append answer first and then question since will reverse the whole chatList later - chatList.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.feedback, - isAnswer: true, - citation: item.retriever_resources, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], - }) - chatList.push({ - id: `question-${item.id}`, - content: item.query, - isAnswer: false, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], - }) -} - export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo) @@ -126,35 +106,12 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100)) const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId)) - const appPrevChatList = useMemo(() => { - const data = appChatListData?.data || [] - const chatList: ChatItem[] = [] - - if (currentConversationId && data.length) { - let nextMessageId = null - for (const item of data) { - if (item.is_regenerated && !item.parent_message_id) { - appendQAToChatList(chatList, item) - break - } - - if (!nextMessageId) { - appendQAToChatList(chatList, item) - if (item.parent_message_id) - nextMessageId = item.parent_message_id - } - else { - if (item.id === nextMessageId) { - appendQAToChatList(chatList, item) - nextMessageId = item.parent_message_id - } - } - } - chatList.reverse() - } - - return chatList - }, [appChatListData, currentConversationId]) + const appPrevChatList = useMemo( + () => (currentConversationId && appChatListData?.data.length) + ? getPrevChatList(appChatListData.data) + : [], + [appChatListData, currentConversationId], + ) const [showNewConversationItemInList, setShowNewConversationItemInList] = useState(false) diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 0fb7ab2a1133c..12fe7f99732bb 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -639,6 +639,7 @@ export const useChat = ( return { chatList, + chatListRef, handleUpdateChatList, conversationId: conversationId.current, isResponding, diff --git a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx index ec0fe1955cbb7..6f685efae6a35 100644 --- a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx +++ b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx @@ -45,6 +45,7 @@ const ChatWrapper = () => { } as ChatConfig }, [appParams, currentConversationItem?.introduction, currentConversationId]) const { + chatListRef, chatList, handleSend, handleStop, @@ -66,13 +67,13 @@ const ChatWrapper = () => { currentChatInstanceRef.current.handleStop = handleStop }, []) - const doSend: OnSend = useCallback((message, files, is_regenerate, last_answer) => { + const doSend: OnSend = useCallback((message, files, is_regenerate = false, last_answer) => { const data: any = { query: message, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, - is_regenerate: !!is_regenerate, - parent_message_id: last_answer?.id || null, + is_regenerate, + parent_message_id: last_answer?.id || chatListRef.current.at(-1)?.id || null, } if (appConfig?.file_upload?.image.enabled && files?.length) @@ -88,6 +89,7 @@ const ChatWrapper = () => { }, ) }, [ + chatListRef, appConfig, currentConversationId, currentConversationItem, diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 39d25f57d194e..fd89efcbff3ab 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -11,10 +11,10 @@ import { useLocalStorageState } from 'ahooks' import produce from 'immer' import type { ChatConfig, - ChatItem, Feedback, } from '../types' import { CONVERSATION_ID_INFO } from '../constants' +import { getPrevChatList, getProcessedInputsFromUrlParams } from '../utils' import { fetchAppInfo, fetchAppMeta, @@ -28,10 +28,8 @@ import type { // AppData, ConversationItem, } from '@/models/share' -import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' import { useToastContext } from '@/app/components/base/toast' import { changeLanguage } from '@/i18n/i18next-config' -import { getProcessedInputsFromUrlParams } from '@/app/components/base/chat/utils' export const useEmbeddedChatbot = () => { const isInstalledApp = false @@ -75,32 +73,12 @@ export const useEmbeddedChatbot = () => { const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100)) const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId)) - const appPrevChatList = useMemo(() => { - const data = appChatListData?.data || [] - const chatList: ChatItem[] = [] - - if (currentConversationId && data.length) { - data.forEach((item: any) => { - chatList.push({ - id: `question-${item.id}`, - content: item.query, - isAnswer: false, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], - }) - chatList.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.feedback, - isAnswer: true, - citation: item.retriever_resources, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], - }) - }) - } - - return chatList - }, [appChatListData, currentConversationId]) + const appPrevChatList = useMemo( + () => (currentConversationId && appChatListData?.data.length) + ? getPrevChatList(appChatListData.data) + : [], + [appChatListData, currentConversationId], + ) const [showNewConversationItemInList, setShowNewConversationItemInList] = useState(false) @@ -155,7 +133,7 @@ export const useEmbeddedChatbot = () => { type: 'text-input', } }) - }, [appParams]) + }, [initInputs, appParams]) useEffect(() => { // init inputs from url params diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index 3fe5050cc7b34..0a7148d219b03 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -1,7 +1,10 @@ +import { addFileInfos, sortAgentSorts } from '../../tools/utils' +import type { ChatItem } from './types' + async function decodeBase64AndDecompress(base64String: string) { const binaryString = atob(base64String) const compressedUint8Array = Uint8Array.from(binaryString, char => char.charCodeAt(0)) - const decompressedStream = new Response(compressedUint8Array).body.pipeThrough(new DecompressionStream('gzip')) + const decompressedStream = new Response(compressedUint8Array).body?.pipeThrough(new DecompressionStream('gzip')) const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer() return new TextDecoder().decode(decompressedArrayBuffer) } @@ -15,6 +18,61 @@ function getProcessedInputsFromUrlParams(): Record { return inputs } +function appendQAToChatList(chatList: ChatItem[], item: any) { + // we append answer first and then question since will reverse the whole chatList later + chatList.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.feedback, + isAnswer: true, + citation: item.retriever_resources, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], + }) + chatList.push({ + id: `question-${item.id}`, + content: item.query, + isAnswer: false, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], + }) +} + +/** + * Computes the chat list for display. + * + * @param appChatListData - The history chat list data from the backend. This includes all history messages and may contain branches. + * @param currentConversationId - The ID of the current conversation. + * @returns An array of ChatItems representing the latest thread. + * + * Note: The chat list from the backend includes all history messages and may contain branches. + * This function only computes the latest thread from that list. + */ +function getPrevChatList(fetchedMessages: any[]) { + const ret: ChatItem[] = [] + + let nextMessageId = null + for (const item of fetchedMessages) { + if (item.is_regenerated && !item.parent_message_id) { + appendQAToChatList(ret, item) + break + } + + if (!nextMessageId) { + appendQAToChatList(ret, item) + if (item.parent_message_id) + nextMessageId = item.parent_message_id + } + else { + if (item.id === nextMessageId) { + appendQAToChatList(ret, item) + nextMessageId = item.parent_message_id + } + } + } + return ret.reverse() +} + export { getProcessedInputsFromUrlParams, + getPrevChatList, } diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index 834ad94311e6e..0fb4d71776df4 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -58,6 +58,7 @@ const ChatWrapper = forwardRef(({ showConv const { conversationId, chatList, + chatListRef, handleUpdateChatList, handleStop, isResponding, @@ -74,21 +75,21 @@ const ChatWrapper = forwardRef(({ showConv taskId => stopChatMessageResponding(appDetail!.id, taskId), ) - const doSend = useCallback((query, files, is_regenerate, last_answer) => { + const doSend = useCallback((query, files, is_regenerate = false, last_answer) => { handleSend( { query, files, inputs: workflowStore.getState().inputs, conversation_id: conversationId, - is_regenerate: !!is_regenerate, - parent_message_id: last_answer?.id || null, + is_regenerate, + parent_message_id: last_answer?.id || chatListRef.current.at(-1)?.id || null, }, { onGetSuggestedQuestions: (messageId, getAbortController) => fetchSuggestedQuestions(appDetail!.id, messageId, getAbortController), }, ) - }, [conversationId, handleSend, workflowStore, appDetail]) + }, [chatListRef, conversationId, handleSend, workflowStore, appDetail]) const doRegenerate = useCallback((chatItem: ChatItem) => { const index = chatList.findIndex(item => item.id === chatItem.id) diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 56fd9c7b50d06..7e778da90d235 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -405,6 +405,7 @@ export const useChat = ( return { conversationId: conversationId.current, chatList, + chatListRef, handleUpdateChatList, handleSend, handleStop, diff --git a/web/hooks/use-app-favicon.ts b/web/hooks/use-app-favicon.ts index 86eadc1b3d086..1ff743928faae 100644 --- a/web/hooks/use-app-favicon.ts +++ b/web/hooks/use-app-favicon.ts @@ -5,10 +5,10 @@ import type { AppIconType } from '@/types/app' type UseAppFaviconOptions = { enable?: boolean - icon_type?: AppIconType + icon_type?: AppIconType | null icon?: string - icon_background?: string - icon_url?: string + icon_background?: string | null + icon_url?: string | null } export function useAppFavicon(options: UseAppFaviconOptions) { From 863e59554c93c5993ed5805e6fc320c3bb52f787 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Mon, 2 Sep 2024 11:13:59 +0800 Subject: [PATCH 18/44] chor: update comments --- web/app/components/base/chat/utils.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index 0a7148d219b03..cc5d9fb526490 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -38,14 +38,10 @@ function appendQAToChatList(chatList: ChatItem[], item: any) { } /** - * Computes the chat list for display. + * Computes the latest thread messages from all messages of the conversation. * - * @param appChatListData - The history chat list data from the backend. This includes all history messages and may contain branches. - * @param currentConversationId - The ID of the current conversation. + * @param fetchedMessages - The history chat list data from the backend, sorted by created_at in descending order. This includes all flattened history messages of the conversation. * @returns An array of ChatItems representing the latest thread. - * - * Note: The chat list from the backend includes all history messages and may contain branches. - * This function only computes the latest thread from that list. */ function getPrevChatList(fetchedMessages: any[]) { const ret: ChatItem[] = [] From 3a5c0b3b99bf5f9872ff80a210df9e06a1bb75a8 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 4 Sep 2024 16:41:49 +0800 Subject: [PATCH 19/44] chore: update db mirgration code --- ...09_04_0840-ddfb53799c6f_message_regenerate.py} | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename api/migrations/versions/{2024_08_29_0945-8c563fdfc838_message_regenerate.py => 2024_09_04_0840-ddfb53799c6f_message_regenerate.py} (79%) diff --git a/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py b/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py similarity index 79% rename from api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py rename to api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py index f6c382d01cdee..e936073537c45 100644 --- a/api/migrations/versions/2024_08_29_0945-8c563fdfc838_message_regenerate.py +++ b/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py @@ -1,18 +1,18 @@ """message regenerate -Revision ID: 8c563fdfc838 -Revises: d0187d6a88dd -Create Date: 2024-08-29 09:45:44.006183 +Revision ID: ddfb53799c6f +Revises: 030f4915f36a +Create Date: 2024-09-04 08:40:21.488287 """ -import sqlalchemy as sa from alembic import op - import models as models +import sqlalchemy as sa + # revision identifiers, used by Alembic. -revision = '8c563fdfc838' -down_revision = 'd0187d6a88dd' +revision = 'ddfb53799c6f' +down_revision = '030f4915f36a' branch_labels = None depends_on = None @@ -27,6 +27,7 @@ def upgrade(): def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('messages', schema=None) as batch_op: batch_op.drop_column('parent_message_id') batch_op.drop_column('is_regenerated') From 823c592cb6605a708287e69677cc2e4edb293ef1 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 4 Sep 2024 16:42:18 +0800 Subject: [PATCH 20/44] chore: lint python code --- .../2024_09_04_0840-ddfb53799c6f_message_regenerate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py b/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py index e936073537c45..b33c18d4364ee 100644 --- a/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py +++ b/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py @@ -5,10 +5,10 @@ Create Date: 2024-09-04 08:40:21.488287 """ -from alembic import op -import models as models import sqlalchemy as sa +from alembic import op +import models as models # revision identifiers, used by Alembic. revision = 'ddfb53799c6f' From f89ca21b5e09aa0c24a5d39688fcd39e7b509251 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sat, 7 Sep 2024 15:39:06 +0800 Subject: [PATCH 21/44] refactor: use only --- api/constants/__init__.py | 1 + api/controllers/console/app/completion.py | 1 - api/controllers/console/app/workflow.py | 1 - api/controllers/console/explore/completion.py | 1 - api/controllers/service_api/app/message.py | 1 - api/controllers/web/completion.py | 1 - api/controllers/web/message.py | 1 - .../app/apps/advanced_chat/app_generator.py | 1 - api/core/app/apps/agent_chat/app_generator.py | 1 - api/core/app/apps/chat/app_generator.py | 1 - .../app/apps/message_based_app_generator.py | 1 - api/core/app/entities/app_invoke_entities.py | 3 - api/core/memory/token_buffer_memory.py | 1 - .../prompt/utils/extract_thread_messages.py | 10 ++- api/fields/message_fields.py | 1 - ...6a2b_add_parent_message_id_to_messages.py} | 13 +-- api/models/model.py | 1 - .../prompt/test_extract_thread_messages.py | 89 +++++++++++++++++++ .../debug-with-multiple-model/chat-item.tsx | 1 - .../debug/debug-with-single-model/index.tsx | 5 +- .../chat/chat-with-history/chat-wrapper.tsx | 5 +- web/app/components/base/chat/constants.ts | 1 + .../chat/embedded-chatbot/chat-wrapper.tsx | 5 +- web/app/components/base/chat/types.ts | 2 +- web/app/components/base/chat/utils.ts | 11 +-- .../panel/debug-and-preview/chat-wrapper.tsx | 5 +- 26 files changed, 119 insertions(+), 45 deletions(-) rename api/migrations/versions/{2024_09_04_0840-ddfb53799c6f_message_regenerate.py => 2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py} (66%) create mode 100644 api/tests/unit_tests/core/prompt/test_extract_thread_messages.py diff --git a/api/constants/__init__.py b/api/constants/__init__.py index e22c3268ef428..75eaf81638cdf 100644 --- a/api/constants/__init__.py +++ b/api/constants/__init__.py @@ -1 +1,2 @@ HIDDEN_VALUE = "[__HIDDEN__]" +UUID_NIL = "00000000-0000-0000-0000-000000000000" diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index bcdc423dab6b8..d3296d3dff44a 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -109,7 +109,6 @@ def post(self, app_model): parser.add_argument("files", type=list, required=False, location="json") parser.add_argument("model_config", type=dict, required=True, location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") - parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") parser.add_argument("response_mode", type=str, choices=["blocking", "streaming"], location="json") parser.add_argument("retriever_from", type=str, required=False, default="dev", location="json") diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 45ec05b77bfa4..e31d27d21b514 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -166,7 +166,6 @@ def post(self, app_model: App): parser.add_argument("query", type=str, required=True, location="json", default="") parser.add_argument("files", type=list, location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") - parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") args = parser.parse_args() diff --git a/api/controllers/console/explore/completion.py b/api/controllers/console/explore/completion.py index 8c31ca20c4e3a..f865ff443159e 100644 --- a/api/controllers/console/explore/completion.py +++ b/api/controllers/console/explore/completion.py @@ -100,7 +100,6 @@ def post(self, installed_app): parser.add_argument("query", type=str, required=True, location="json") parser.add_argument("files", type=list, required=False, location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") - parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") parser.add_argument("retriever_from", type=str, required=False, default="explore_app", location="json") args = parser.parse_args() diff --git a/api/controllers/service_api/app/message.py b/api/controllers/service_api/app/message.py index 97ce8cabf2f32..a17a6d86d37e1 100644 --- a/api/controllers/service_api/app/message.py +++ b/api/controllers/service_api/app/message.py @@ -54,7 +54,6 @@ class MessageListApi(Resource): message_fields = { "id": fields.String, "conversation_id": fields.String, - "is_regenerated": fields.Boolean, "parent_message_id": fields.String, "inputs": fields.Raw, "query": fields.String, diff --git a/api/controllers/web/completion.py b/api/controllers/web/completion.py index 43a3e25c23510..87ce709496717 100644 --- a/api/controllers/web/completion.py +++ b/api/controllers/web/completion.py @@ -96,7 +96,6 @@ def post(self, app_model, end_user): parser.add_argument("files", type=list, required=False, location="json") parser.add_argument("response_mode", type=str, choices=["blocking", "streaming"], location="json") parser.add_argument("conversation_id", type=uuid_value, location="json") - parser.add_argument("is_regenerate", type=bool, required=False, default=False, location="json") parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json") parser.add_argument("retriever_from", type=str, required=False, default="web_app", location="json") diff --git a/api/controllers/web/message.py b/api/controllers/web/message.py index 7736489e01f22..c6ef0f7e509bc 100644 --- a/api/controllers/web/message.py +++ b/api/controllers/web/message.py @@ -57,7 +57,6 @@ class MessageListApi(WebApiResource): message_fields = { "id": fields.String, "conversation_id": fields.String, - "is_regenerated": fields.Boolean, "parent_message_id": fields.String, "inputs": fields.Raw, "query": fields.String, diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index 33d835327e82f..a133d03f989c5 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -132,7 +132,6 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, - is_regenerate=args.get('is_regenerate', False), parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index d9736892a6815..29d8ae1135894 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -140,7 +140,6 @@ def generate(self, app_model: App, inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, - is_regenerate=args.get('is_regenerate', False), parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, diff --git a/api/core/app/apps/chat/app_generator.py b/api/core/app/apps/chat/app_generator.py index 89d672afe20f0..d26fea59c0f53 100644 --- a/api/core/app/apps/chat/app_generator.py +++ b/api/core/app/apps/chat/app_generator.py @@ -137,7 +137,6 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, - is_regenerate=args.get('is_regenerate', False), parent_message_id=args.get('parent_message_id'), user_id=user.id, stream=stream, diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index d968f45fc569b..12ffabf5cc499 100644 --- a/api/core/app/apps/message_based_app_generator.py +++ b/api/core/app/apps/message_based_app_generator.py @@ -214,7 +214,6 @@ def _init_generate_records(self, answer_tokens=0, answer_unit_price=0, answer_price_unit=0, - is_regenerated=getattr(application_generate_entity, 'is_regenerate', False), parent_message_id=getattr(application_generate_entity, 'parent_message_id', None), provider_response_latency=0, total_price=0, diff --git a/api/core/app/entities/app_invoke_entities.py b/api/core/app/entities/app_invoke_entities.py index d555f90812808..1f8d0230b62fb 100644 --- a/api/core/app/entities/app_invoke_entities.py +++ b/api/core/app/entities/app_invoke_entities.py @@ -117,7 +117,6 @@ class ChatAppGenerateEntity(EasyUIBasedAppGenerateEntity): Chat Application Generate Entity. """ conversation_id: Optional[str] = None - is_regenerate: bool parent_message_id: Optional[str] = None @@ -133,7 +132,6 @@ class AgentChatAppGenerateEntity(EasyUIBasedAppGenerateEntity): Agent Chat Application Generate Entity. """ conversation_id: Optional[str] = None - is_regenerate: bool parent_message_id: Optional[str] = None @@ -145,7 +143,6 @@ class AdvancedChatAppGenerateEntity(AppGenerateEntity): app_config: WorkflowUIBasedAppConfig conversation_id: Optional[str] = None - is_regenerate: bool parent_message_id: Optional[str] = None query: str diff --git a/api/core/memory/token_buffer_memory.py b/api/core/memory/token_buffer_memory.py index c5735eff63391..b93d8ec36516d 100644 --- a/api/core/memory/token_buffer_memory.py +++ b/api/core/memory/token_buffer_memory.py @@ -38,7 +38,6 @@ def get_history_prompt_messages(self, max_token_limit: int = 2000, Message.answer, Message.created_at, Message.workflow_run_id, - Message.is_regenerated, Message.parent_message_id, ).filter( Message.conversation_id == self.conversation.id, diff --git a/api/core/prompt/utils/extract_thread_messages.py b/api/core/prompt/utils/extract_thread_messages.py index ed89b6034e9b3..e16153f99bd72 100644 --- a/api/core/prompt/utils/extract_thread_messages.py +++ b/api/core/prompt/utils/extract_thread_messages.py @@ -1,19 +1,21 @@ +from constants import UUID_NIL + + def extract_thread_messages(messages: list[dict]) -> list[dict]: thread_messages = [] next_message = None for message in messages: - if message.is_regenerated and not message.parent_message_id: + if not message.parent_message_id: # If the message is regenerated and does not have a parent message, it is the start of a new thread thread_messages.append(message) break if not next_message: thread_messages.append(message) - if message.parent_message_id: - next_message = message.parent_message_id + next_message = message.parent_message_id else: - if message.id == next_message: + if message.id == next_message or next_message == UUID_NIL: thread_messages.append(message) next_message = message.parent_message_id diff --git a/api/fields/message_fields.py b/api/fields/message_fields.py index 31392bd41b758..c938097131f7a 100644 --- a/api/fields/message_fields.py +++ b/api/fields/message_fields.py @@ -62,7 +62,6 @@ message_fields = { "id": fields.String, "conversation_id": fields.String, - "is_regenerated": fields.Boolean, "parent_message_id": fields.String, "inputs": fields.Raw, "query": fields.String, diff --git a/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py b/api/migrations/versions/2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py similarity index 66% rename from api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py rename to api/migrations/versions/2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py index b33c18d4364ee..e45b366346664 100644 --- a/api/migrations/versions/2024_09_04_0840-ddfb53799c6f_message_regenerate.py +++ b/api/migrations/versions/2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py @@ -1,8 +1,8 @@ -"""message regenerate +"""add parent_message_id to messages -Revision ID: ddfb53799c6f +Revision ID: ed5ab9b16a2b Revises: 030f4915f36a -Create Date: 2024-09-04 08:40:21.488287 +Create Date: 2024-09-05 10:12:33.799034 """ import sqlalchemy as sa @@ -11,7 +11,7 @@ import models as models # revision identifiers, used by Alembic. -revision = 'ddfb53799c6f' +revision = 'ed5ab9b16a2b' down_revision = '030f4915f36a' branch_labels = None depends_on = None @@ -20,8 +20,10 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('messages', schema=None) as batch_op: - batch_op.add_column(sa.Column('is_regenerated', sa.Boolean(), server_default=sa.text('false'), nullable=False)) batch_op.add_column(sa.Column('parent_message_id', models.types.StringUUID(), nullable=True)) + + # Set parent_message_id for existing messages to uuid_nil() to distinguish them from new messages with actual parent IDs or NULLs + op.execute('UPDATE messages SET parent_message_id = uuid_nil() WHERE parent_message_id IS NULL') # ### end Alembic commands ### @@ -30,6 +32,5 @@ def downgrade(): # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('messages', schema=None) as batch_op: batch_op.drop_column('parent_message_id') - batch_op.drop_column('is_regenerated') # ### end Alembic commands ### diff --git a/api/models/model.py b/api/models/model.py index d2a60496188a6..6caf667c79568 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -668,7 +668,6 @@ class Message(db.Model): answer_tokens = db.Column(db.Integer, nullable=False, server_default=db.text('0')) answer_unit_price = db.Column(db.Numeric(10, 4), nullable=False) answer_price_unit = db.Column(db.Numeric(10, 7), nullable=False, server_default=db.text('0.001')) - is_regenerated = db.Column(db.Boolean, nullable=False, server_default=db.text('false')) parent_message_id = db.Column(StringUUID, nullable=True) provider_response_latency = db.Column(db.Float, nullable=False, server_default=db.text('0')) total_price = db.Column(db.Numeric(10, 7)) diff --git a/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py b/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py new file mode 100644 index 0000000000000..7def79c5db382 --- /dev/null +++ b/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py @@ -0,0 +1,89 @@ +from uuid import uuid4 + +from constants import UUID_NIL +from core.prompt.utils.extract_thread_messages import extract_thread_messages + + +class TestMessage: + def __init__(self, id, parent_message_id): + self.id = id + self.parent_message_id = parent_message_id + + def __getitem__(self, item): + return getattr(self, item) + +def test_extract_thread_messages_single_message(): + messages = [TestMessage(str(uuid4()), UUID_NIL)] + result = extract_thread_messages(messages) + assert len(result) == 1 + assert result[0] == messages[0] + + +def test_extract_thread_messages_linear_thread(): + id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) + messages = [ + TestMessage(id5, id4), + TestMessage(id4, id3), + TestMessage(id3, id2), + TestMessage(id2, id1), + TestMessage(id1, UUID_NIL), + ] + result = extract_thread_messages(messages) + assert len(result) == 5 + assert [msg["id"] for msg in result] == [id5, id4, id3, id2, id1] + + +def test_extract_thread_messages_branched_thread(): + id1, id2, id3, id4 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) + messages = [ + TestMessage(id4, id2), + TestMessage(id3, id2), + TestMessage(id2, id1), + TestMessage(id1, UUID_NIL), + ] + result = extract_thread_messages(messages) + assert len(result) == 3 + assert [msg["id"] for msg in result] == [id4, id2, id1] + + +def test_extract_thread_messages_empty_list(): + messages = [] + result = extract_thread_messages(messages) + assert len(result) == 0 + + +def test_extract_thread_messages_partially_loaded(): + id0, id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) + messages = [ + TestMessage(id3, id2), + TestMessage(id2, id1), + TestMessage(id1, id0), + ] + result = extract_thread_messages(messages) + assert len(result) == 3 + assert [msg["id"] for msg in result] == [id3, id2, id1] + + +def test_extract_thread_messages_legacy_messages(): + id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4()) + messages = [ + TestMessage(id3, UUID_NIL), + TestMessage(id2, UUID_NIL), + TestMessage(id1, UUID_NIL), + ] + result = extract_thread_messages(messages) + assert len(result) == 3 + assert [msg["id"] for msg in result] == [id3, id2, id1] + +def test_extract_thread_messages_mixed_with_legacy_messages(): + id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) + messages = [ + TestMessage(id5, id4), + TestMessage(id4, id2), + TestMessage(id3, id2), + TestMessage(id2, UUID_NIL), + TestMessage(id1, UUID_NIL), + ] + result = extract_thread_messages(messages) + assert len(result) == 4 + assert [msg["id"] for msg in result] == [id5, id4, id2, id1] diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx index 0e189b9036178..863a31aae06ae 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx @@ -81,7 +81,6 @@ const ChatItem: FC = ({ query: message, inputs, model_config: configData, - is_regenerate: false, parent_message_id: chatListRef.current.at(-1)?.id || null, } diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx index 2bc68330960ef..a1aa316eb7902 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx @@ -66,7 +66,7 @@ const DebugWithSingleModel = forwardRef { + const doSend: OnSend = useCallback((message, files, last_answer) => { if (checkCanSend && !checkCanSend()) return const currentProvider = textGenerationModelList.find(item => item.provider === modelConfig.provider) @@ -87,7 +87,6 @@ const DebugWithSingleModel = forwardRef { diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index 272f89f03908e..1f816d6e96f39 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -66,12 +66,11 @@ const ChatWrapper = () => { currentChatInstanceRef.current.handleStop = handleStop }, []) - const doSend: OnSend = useCallback((message, files, is_regenerate = false, last_answer) => { + const doSend: OnSend = useCallback((message, files, last_answer) => { const data: any = { query: message, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, - is_regenerate, parent_message_id: last_answer?.id || chatListRef.current.at(-1)?.id || null, } @@ -112,7 +111,7 @@ const ChatWrapper = () => { return handleUpdateChatList(prevMessages) - doSend(question.content, question.message_files, true, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) + doSend(question.content, question.message_files, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) }, [chatList, handleUpdateChatList, doSend]) const chatNode = useMemo(() => { diff --git a/web/app/components/base/chat/constants.ts b/web/app/components/base/chat/constants.ts index 8249be7375d2e..309f0f04a716b 100644 --- a/web/app/components/base/chat/constants.ts +++ b/web/app/components/base/chat/constants.ts @@ -1 +1,2 @@ export const CONVERSATION_ID_INFO = 'conversationIdInfo' +export const UUID_NIL = '00000000-0000-0000-0000-000000000000' diff --git a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx index 3e142006b29d3..5f4903b7858d5 100644 --- a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx +++ b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx @@ -68,12 +68,11 @@ const ChatWrapper = () => { currentChatInstanceRef.current.handleStop = handleStop }, []) - const doSend: OnSend = useCallback((message, files, is_regenerate = false, last_answer) => { + const doSend: OnSend = useCallback((message, files, last_answer) => { const data: any = { query: message, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, - is_regenerate, parent_message_id: last_answer?.id || chatListRef.current.at(-1)?.id || null, } @@ -114,7 +113,7 @@ const ChatWrapper = () => { return handleUpdateChatList(prevMessages) - doSend(question.content, question.message_files, true, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) + doSend(question.content, question.message_files, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) }, [chatList, handleUpdateChatList, doSend]) const chatNode = useMemo(() => { diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts index bc097bc9dff89..ec4c39743be10 100644 --- a/web/app/components/base/chat/types.ts +++ b/web/app/components/base/chat/types.ts @@ -63,7 +63,7 @@ export type ChatItem = IChatItem & { conversationId?: string } -export type OnSend = (message: string, files?: VisionFile[], isRegenerate?: boolean, last_answer?: ChatItem) => void +export type OnSend = (message: string, files?: VisionFile[], last_answer?: ChatItem) => void export type OnRegenerate = (chatItem: ChatItem) => void diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index cc5d9fb526490..e851c4c4633fb 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -1,4 +1,5 @@ import { addFileInfos, sortAgentSorts } from '../../tools/utils' +import { UUID_NIL } from './constants' import type { ChatItem } from './types' async function decodeBase64AndDecompress(base64String: string) { @@ -39,27 +40,27 @@ function appendQAToChatList(chatList: ChatItem[], item: any) { /** * Computes the latest thread messages from all messages of the conversation. + * Same logic as backend codebase `api/core/prompt/utils/extract_thread_messages.py` * * @param fetchedMessages - The history chat list data from the backend, sorted by created_at in descending order. This includes all flattened history messages of the conversation. * @returns An array of ChatItems representing the latest thread. */ function getPrevChatList(fetchedMessages: any[]) { const ret: ChatItem[] = [] - let nextMessageId = null + for (const item of fetchedMessages) { - if (item.is_regenerated && !item.parent_message_id) { + if (!item.parent_message_id) { appendQAToChatList(ret, item) break } if (!nextMessageId) { appendQAToChatList(ret, item) - if (item.parent_message_id) - nextMessageId = item.parent_message_id + nextMessageId = item.parent_message_id } else { - if (item.id === nextMessageId) { + if (item.id === nextMessageId || nextMessageId === UUID_NIL) { appendQAToChatList(ret, item) nextMessageId = item.parent_message_id } diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index 0fb4d71776df4..5be89443d95af 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -75,14 +75,13 @@ const ChatWrapper = forwardRef(({ showConv taskId => stopChatMessageResponding(appDetail!.id, taskId), ) - const doSend = useCallback((query, files, is_regenerate = false, last_answer) => { + const doSend = useCallback((query, files, last_answer) => { handleSend( { query, files, inputs: workflowStore.getState().inputs, conversation_id: conversationId, - is_regenerate, parent_message_id: last_answer?.id || chatListRef.current.at(-1)?.id || null, }, { @@ -104,7 +103,7 @@ const ChatWrapper = forwardRef(({ showConv return handleUpdateChatList(prevMessages) - doSend(question.content, question.message_files, true, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) + doSend(question.content, question.message_files, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer) }, [chatList, handleUpdateChatList, doSend]) useImperativeHandle(ref, () => { From c32f2739fabefad1a00b9b047499f38389f32fdf Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sat, 7 Sep 2024 15:51:06 +0800 Subject: [PATCH 22/44] style: regenerate icon --- web/app/components/base/regenerate-btn/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/app/components/base/regenerate-btn/index.tsx b/web/app/components/base/regenerate-btn/index.tsx index 7428f8ccccad1..99f3eb91c64b8 100644 --- a/web/app/components/base/regenerate-btn/index.tsx +++ b/web/app/components/base/regenerate-btn/index.tsx @@ -17,8 +17,11 @@ const RegenerateBtn = ({ className, onClick }: Props) => {
onClick?.()} + style={{ + boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)', + }} > - +
From 5b6a03ca28862fd668fd47a70f716b1c2afe14b0 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sat, 7 Sep 2024 15:53:09 +0800 Subject: [PATCH 23/44] chore: lint python code --- .../unit_tests/core/prompt/test_extract_thread_messages.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py b/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py index 7def79c5db382..ba3c1eb5e032a 100644 --- a/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py +++ b/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py @@ -8,10 +8,11 @@ class TestMessage: def __init__(self, id, parent_message_id): self.id = id self.parent_message_id = parent_message_id - + def __getitem__(self, item): return getattr(self, item) + def test_extract_thread_messages_single_message(): messages = [TestMessage(str(uuid4()), UUID_NIL)] result = extract_thread_messages(messages) @@ -75,6 +76,7 @@ def test_extract_thread_messages_legacy_messages(): assert len(result) == 3 assert [msg["id"] for msg in result] == [id3, id2, id1] + def test_extract_thread_messages_mixed_with_legacy_messages(): id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) messages = [ From ab6ad76aaaf905f627b6c5edd71431c6e8712368 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 10 Sep 2024 15:33:56 +0800 Subject: [PATCH 24/44] staging --- api/fields/conversation_fields.py | 1 + web/app/components/app/log/list.tsx | 63 ++++--- .../chat/__tests__/branchedTestMessages.json | 42 +++++ .../chat/__tests__/legacyTestMessages.json | 42 +++++ .../chat/__tests__/mixedTestMessages.json | 42 +++++ .../base/chat/__tests__/utils.spec.ts | 175 ++++++++++++++++++ .../base/chat/chat/answer/index.tsx | 10 + web/app/components/base/chat/chat/type.ts | 3 + web/app/components/base/chat/types.ts | 4 + web/app/components/base/chat/utils.ts | 93 +++++++++- web/models/log.ts | 2 + 11 files changed, 453 insertions(+), 24 deletions(-) create mode 100644 web/app/components/base/chat/__tests__/branchedTestMessages.json create mode 100644 web/app/components/base/chat/__tests__/legacyTestMessages.json create mode 100644 web/app/components/base/chat/__tests__/mixedTestMessages.json create mode 100644 web/app/components/base/chat/__tests__/utils.spec.ts diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 9207314fc2132..3dcd88d1de31d 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -75,6 +75,7 @@ def format(self, value): "metadata": fields.Raw(attribute="message_metadata_dict"), "status": fields.String, "error": fields.String, + "parent_message_id": fields.String, } feedback_stat_fields = {"like": fields.Integer, "dislike": fields.Integer} diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 0bc118c46fdb4..c424e4f08fbae 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -41,6 +41,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) @@ -90,6 +91,7 @@ const getFormattedChatList = (messages: ChatMessage[], conversationId: string, t 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, @@ -144,6 +146,7 @@ const getFormattedChatList = (messages: ChatMessage[], conversationId: string, t return undefined })(), + parentMessageId: `question-${item.id}`, }) }) return newChatList @@ -170,48 +173,62 @@ function DetailPanel([]) + const [hasMore, setHasMore] = useState(true) const [varValues, setVarValues] = useState>({}) - const fetchData = async () => { + + const [allChatItems, setAllChatItems] = useState([]) + const [threadChatItems, setThreadChatItems] = useState([]) + + 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) } - const newItems = [...getFormattedChatList(messageRes.data, detail.id, timezone!, t('appLog.dateTimeFormat') as string), ...items] + setHasMore(messageRes.has_more) + + const newAllChatItems = [ + ...getFormattedChatList(messageRes.data, detail.id, timezone!, t('appLog.dateTimeFormat') as string), + ...allChatItems, + ] + console.log(newAllChatItems) + 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) + + 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 handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => { - setItems(items.map((item, i) => { + setAllChatItems(allChatItems.map((item, i) => { if (i === index - 1) { return { ...item, @@ -232,9 +249,9 @@ function DetailPanel { - setItems(items.map((item, i) => { + setAllChatItems(allChatItems.map((item, i) => { if (i === index - 1) { return { ...item, @@ -262,9 +279,9 @@ function DetailPanel { - setItems(items.map((item, i) => { + setAllChatItems(allChatItems.map((item, i) => { if (i === index) { return { ...item, @@ -274,12 +291,12 @@ function DetailPanel { 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' @@ -435,7 +452,7 @@ function DetailPanel
- : items.length < 8 + : threadChatItems.length < 8 ?
{t('appLog.detail.loading')}...
} @@ -503,7 +520,7 @@ function DetailPanel { + it('should build a tree from a list of chat items', () => { + const tree1 = buildChatItemTree(branchedTestMessages as IChatItem[]) + expect(tree1).toMatchObject([{ + id: 'question-1', + isAnswer: false, + parentMessageId: null, + children: [{ + id: '1', + isAnswer: true, + parentMessageId: 'question-1', + children: [ + { + id: 'question-2', + isAnswer: false, + parentMessageId: '1', + children: [{ + id: '2', + isAnswer: true, + parentMessageId: 'question-2', + children: [{ + id: 'question-3', + isAnswer: false, + parentMessageId: '2', + children: [{ + id: '3', + isAnswer: true, + parentMessageId: 'question-3', + children: [], + }], + }], + }], + }, + { + id: 'question-4', + isAnswer: false, + parentMessageId: '1', + children: [{ + id: '4', + isAnswer: true, + parentMessageId: 'question-4', + children: [], + }], + }], + }], + }]) + }) + + it('should be compatible with legacy chat items', () => { + const tree2 = buildChatItemTree(legacyTestMessages as IChatItem[]) + expect(tree2).toMatchObject([ + { + id: 'question-1', + isAnswer: false, + parentMessageId: '00000000-0000-0000-0000-000000000000', + children: [ + { + id: '1', + isAnswer: true, + parentMessageId: 'question-1', + children: [ + { + id: 'question-2', + isAnswer: false, + parentMessageId: '00000000-0000-0000-0000-000000000000', + children: [ + { + id: '2', + isAnswer: true, + parentMessageId: 'question-2', + children: [ + { + id: 'question-3', + isAnswer: false, + parentMessageId: '00000000-0000-0000-0000-000000000000', + children: [ + { + id: '3', + isAnswer: true, + parentMessageId: 'question-3', + children: [ + { + id: 'question-4', + isAnswer: false, + parentMessageId: '00000000-0000-0000-0000-000000000000', + children: [ + { + id: '4', + isAnswer: true, + parentMessageId: 'question-4', + children: [], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ]) + }) + + it('should build a tree from a list of mixed chat items', () => { + const tree3 = buildChatItemTree(mixedTestMessages as IChatItem[]) + expect(tree3).toMatchObject([ + { + id: 'question-1', + isAnswer: false, + parentMessageId: '00000000-0000-0000-0000-000000000000', + children: [ + { + id: '1', + isAnswer: true, + parentMessageId: 'question-1', + children: [ + { + id: 'question-2', + isAnswer: false, + parentMessageId: '00000000-0000-0000-0000-000000000000', + children: [ + { + id: '2', + isAnswer: true, + parentMessageId: 'question-2', + children: [ + { + id: 'question-3', + isAnswer: false, + parentMessageId: '2', + children: [ + { + id: '3', + isAnswer: true, + parentMessageId: 'question-3', + children: [], + }, + ], + }, + ], + }, + ], + }, + { + id: 'question-4', + isAnswer: false, + parentMessageId: '1', + children: [ + { + id: '4', + isAnswer: true, + parentMessageId: 'question-4', + children: [], + }, + ], + }, + ], + }, + ], + }, + ]) + }) +}) diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 8d354cf30bca3..d29e63a91b088 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -23,6 +23,7 @@ import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal import type { Emoji } from '@/app/components/tools/types' import type { AppData } from '@/models/share' import AnswerIcon from '@/app/components/base/answer-icon' +import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' type AnswerProps = { item: ChatItem @@ -192,6 +193,15 @@ const Answer: FC = ({ ) } +
+ + 2 / 2 + +
diff --git a/web/app/components/base/chat/chat/type.ts b/web/app/components/base/chat/chat/type.ts index 16ccff4d4d223..d6210d3918a5d 100644 --- a/web/app/components/base/chat/chat/type.ts +++ b/web/app/components/base/chat/chat/type.ts @@ -95,6 +95,9 @@ export type IChatItem = { // for agent log conversationId?: string input?: any + parentMessageId?: string | null + siblingCount?: number + siblingIndex?: number } export type Metadata = { diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts index ec4c39743be10..2dc31d1ee9e99 100644 --- a/web/app/components/base/chat/types.ts +++ b/web/app/components/base/chat/types.ts @@ -63,6 +63,10 @@ export type ChatItem = IChatItem & { conversationId?: string } +export type ChatItemInTree = { + children?: ChatItemInTree[] +} & IChatItem + export type OnSend = (message: string, files?: VisionFile[], last_answer?: ChatItem) => void export type OnRegenerate = (chatItem: ChatItem) => void diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index e851c4c4633fb..a9a696e4b6596 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -1,6 +1,7 @@ import { addFileInfos, sortAgentSorts } from '../../tools/utils' import { UUID_NIL } from './constants' -import type { ChatItem } from './types' +import type { IChatItem } from './chat/type' +import type { ChatItem, ChatItemInTree } from './types' async function decodeBase64AndDecompress(base64String: string) { const binaryString = atob(base64String) @@ -69,7 +70,97 @@ function getPrevChatList(fetchedMessages: any[]) { return ret.reverse() } +function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { + const map = new Map() + let rootNodes: ChatItemInTree[] = [] + const childrenCount: Record = {} + + allMessages.forEach((item) => { + map.set(item.id, { + ...item, + children: [], + siblingCount: 0, + siblingIndex: 0, + }) + }) + + for (let i = 0; i < allMessages.length; i += 2) { + const questionItem = allMessages[i + 1]! + const answerItem = allMessages[i]! + + const parentMessageId = questionItem.parentMessageId + + const questionItemInTree = map.get(questionItem.id)! + const answerItemInTree = map.get(answerItem.id)! + + questionItemInTree.children!.unshift(answerItemInTree) + questionItemInTree.siblingCount = (questionItemInTree.siblingCount || 0) + 1 + answerItemInTree.siblingCount = (answerItemInTree.siblingCount || 0) + 1 + + if (!parentMessageId) + rootNodes.unshift(questionItemInTree) + else if (parentMessageId && parentMessageId !== UUID_NIL) + map.get(parentMessageId)?.children!.unshift(questionItemInTree) + } + + // legacy message compat + allMessages.forEach((item) => { + // if a question message's `parentMessageId` is UUID_NIL, + // then this message pair is legacy, and should be root node for the tree + if (!item.isAnswer && item.parentMessageId === UUID_NIL) { + const curr = map.get(item.id) + if (curr) { + const correspondingAnswer = curr.children![0] + correspondingAnswer.children?.unshift(...rootNodes) + rootNodes = [curr] + } + } + }) + + return rootNodes +} + +function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): ChatItemInTree[] { + let ret: ChatItemInTree[] = [] + let targetNode: ChatItemInTree | undefined + + // find path to the target message + const stack = tree.map(rootNode => ({ + node: rootNode, + path: [rootNode], + })) + while (stack.length > 0) { + const { node, path } = stack.pop()! + if (node.id === targetMessageId) { + targetNode = node + ret = path + break + } + if (node.children) { + stack.push(...node.children.map(child => ({ + node: child, + path: [...path, child], + }))) + } + } + + // append all descendant messages to the path + if (targetNode) { + const stack = [targetNode] + while (stack.length > 0) { + const node = stack.pop()! + if (node !== targetNode) + ret.push(node) + if (node.children?.length) + stack.push(node.children.at(-1)!) + } + } + return ret +} + export { getProcessedInputsFromUrlParams, getPrevChatList, + buildChatItemTree, + getThreadMessages, } diff --git a/web/models/log.ts b/web/models/log.ts index 6f8ebb1a78f02..5b6e71ca9e763 100644 --- a/web/models/log.ts +++ b/web/models/log.ts @@ -106,6 +106,8 @@ export type MessageContent = { metadata: Metadata agent_thoughts: any[] // TODO workflow_run_id: string + is_regenerated: boolean + parent_message_id: string | null } export type CompletionConversationGeneralDetail = { From 8cb70e7226fb66c92e5742a76cda036928b5245d Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 10 Sep 2024 16:35:23 +0800 Subject: [PATCH 25/44] staging --- .../chat/__tests__/branchedTestMessages.json | 30 ++++---- .../chat/__tests__/legacyTestMessages.json | 26 +++---- .../chat/__tests__/mixedTestMessages.json | 34 ++++----- .../base/chat/__tests__/utils.spec.ts | 7 ++ web/app/components/base/chat/utils.ts | 76 ++++++++++--------- 5 files changed, 92 insertions(+), 81 deletions(-) diff --git a/web/app/components/base/chat/__tests__/branchedTestMessages.json b/web/app/components/base/chat/__tests__/branchedTestMessages.json index f9fdcd930cb16..30e0a82cb5b4a 100644 --- a/web/app/components/base/chat/__tests__/branchedTestMessages.json +++ b/web/app/components/base/chat/__tests__/branchedTestMessages.json @@ -1,18 +1,23 @@ [ { - "id": "4", + "id": "question-1", + "isAnswer": false, + "parentMessageId": null + }, + { + "id": "1", "isAnswer": true, - "parentMessageId": "question-4" + "parentMessageId": "question-1" }, { - "id": "question-4", + "id": "question-2", "isAnswer": false, "parentMessageId": "1" }, { - "id": "3", + "id": "2", "isAnswer": true, - "parentMessageId": "question-3" + "parentMessageId": "question-2" }, { "id": "question-3", @@ -20,23 +25,18 @@ "parentMessageId": "2" }, { - "id": "2", + "id": "3", "isAnswer": true, - "parentMessageId": "question-2" + "parentMessageId": "question-3" }, { - "id": "question-2", + "id": "question-4", "isAnswer": false, "parentMessageId": "1" }, { - "id": "1", + "id": "4", "isAnswer": true, - "parentMessageId": "question-1" - }, - { - "id": "question-1", - "isAnswer": false, - "parentMessageId": null + "parentMessageId": "question-4" } ] diff --git a/web/app/components/base/chat/__tests__/legacyTestMessages.json b/web/app/components/base/chat/__tests__/legacyTestMessages.json index 0b2058c065397..2dab58985adf1 100644 --- a/web/app/components/base/chat/__tests__/legacyTestMessages.json +++ b/web/app/components/base/chat/__tests__/legacyTestMessages.json @@ -1,21 +1,16 @@ [ { - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4" - }, - { - "id": "question-4", + "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000" }, { - "id": "3", + "id": "1", "isAnswer": true, - "parentMessageId": "question-3" + "parentMessageId": "question-1" }, { - "id": "question-3", + "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000" }, @@ -25,18 +20,23 @@ "parentMessageId": "question-2" }, { - "id": "question-2", + "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000" }, { - "id": "1", + "id": "3", "isAnswer": true, - "parentMessageId": "question-1" + "parentMessageId": "question-3" }, { - "id": "question-1", + "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000" + }, + { + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4" } ] diff --git a/web/app/components/base/chat/__tests__/mixedTestMessages.json b/web/app/components/base/chat/__tests__/mixedTestMessages.json index 9d6000ad49636..14789d9518416 100644 --- a/web/app/components/base/chat/__tests__/mixedTestMessages.json +++ b/web/app/components/base/chat/__tests__/mixedTestMessages.json @@ -1,23 +1,18 @@ [ { - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4" - }, - { - "id": "question-4", + "id": "question-1", "isAnswer": false, - "parentMessageId": "1" + "parentMessageId": "00000000-0000-0000-0000-000000000000" }, { - "id": "3", + "id": "1", "isAnswer": true, - "parentMessageId": "question-3" + "parentMessageId": "question-1" }, { - "id": "question-3", + "id": "question-2", "isAnswer": false, - "parentMessageId": "2" + "parentMessageId": "00000000-0000-0000-0000-000000000000" }, { "id": "2", @@ -25,18 +20,23 @@ "parentMessageId": "question-2" }, { - "id": "question-2", + "id": "question-3", "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000" + "parentMessageId": "2" }, { - "id": "1", + "id": "3", "isAnswer": true, - "parentMessageId": "question-1" + "parentMessageId": "question-3" }, { - "id": "question-1", + "id": "question-4", "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000" + "parentMessageId": "1" + }, + { + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4" } ] diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts index 9c6d8ed90cdf4..d2062cd220e7f 100644 --- a/web/app/components/base/chat/__tests__/utils.spec.ts +++ b/web/app/components/base/chat/__tests__/utils.spec.ts @@ -11,27 +11,33 @@ describe('buildChatItemTree', () => { id: 'question-1', isAnswer: false, parentMessageId: null, + siblingIndex: 0, children: [{ id: '1', isAnswer: true, parentMessageId: 'question-1', + siblingIndex: 0, children: [ { id: 'question-2', isAnswer: false, parentMessageId: '1', + siblingIndex: 0, children: [{ id: '2', isAnswer: true, parentMessageId: 'question-2', + siblingIndex: 0, children: [{ id: 'question-3', isAnswer: false, parentMessageId: '2', + siblingIndex: 0, children: [{ id: '3', isAnswer: true, parentMessageId: 'question-3', + siblingIndex: 0, children: [], }], }], @@ -41,6 +47,7 @@ describe('buildChatItemTree', () => { id: 'question-4', isAnswer: false, parentMessageId: '1', + siblingIndex: 1, children: [{ id: '4', isAnswer: true, diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index a9a696e4b6596..59bdc67573797 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -71,51 +71,55 @@ function getPrevChatList(fetchedMessages: any[]) { } function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { - const map = new Map() + const map: Record = {} let rootNodes: ChatItemInTree[] = [] const childrenCount: Record = {} - allMessages.forEach((item) => { - map.set(item.id, { - ...item, - children: [], - siblingCount: 0, - siblingIndex: 0, - }) - }) + const legacyQuestions: ChatItemInTree[] = [] for (let i = 0; i < allMessages.length; i += 2) { - const questionItem = allMessages[i + 1]! - const answerItem = allMessages[i]! - - const parentMessageId = questionItem.parentMessageId - - const questionItemInTree = map.get(questionItem.id)! - const answerItemInTree = map.get(answerItem.id)! - - questionItemInTree.children!.unshift(answerItemInTree) - questionItemInTree.siblingCount = (questionItemInTree.siblingCount || 0) + 1 - answerItemInTree.siblingCount = (answerItemInTree.siblingCount || 0) + 1 + const question = allMessages[i]! + const answer = allMessages[i + 1]! + + // Process question + const parentId = question.parentMessageId ?? '' + childrenCount[parentId] = (childrenCount[parentId] || 0) + 1 + const questionNode: ChatItemInTree = { + ...question, + children: [], + siblingIndex: childrenCount[parentId] - 1, + } + map[question.id] = questionNode - if (!parentMessageId) - rootNodes.unshift(questionItemInTree) - else if (parentMessageId && parentMessageId !== UUID_NIL) - map.get(parentMessageId)?.children!.unshift(questionItemInTree) + // Process answer + childrenCount[question.id] = 1 + const answerNode: ChatItemInTree = { + ...answer, + children: [], + siblingIndex: 0, + } + map[answer.id] = answerNode + + // Connect question and answer + questionNode.children!.push(answerNode) + + // Connect to parent or add to root + if (!parentId) + rootNodes.push(questionNode) + else if (parentId !== UUID_NIL) + map[parentId]?.children!.push(questionNode) + else + legacyQuestions.unshift(questionNode) } // legacy message compat - allMessages.forEach((item) => { - // if a question message's `parentMessageId` is UUID_NIL, - // then this message pair is legacy, and should be root node for the tree - if (!item.isAnswer && item.parentMessageId === UUID_NIL) { - const curr = map.get(item.id) - if (curr) { - const correspondingAnswer = curr.children![0] - correspondingAnswer.children?.unshift(...rootNodes) - rootNodes = [curr] - } - } - }) + for (const legacyQuestion of legacyQuestions) { + const answer = legacyQuestion.children![0]! + const questionNode = map[legacyQuestion.id] + const answerNode = map[answer.id] + answerNode.children?.unshift(...rootNodes) + rootNodes = [questionNode] + } return rootNodes } From 8a28713feb5bef4bac4f3dfbd572a3a8f9e70420 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 10 Sep 2024 17:35:23 +0800 Subject: [PATCH 26/44] finish --- .../base/chat/__tests__/utils.spec.ts | 130 ++++++++++-------- web/app/components/base/chat/utils.ts | 42 +++--- 2 files changed, 95 insertions(+), 77 deletions(-) diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts index d2062cd220e7f..d6198c82d4eb9 100644 --- a/web/app/components/base/chat/__tests__/utils.spec.ts +++ b/web/app/components/base/chat/__tests__/utils.spec.ts @@ -66,41 +66,49 @@ describe('buildChatItemTree', () => { id: 'question-1', isAnswer: false, parentMessageId: '00000000-0000-0000-0000-000000000000', + siblingIndex: 0, children: [ { id: '1', isAnswer: true, parentMessageId: 'question-1', + siblingIndex: 0, children: [ { id: 'question-2', isAnswer: false, parentMessageId: '00000000-0000-0000-0000-000000000000', + siblingIndex: 0, children: [ { id: '2', isAnswer: true, parentMessageId: 'question-2', + siblingIndex: 0, children: [ { id: 'question-3', isAnswer: false, parentMessageId: '00000000-0000-0000-0000-000000000000', + siblingIndex: 0, children: [ { id: '3', isAnswer: true, parentMessageId: 'question-3', + siblingIndex: 0, children: [ { id: 'question-4', isAnswer: false, parentMessageId: '00000000-0000-0000-0000-000000000000', + siblingIndex: 0, children: [ { id: '4', isAnswer: true, parentMessageId: 'question-4', + siblingIndex: 0, children: [], }, ], @@ -122,61 +130,71 @@ describe('buildChatItemTree', () => { it('should build a tree from a list of mixed chat items', () => { const tree3 = buildChatItemTree(mixedTestMessages as IChatItem[]) - expect(tree3).toMatchObject([ - { - id: 'question-1', - isAnswer: false, - parentMessageId: '00000000-0000-0000-0000-000000000000', - children: [ - { - id: '1', - isAnswer: true, - parentMessageId: 'question-1', - children: [ - { - id: 'question-2', - isAnswer: false, - parentMessageId: '00000000-0000-0000-0000-000000000000', - children: [ - { - id: '2', - isAnswer: true, - parentMessageId: 'question-2', - children: [ - { - id: 'question-3', - isAnswer: false, - parentMessageId: '2', - children: [ - { - id: '3', - isAnswer: true, - parentMessageId: 'question-3', - children: [], - }, - ], - }, - ], - }, - ], - }, - { - id: 'question-4', - isAnswer: false, - parentMessageId: '1', - children: [ - { - id: '4', - isAnswer: true, - parentMessageId: 'question-4', - children: [], - }, - ], - }, - ], - }, - ], - }, - ]) + console.log(JSON.stringify(tree3, null, 2)) + + // expect(tree3).toMatchObject([ + // { + // id: 'question-1', + // isAnswer: false, + // parentMessageId: '00000000-0000-0000-0000-000000000000', + // siblingIndex: 0, + // children: [ + // { + // id: '1', + // isAnswer: true, + // parentMessageId: 'question-1', + // siblingIndex: 0, + // children: [ + // { + // id: 'question-2', + // isAnswer: false, + // parentMessageId: '00000000-0000-0000-0000-000000000000', + // siblingIndex: 0, + // children: [ + // { + // id: '2', + // isAnswer: true, + // parentMessageId: 'question-2', + // siblingIndex: 0, + // children: [ + // { + // id: 'question-3', + // isAnswer: false, + // parentMessageId: '2', + // siblingIndex: 0, + // children: [ + // { + // id: '3', + // isAnswer: true, + // parentMessageId: 'question-3', + // siblingIndex: 0, + // children: [], + // }, + // ], + // }, + // ], + // }, + // ], + // }, + // { + // id: 'question-4', + // isAnswer: false, + // parentMessageId: '1', + // siblingIndex: 1, + // children: [ + // { + // id: '4', + // isAnswer: true, + // parentMessageId: 'question-4', + // siblingIndex: 0, + // children: [], + // }, + // ], + // }, + // ], + // }, + // ], + // }, + // ]) }) }) diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index 59bdc67573797..393f997c02e8f 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -72,22 +72,23 @@ function getPrevChatList(fetchedMessages: any[]) { function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { const map: Record = {} - let rootNodes: ChatItemInTree[] = [] + const rootNodes: ChatItemInTree[] = [] const childrenCount: Record = {} - const legacyQuestions: ChatItemInTree[] = [] - + let lastAppendedLegacyAnswer: ChatItemInTree | null = null for (let i = 0; i < allMessages.length; i += 2) { const question = allMessages[i]! const answer = allMessages[i + 1]! + const parentMessageId = question.parentMessageId ?? '' + const isLegacy = parentMessageId === UUID_NIL + // Process question - const parentId = question.parentMessageId ?? '' - childrenCount[parentId] = (childrenCount[parentId] || 0) + 1 + childrenCount[parentMessageId] = (childrenCount[parentMessageId] || 0) + 1 const questionNode: ChatItemInTree = { ...question, children: [], - siblingIndex: childrenCount[parentId] - 1, + siblingIndex: isLegacy ? 0 : childrenCount[parentMessageId] - 1, } map[question.id] = questionNode @@ -103,22 +104,21 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { // Connect question and answer questionNode.children!.push(answerNode) - // Connect to parent or add to root - if (!parentId) - rootNodes.push(questionNode) - else if (parentId !== UUID_NIL) - map[parentId]?.children!.push(questionNode) - else - legacyQuestions.unshift(questionNode) - } + // Append to parent or add to root + if (isLegacy) { + if (!lastAppendedLegacyAnswer) + rootNodes.push(questionNode) + else + lastAppendedLegacyAnswer.children!.push(questionNode) - // legacy message compat - for (const legacyQuestion of legacyQuestions) { - const answer = legacyQuestion.children![0]! - const questionNode = map[legacyQuestion.id] - const answerNode = map[answer.id] - answerNode.children?.unshift(...rootNodes) - rootNodes = [questionNode] + lastAppendedLegacyAnswer = answerNode + } + else { + if (!parentMessageId) + rootNodes.push(questionNode) + else + map[parentMessageId]?.children!.push(questionNode) + } } return rootNodes From dc54187f1cf2c8c72acc019f8ab2ed2dede97ff4 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 10 Sep 2024 17:58:36 +0800 Subject: [PATCH 27/44] fix test and use snapshot --- .../__snapshots__/utils.spec.ts.snap | 367 ++++++++++++++++++ .../__tests__/multiRootNodesMessages.json | 52 +++ .../multiRootNodesWithLegacyTestMessages.json | 52 +++ .../base/chat/__tests__/utils.spec.ts | 194 +-------- web/app/components/base/chat/utils.ts | 6 +- 5 files changed, 489 insertions(+), 182 deletions(-) create mode 100644 web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap create mode 100644 web/app/components/base/chat/__tests__/multiRootNodesMessages.json create mode 100644 web/app/components/base/chat/__tests__/multiRootNodesWithLegacyTestMessages.json diff --git a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap new file mode 100644 index 0000000000000..ca5ed886050e2 --- /dev/null +++ b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap @@ -0,0 +1,367 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`buildChatItemTree should be compatible with legacy chat items 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4", + "siblingIndex": 0, + }, + ], + "id": "question-4", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, + ], + "id": "3", + "isAnswer": true, + "parentMessageId": "question-3", + "siblingIndex": 0, + }, + ], + "id": "question-3", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, + ], + "id": "2", + "isAnswer": true, + "parentMessageId": "question-2", + "siblingIndex": 0, + }, + ], + "id": "question-2", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, + ], + "id": "1", + "isAnswer": true, + "parentMessageId": "question-1", + "siblingIndex": 0, + }, + ], + "id": "question-1", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, +] +`; + +exports[`buildChatItemTree should build a tree from a list of chat items 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "id": "3", + "isAnswer": true, + "parentMessageId": "question-3", + "siblingIndex": 0, + }, + ], + "id": "question-3", + "isAnswer": false, + "parentMessageId": "2", + "siblingIndex": 0, + }, + ], + "id": "2", + "isAnswer": true, + "parentMessageId": "question-2", + "siblingIndex": 0, + }, + ], + "id": "question-2", + "isAnswer": false, + "parentMessageId": "1", + "siblingIndex": 0, + }, + { + "children": [ + { + "children": [], + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4", + "siblingIndex": 0, + }, + ], + "id": "question-4", + "isAnswer": false, + "parentMessageId": "1", + "siblingIndex": 1, + }, + ], + "id": "1", + "isAnswer": true, + "parentMessageId": "question-1", + "siblingIndex": 0, + }, + ], + "id": "question-1", + "isAnswer": false, + "parentMessageId": null, + "siblingIndex": 0, + }, +] +`; + +exports[`buildChatItemTree should build a tree from a list of mixed chat items 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "id": "3", + "isAnswer": true, + "parentMessageId": "question-3", + "siblingIndex": 0, + }, + ], + "id": "question-3", + "isAnswer": false, + "parentMessageId": "2", + "siblingIndex": 0, + }, + ], + "id": "2", + "isAnswer": true, + "parentMessageId": "question-2", + "siblingIndex": 0, + }, + ], + "id": "question-2", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, + { + "children": [ + { + "children": [], + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4", + "siblingIndex": 0, + }, + ], + "id": "question-4", + "isAnswer": false, + "parentMessageId": "1", + "siblingIndex": 1, + }, + ], + "id": "1", + "isAnswer": true, + "parentMessageId": "question-1", + "siblingIndex": 0, + }, + ], + "id": "question-1", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, +] +`; + +exports[`buildChatItemTree should build a tree from a list of multi root nodes chat items 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "id": "3", + "isAnswer": true, + "parentMessageId": "question-3", + "siblingIndex": 0, + }, + ], + "id": "question-3", + "isAnswer": false, + "parentMessageId": "2", + "siblingIndex": 0, + }, + ], + "id": "2", + "isAnswer": true, + "parentMessageId": "question-2", + "siblingIndex": 0, + }, + ], + "id": "question-2", + "isAnswer": false, + "parentMessageId": "1", + "siblingIndex": 0, + }, + { + "children": [ + { + "children": [], + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4", + "siblingIndex": 0, + }, + ], + "id": "question-4", + "isAnswer": false, + "parentMessageId": "1", + "siblingIndex": 1, + }, + ], + "id": "1", + "isAnswer": true, + "parentMessageId": "question-1", + "siblingIndex": 0, + }, + ], + "id": "question-1", + "isAnswer": false, + "parentMessageId": null, + "siblingIndex": 0, + }, + { + "children": [ + { + "children": [], + "id": "5", + "isAnswer": true, + "parentMessageId": "question-5", + "siblingIndex": 0, + }, + ], + "id": "question-5", + "isAnswer": false, + "parentMessageId": null, + "siblingIndex": 1, + }, +] +`; + +exports[`buildChatItemTree should build a tree from a list of multi root nodes chat items with legacy chat items 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "id": "3", + "isAnswer": true, + "parentMessageId": "question-3", + "siblingIndex": 0, + }, + ], + "id": "question-3", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, + ], + "id": "2", + "isAnswer": true, + "parentMessageId": "question-2", + "siblingIndex": 0, + }, + ], + "id": "question-2", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, + { + "children": [ + { + "children": [], + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4", + "siblingIndex": 0, + }, + ], + "id": "question-4", + "isAnswer": false, + "parentMessageId": "1", + "siblingIndex": 1, + }, + ], + "id": "1", + "isAnswer": true, + "parentMessageId": "question-1", + "siblingIndex": 0, + }, + ], + "id": "question-1", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000", + "siblingIndex": 0, + }, + { + "children": [ + { + "children": [], + "id": "5", + "isAnswer": true, + "parentMessageId": "question-5", + "siblingIndex": 0, + }, + ], + "id": "question-5", + "isAnswer": false, + "parentMessageId": null, + "siblingIndex": 1, + }, +] +`; diff --git a/web/app/components/base/chat/__tests__/multiRootNodesMessages.json b/web/app/components/base/chat/__tests__/multiRootNodesMessages.json new file mode 100644 index 0000000000000..782ccb7f94955 --- /dev/null +++ b/web/app/components/base/chat/__tests__/multiRootNodesMessages.json @@ -0,0 +1,52 @@ +[ + { + "id": "question-1", + "isAnswer": false, + "parentMessageId": null + }, + { + "id": "1", + "isAnswer": true, + "parentMessageId": "question-1" + }, + { + "id": "question-2", + "isAnswer": false, + "parentMessageId": "1" + }, + { + "id": "2", + "isAnswer": true, + "parentMessageId": "question-2" + }, + { + "id": "question-3", + "isAnswer": false, + "parentMessageId": "2" + }, + { + "id": "3", + "isAnswer": true, + "parentMessageId": "question-3" + }, + { + "id": "question-4", + "isAnswer": false, + "parentMessageId": "1" + }, + { + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4" + }, + { + "id": "question-5", + "isAnswer": false, + "parentMessageId": null + }, + { + "id": "5", + "isAnswer": true, + "parentMessageId": "question-5" + } +] diff --git a/web/app/components/base/chat/__tests__/multiRootNodesWithLegacyTestMessages.json b/web/app/components/base/chat/__tests__/multiRootNodesWithLegacyTestMessages.json new file mode 100644 index 0000000000000..5eadc726e5368 --- /dev/null +++ b/web/app/components/base/chat/__tests__/multiRootNodesWithLegacyTestMessages.json @@ -0,0 +1,52 @@ +[ + { + "id": "question-1", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000" + }, + { + "id": "1", + "isAnswer": true, + "parentMessageId": "question-1" + }, + { + "id": "question-2", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000" + }, + { + "id": "2", + "isAnswer": true, + "parentMessageId": "question-2" + }, + { + "id": "question-3", + "isAnswer": false, + "parentMessageId": "00000000-0000-0000-0000-000000000000" + }, + { + "id": "3", + "isAnswer": true, + "parentMessageId": "question-3" + }, + { + "id": "question-4", + "isAnswer": false, + "parentMessageId": "1" + }, + { + "id": "4", + "isAnswer": true, + "parentMessageId": "question-4" + }, + { + "id": "question-5", + "isAnswer": false, + "parentMessageId": null + }, + { + "id": "5", + "isAnswer": true, + "parentMessageId": "question-5" + } +] diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts index d6198c82d4eb9..70194307ef73d 100644 --- a/web/app/components/base/chat/__tests__/utils.spec.ts +++ b/web/app/components/base/chat/__tests__/utils.spec.ts @@ -3,198 +3,32 @@ import { buildChatItemTree } from '../utils' import branchedTestMessages from './branchedTestMessages.json' import legacyTestMessages from './legacyTestMessages.json' import mixedTestMessages from './mixedTestMessages.json' +import multiRootNodesMessages from './multiRootNodesMessages.json' +import multiRootNodesWithLegacyTestMessages from './multiRootNodesWithLegacyTestMessages.json' describe('buildChatItemTree', () => { it('should build a tree from a list of chat items', () => { const tree1 = buildChatItemTree(branchedTestMessages as IChatItem[]) - expect(tree1).toMatchObject([{ - id: 'question-1', - isAnswer: false, - parentMessageId: null, - siblingIndex: 0, - children: [{ - id: '1', - isAnswer: true, - parentMessageId: 'question-1', - siblingIndex: 0, - children: [ - { - id: 'question-2', - isAnswer: false, - parentMessageId: '1', - siblingIndex: 0, - children: [{ - id: '2', - isAnswer: true, - parentMessageId: 'question-2', - siblingIndex: 0, - children: [{ - id: 'question-3', - isAnswer: false, - parentMessageId: '2', - siblingIndex: 0, - children: [{ - id: '3', - isAnswer: true, - parentMessageId: 'question-3', - siblingIndex: 0, - children: [], - }], - }], - }], - }, - { - id: 'question-4', - isAnswer: false, - parentMessageId: '1', - siblingIndex: 1, - children: [{ - id: '4', - isAnswer: true, - parentMessageId: 'question-4', - children: [], - }], - }], - }], - }]) + expect(tree1).toMatchSnapshot() }) it('should be compatible with legacy chat items', () => { const tree2 = buildChatItemTree(legacyTestMessages as IChatItem[]) - expect(tree2).toMatchObject([ - { - id: 'question-1', - isAnswer: false, - parentMessageId: '00000000-0000-0000-0000-000000000000', - siblingIndex: 0, - children: [ - { - id: '1', - isAnswer: true, - parentMessageId: 'question-1', - siblingIndex: 0, - children: [ - { - id: 'question-2', - isAnswer: false, - parentMessageId: '00000000-0000-0000-0000-000000000000', - siblingIndex: 0, - children: [ - { - id: '2', - isAnswer: true, - parentMessageId: 'question-2', - siblingIndex: 0, - children: [ - { - id: 'question-3', - isAnswer: false, - parentMessageId: '00000000-0000-0000-0000-000000000000', - siblingIndex: 0, - children: [ - { - id: '3', - isAnswer: true, - parentMessageId: 'question-3', - siblingIndex: 0, - children: [ - { - id: 'question-4', - isAnswer: false, - parentMessageId: '00000000-0000-0000-0000-000000000000', - siblingIndex: 0, - children: [ - { - id: '4', - isAnswer: true, - parentMessageId: 'question-4', - siblingIndex: 0, - children: [], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ]) + expect(tree2).toMatchSnapshot() }) it('should build a tree from a list of mixed chat items', () => { const tree3 = buildChatItemTree(mixedTestMessages as IChatItem[]) - console.log(JSON.stringify(tree3, null, 2)) + expect(tree3).toMatchSnapshot() + }) + + it('should build a tree from a list of multi root nodes chat items', () => { + const tree4 = buildChatItemTree(multiRootNodesMessages as IChatItem[]) + expect(tree4).toMatchSnapshot() + }) - // expect(tree3).toMatchObject([ - // { - // id: 'question-1', - // isAnswer: false, - // parentMessageId: '00000000-0000-0000-0000-000000000000', - // siblingIndex: 0, - // children: [ - // { - // id: '1', - // isAnswer: true, - // parentMessageId: 'question-1', - // siblingIndex: 0, - // children: [ - // { - // id: 'question-2', - // isAnswer: false, - // parentMessageId: '00000000-0000-0000-0000-000000000000', - // siblingIndex: 0, - // children: [ - // { - // id: '2', - // isAnswer: true, - // parentMessageId: 'question-2', - // siblingIndex: 0, - // children: [ - // { - // id: 'question-3', - // isAnswer: false, - // parentMessageId: '2', - // siblingIndex: 0, - // children: [ - // { - // id: '3', - // isAnswer: true, - // parentMessageId: 'question-3', - // siblingIndex: 0, - // children: [], - // }, - // ], - // }, - // ], - // }, - // ], - // }, - // { - // id: 'question-4', - // isAnswer: false, - // parentMessageId: '1', - // siblingIndex: 1, - // children: [ - // { - // id: '4', - // isAnswer: true, - // parentMessageId: 'question-4', - // siblingIndex: 0, - // children: [], - // }, - // ], - // }, - // ], - // }, - // ], - // }, - // ]) + it('should build a tree from a list of multi root nodes chat items with legacy chat items', () => { + const tree5 = buildChatItemTree(multiRootNodesWithLegacyTestMessages as IChatItem[]) + expect(tree5).toMatchSnapshot() }) }) diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index 393f997c02e8f..418e8ed5775c6 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -80,8 +80,10 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { const question = allMessages[i]! const answer = allMessages[i + 1]! - const parentMessageId = question.parentMessageId ?? '' - const isLegacy = parentMessageId === UUID_NIL + const isLegacy = question.parentMessageId === UUID_NIL + const parentMessageId = isLegacy + ? (lastAppendedLegacyAnswer?.id || '') + : (question.parentMessageId || '') // Process question childrenCount[parentMessageId] = (childrenCount[parentMessageId] || 0) + 1 From 9aed5255599c84280e3b85f5c8e70da2e79d0a36 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 11 Sep 2024 13:44:46 +0800 Subject: [PATCH 28/44] getThreadMessages --- web/app/components/app/log/list.tsx | 1 - .../__snapshots__/utils.spec.ts.snap | 1323 ++++++++++++++++- .../base/chat/__tests__/utils.spec.ts | 45 +- web/app/components/base/chat/utils.ts | 24 +- 4 files changed, 1354 insertions(+), 39 deletions(-) diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index c424e4f08fbae..b0c2a8d6be8f2 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -205,7 +205,6 @@ function DetailPanel { - it('should build a tree from a list of chat items', () => { +describe('build chat item tree and get thread messages', () => { + it('get thread messages', () => { const tree1 = buildChatItemTree(branchedTestMessages as IChatItem[]) - expect(tree1).toMatchSnapshot() + expect(tree1).toMatchSnapshot('tree1') + + const threadMessages1_1 = getThreadMessages(tree1) + expect(threadMessages1_1).toMatchSnapshot('threadMessages1_1') + + const threadMessages1_2 = getThreadMessages(tree1, '3') + expect(threadMessages1_2).toMatchSnapshot('threadMessages1_2') }) - it('should be compatible with legacy chat items', () => { + it('get thread messages with legacy chat items', () => { const tree2 = buildChatItemTree(legacyTestMessages as IChatItem[]) - expect(tree2).toMatchSnapshot() + expect(tree2).toMatchSnapshot('tree2') + + const threadMessages2 = getThreadMessages(tree2) + expect(threadMessages2).toMatchSnapshot('threadMessages2') }) - it('should build a tree from a list of mixed chat items', () => { + it('get thread messages with mixed chat items', () => { const tree3 = buildChatItemTree(mixedTestMessages as IChatItem[]) - expect(tree3).toMatchSnapshot() + expect(tree3).toMatchSnapshot('tree3') + + const threadMessages3_1 = getThreadMessages(tree3) + expect(threadMessages3_1).toMatchSnapshot('threadMessages3_1') + + const threadMessages3_2 = getThreadMessages(tree3, '3') + expect(threadMessages3_2).toMatchSnapshot('threadMessages3_2') }) - it('should build a tree from a list of multi root nodes chat items', () => { + it('get thread messages with multi root nodes chat items', () => { const tree4 = buildChatItemTree(multiRootNodesMessages as IChatItem[]) - expect(tree4).toMatchSnapshot() + expect(tree4).toMatchSnapshot('tree4') + + const threadMessages4 = getThreadMessages(tree4) + expect(threadMessages4).toMatchSnapshot('threadMessages4') }) - it('should build a tree from a list of multi root nodes chat items with legacy chat items', () => { + it('get thread messages with multi root nodes chat items with legacy chat items', () => { const tree5 = buildChatItemTree(multiRootNodesWithLegacyTestMessages as IChatItem[]) - expect(tree5).toMatchSnapshot() + expect(tree5).toMatchSnapshot('tree5') + + const threadMessages5 = getThreadMessages(tree5) + expect(threadMessages5).toMatchSnapshot('threadMessages5') }) }) diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index 418e8ed5775c6..946aacd7eae08 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -137,16 +137,21 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch })) while (stack.length > 0) { const { node, path } = stack.pop()! - if (node.id === targetMessageId) { + if ( + node.id === targetMessageId + || (!targetMessageId && !node.children?.length && !stack.length) // if targetMessageId is not provided, we use the last message in the tree as the target + ) { targetNode = node - ret = path + ret = path.map((n, i) => ({ ...n, siblingCount: i === 0 ? tree.length : path[i - 1].children!.length })) break } if (node.children) { - stack.push(...node.children.map(child => ({ - node: child, - path: [...path, child], - }))) + for (let i = node.children.length - 1; i >= 0; i--) { + stack.push({ + node: node.children[i], + path: [...path, node.children[i]], + }) + } } } @@ -157,10 +162,13 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch const node = stack.pop()! if (node !== targetNode) ret.push(node) - if (node.children?.length) - stack.push(node.children.at(-1)!) + if (node.children?.length) { + const lastDescendant = node.children.at(-1)! + stack.push({ ...lastDescendant, siblingCount: node.children.length }) + } } } + return ret } From a7b5742b6a85899ad44bcc8b04702e809c9ac63e Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 11 Sep 2024 16:35:50 +0800 Subject: [PATCH 29/44] sibling switching works --- web/app/components/app/log/list.tsx | 9 + .../__snapshots__/utils.spec.ts.snap | 178 ++++-------------- .../base/chat/chat/answer/index.tsx | 20 +- web/app/components/base/chat/chat/index.tsx | 3 + web/app/components/base/chat/chat/type.ts | 2 + web/app/components/base/chat/utils.ts | 17 +- 6 files changed, 77 insertions(+), 152 deletions(-) diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index b0c2a8d6be8f2..270e31b291117 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -16,6 +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 type { ChatItemInTree } from '../../base/chat/types' import s from './style.module.css' import VarPanel from './var-panel' import cn from '@/utils/classnames' @@ -178,6 +179,7 @@ function DetailPanel>({}) const [allChatItems, setAllChatItems] = useState([]) + const [chatItemTree, setChatItemTree] = useState([]) const [threadChatItems, setThreadChatItems] = useState([]) const fetchData = useCallback(async () => { @@ -218,6 +220,7 @@ function DetailPanel { + setThreadChatItems(getThreadMessages(chatItemTree, siblingMessageId)) + }, [chatItemTree]) + const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => { setAllChatItems(allChatItems.map((item, i) => { if (i === index - 1) { @@ -474,6 +481,7 @@ function DetailPanel
:
diff --git a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap index a175f8f2b5403..f82934bcd4714 100644 --- a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap +++ b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap @@ -27,7 +27,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "3", @@ -39,7 +38,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -51,7 +49,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "1", @@ -63,8 +60,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [ @@ -89,7 +84,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "3", @@ -101,7 +95,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -113,7 +106,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "1", @@ -143,7 +135,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "3", @@ -155,7 +146,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -167,8 +157,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [ @@ -189,7 +177,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "3", @@ -201,7 +188,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -227,7 +213,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "3", @@ -239,8 +224,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [ @@ -257,7 +240,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "3", @@ -279,8 +261,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [], @@ -320,7 +300,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-4", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "3", @@ -332,7 +311,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -344,7 +322,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "1", @@ -356,7 +333,6 @@ exports[`build chat item tree and get thread messages get thread messages with l "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ] `; @@ -384,7 +360,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -396,7 +371,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -405,13 +379,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -423,8 +396,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [ @@ -445,7 +416,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -457,7 +427,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -466,13 +435,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -488,22 +456,20 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingCount": 2, - "siblingIndex": 1, }, { "children": [], "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingCount": 1, - "siblingIndex": 0, + "siblingCount": 2, + "siblingIndex": 1, }, ] `; @@ -531,7 +497,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -543,7 +508,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -552,13 +516,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -570,8 +533,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [ @@ -592,7 +553,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -604,7 +564,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -613,13 +572,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -645,7 +603,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -657,8 +614,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 2, - "siblingIndex": 0, }, { "children": [ @@ -675,13 +630,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", "isAnswer": true, "parentMessageId": "question-2", - "siblingCount": 1, + "siblingCount": 2, "siblingIndex": 0, }, { @@ -697,8 +651,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [], @@ -734,7 +686,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -746,7 +697,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -755,13 +705,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -773,7 +722,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ] `; @@ -801,7 +749,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -813,7 +760,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -822,13 +768,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -840,8 +785,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingCount": 2, - "siblingIndex": 0, }, { "children": [ @@ -862,7 +805,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -874,7 +816,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -883,19 +824,18 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", "isAnswer": true, "parentMessageId": "question-1", - "siblingCount": 1, + "siblingCount": 2, "siblingIndex": 0, }, { @@ -905,22 +845,20 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingCount": 2, - "siblingIndex": 1, }, { "children": [], "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingCount": 1, - "siblingIndex": 0, + "siblingCount": 2, + "siblingIndex": 1, }, ] `; @@ -948,7 +886,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, ], "id": "2", @@ -960,7 +897,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -969,13 +905,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -987,7 +922,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-1", "isAnswer": false, "parentMessageId": "00000000-0000-0000-0000-000000000000", - "siblingIndex": 0, }, { "children": [ @@ -996,13 +930,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "5", "isAnswer": true, "parentMessageId": "question-5", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-5", "isAnswer": false, "parentMessageId": null, - "siblingIndex": 1, }, ] `; @@ -1030,7 +963,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1042,7 +974,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1051,13 +982,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -1069,8 +999,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-1", "isAnswer": false, "parentMessageId": null, - "siblingCount": 2, - "siblingIndex": 0, }, { "children": [ @@ -1091,7 +1019,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1103,7 +1030,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1112,19 +1038,18 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", "isAnswer": true, "parentMessageId": "question-1", - "siblingCount": 1, + "siblingCount": 2, "siblingIndex": 0, }, { @@ -1134,22 +1059,20 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingCount": 2, - "siblingIndex": 1, }, { "children": [], "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingCount": 1, - "siblingIndex": 0, + "siblingCount": 2, + "siblingIndex": 1, }, ] `; @@ -1177,7 +1100,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1189,7 +1111,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1198,13 +1119,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -1216,7 +1136,6 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "question-1", "isAnswer": false, "parentMessageId": null, - "siblingIndex": 0, }, { "children": [ @@ -1225,13 +1144,12 @@ exports[`build chat item tree and get thread messages get thread messages with m "id": "5", "isAnswer": true, "parentMessageId": "question-5", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-5", "isAnswer": false, "parentMessageId": null, - "siblingIndex": 1, }, ] `; @@ -1259,7 +1177,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1271,7 +1188,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1280,13 +1196,12 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -1298,8 +1213,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-1", "isAnswer": false, "parentMessageId": null, - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [ @@ -1320,7 +1233,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1332,7 +1244,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1341,13 +1252,12 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -1363,22 +1273,20 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingCount": 2, - "siblingIndex": 1, }, { "children": [], "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingCount": 1, - "siblingIndex": 0, + "siblingCount": 2, + "siblingIndex": 1, }, ] `; @@ -1406,7 +1314,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1418,7 +1325,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1427,13 +1333,12 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -1445,8 +1350,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-1", "isAnswer": false, "parentMessageId": null, - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [ @@ -1467,7 +1370,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1479,7 +1381,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1488,13 +1389,12 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -1520,7 +1420,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1532,8 +1431,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingCount": 2, - "siblingIndex": 0, }, { "children": [ @@ -1550,13 +1447,12 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", "isAnswer": true, "parentMessageId": "question-2", - "siblingCount": 1, + "siblingCount": 2, "siblingIndex": 0, }, { @@ -1572,8 +1468,6 @@ exports[`build chat item tree and get thread messages get thread messages: threa "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingCount": 1, - "siblingIndex": 0, }, { "children": [], @@ -1609,7 +1503,6 @@ exports[`build chat item tree and get thread messages get thread messages: tree1 "id": "question-3", "isAnswer": false, "parentMessageId": "2", - "siblingIndex": 0, }, ], "id": "2", @@ -1621,7 +1514,6 @@ exports[`build chat item tree and get thread messages get thread messages: tree1 "id": "question-2", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 0, }, { "children": [ @@ -1630,13 +1522,12 @@ exports[`build chat item tree and get thread messages get thread messages: tree1 "id": "4", "isAnswer": true, "parentMessageId": "question-4", - "siblingIndex": 0, + "siblingIndex": 1, }, ], "id": "question-4", "isAnswer": false, "parentMessageId": "1", - "siblingIndex": 1, }, ], "id": "1", @@ -1648,7 +1539,6 @@ exports[`build chat item tree and get thread messages get thread messages: tree1 "id": "question-1", "isAnswer": false, "parentMessageId": null, - "siblingIndex": 0, }, ] `; diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index d29e63a91b088..a7829cf9315ac 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -38,6 +38,7 @@ type AnswerProps = { hideProcessDetail?: boolean appData?: AppData noChatInput?: boolean + switchSibling?: (siblingMessageId: string) => void } const Answer: FC = ({ item, @@ -52,6 +53,7 @@ const Answer: FC = ({ hideProcessDetail, appData, noChatInput, + switchSibling, }) => { const { t } = useTranslation() const { @@ -193,15 +195,23 @@ const Answer: FC = ({ ) } -
- - 2 / 2 - -
+ } diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index e591731a97363..7bc4e0bc254c4 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -62,6 +62,7 @@ export type ChatProps = { hideProcessDetail?: boolean hideLogModal?: boolean themeBuilder?: ThemeBuilder + switchSibling?: (siblingMessageId: string) => void } const Chat: FC = ({ @@ -92,6 +93,7 @@ const Chat: FC = ({ hideProcessDetail, hideLogModal, themeBuilder, + switchSibling, }) => { const { t } = useTranslation() const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({ @@ -222,6 +224,7 @@ const Chat: FC = ({ chatAnswerContainerInner={chatAnswerContainerInner} hideProcessDetail={hideProcessDetail} noChatInput={noChatInput} + switchSibling={switchSibling} /> ) } diff --git a/web/app/components/base/chat/chat/type.ts b/web/app/components/base/chat/chat/type.ts index d6210d3918a5d..cbf787208603e 100644 --- a/web/app/components/base/chat/chat/type.ts +++ b/web/app/components/base/chat/chat/type.ts @@ -98,6 +98,8 @@ export type IChatItem = { parentMessageId?: string | null siblingCount?: number siblingIndex?: number + prevSibling?: string + nextSibling?: string } export type Metadata = { diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index 946aacd7eae08..c905a4508b6e3 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -90,7 +90,6 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { const questionNode: ChatItemInTree = { ...question, children: [], - siblingIndex: isLegacy ? 0 : childrenCount[parentMessageId] - 1, } map[question.id] = questionNode @@ -99,7 +98,7 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { const answerNode: ChatItemInTree = { ...answer, children: [], - siblingIndex: 0, + siblingIndex: isLegacy ? 0 : childrenCount[parentMessageId] - 1, } map[answer.id] = answerNode @@ -142,7 +141,19 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch || (!targetMessageId && !node.children?.length && !stack.length) // if targetMessageId is not provided, we use the last message in the tree as the target ) { targetNode = node - ret = path.map((n, i) => ({ ...n, siblingCount: i === 0 ? tree.length : path[i - 1].children!.length })) + ret = path.map((item, index) => { + if (!item.isAnswer) + return item + + const parentAnswer = path[index - 2] + const siblingCount = !parentAnswer ? tree.length : parentAnswer.children!.length + const prevSibling = !parentAnswer ? tree[index - 1]?.id : parentAnswer.children![item.siblingIndex! - 1]?.children?.[0].id + const nextSibling = !parentAnswer ? tree[index + 1]?.id : parentAnswer.children![item.siblingIndex! + 1]?.children?.[0].id + if (item.id === '9b221880-044f-43be-bd5a-39b340bf84a1') + console.log({ parentAnswer, siblingIndex: item.siblingIndex, siblingCount, prevSibling, nextSibling }) + + return { ...item, siblingCount, prevSibling, nextSibling } + }) break } if (node.children) { From 849685b6cb914a3222f38cd005f88fa1a86cc0d2 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 11 Sep 2024 17:53:31 +0800 Subject: [PATCH 30/44] chore: update test snapshot --- .../__snapshots__/utils.spec.ts.snap | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap index f82934bcd4714..54e09740f831a 100644 --- a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap +++ b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap @@ -110,7 +110,9 @@ exports[`build chat item tree and get thread messages get thread messages with l ], "id": "1", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-1", + "prevSibling": "question-1", "siblingCount": 1, "siblingIndex": 0, }, @@ -192,7 +194,9 @@ exports[`build chat item tree and get thread messages get thread messages with l ], "id": "2", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-2", + "prevSibling": undefined, "siblingCount": 1, "siblingIndex": 0, }, @@ -244,7 +248,9 @@ exports[`build chat item tree and get thread messages get thread messages with l ], "id": "3", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-3", + "prevSibling": undefined, "siblingCount": 1, "siblingIndex": 0, }, @@ -266,7 +272,9 @@ exports[`build chat item tree and get thread messages get thread messages with l "children": [], "id": "4", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-4", + "prevSibling": undefined, "siblingCount": 1, "siblingIndex": 0, }, @@ -445,7 +453,9 @@ exports[`build chat item tree and get thread messages get thread messages with m ], "id": "1", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-1", + "prevSibling": "question-1", "siblingCount": 1, "siblingIndex": 0, }, @@ -467,7 +477,9 @@ exports[`build chat item tree and get thread messages get thread messages with m "children": [], "id": "4", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-4", + "prevSibling": "2", "siblingCount": 2, "siblingIndex": 1, }, @@ -582,7 +594,9 @@ exports[`build chat item tree and get thread messages get thread messages with m ], "id": "1", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-1", + "prevSibling": "question-1", "siblingCount": 1, "siblingIndex": 0, }, @@ -634,7 +648,9 @@ exports[`build chat item tree and get thread messages get thread messages with m ], "id": "2", "isAnswer": true, + "nextSibling": "4", "parentMessageId": "question-2", + "prevSibling": undefined, "siblingCount": 2, "siblingIndex": 0, }, @@ -656,7 +672,9 @@ exports[`build chat item tree and get thread messages get thread messages with m "children": [], "id": "3", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-3", + "prevSibling": undefined, "siblingCount": 1, "siblingIndex": 0, }, @@ -834,7 +852,9 @@ exports[`build chat item tree and get thread messages get thread messages with m ], "id": "1", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-1", + "prevSibling": "question-1", "siblingCount": 2, "siblingIndex": 0, }, @@ -856,7 +876,9 @@ exports[`build chat item tree and get thread messages get thread messages with m "children": [], "id": "4", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-4", + "prevSibling": "2", "siblingCount": 2, "siblingIndex": 1, }, @@ -1048,7 +1070,9 @@ exports[`build chat item tree and get thread messages get thread messages with m ], "id": "1", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-1", + "prevSibling": "question-1", "siblingCount": 2, "siblingIndex": 0, }, @@ -1070,7 +1094,9 @@ exports[`build chat item tree and get thread messages get thread messages with m "children": [], "id": "4", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-4", + "prevSibling": "2", "siblingCount": 2, "siblingIndex": 1, }, @@ -1262,7 +1288,9 @@ exports[`build chat item tree and get thread messages get thread messages: threa ], "id": "1", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-1", + "prevSibling": "question-1", "siblingCount": 1, "siblingIndex": 0, }, @@ -1284,7 +1312,9 @@ exports[`build chat item tree and get thread messages get thread messages: threa "children": [], "id": "4", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-4", + "prevSibling": "2", "siblingCount": 2, "siblingIndex": 1, }, @@ -1399,7 +1429,9 @@ exports[`build chat item tree and get thread messages get thread messages: threa ], "id": "1", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-1", + "prevSibling": "question-1", "siblingCount": 1, "siblingIndex": 0, }, @@ -1451,7 +1483,9 @@ exports[`build chat item tree and get thread messages get thread messages: threa ], "id": "2", "isAnswer": true, + "nextSibling": "4", "parentMessageId": "question-2", + "prevSibling": undefined, "siblingCount": 2, "siblingIndex": 0, }, @@ -1473,7 +1507,9 @@ exports[`build chat item tree and get thread messages get thread messages: threa "children": [], "id": "3", "isAnswer": true, + "nextSibling": undefined, "parentMessageId": "question-3", + "prevSibling": undefined, "siblingCount": 1, "siblingIndex": 0, }, From c585fae9a55ed24bb8577580ab003dcf59a36732 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 11 Sep 2024 18:04:55 +0800 Subject: [PATCH 31/44] chore: lint python code --- api/core/app/apps/advanced_chat/app_generator.py | 2 +- api/core/app/apps/agent_chat/app_generator.py | 2 +- api/core/app/apps/chat/app_generator.py | 2 +- api/core/app/apps/message_based_app_generator.py | 2 +- api/core/prompt/utils/extract_thread_messages.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index e8cb6c69f500e..445ef6d0ab19f 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -121,7 +121,7 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, - parent_message_id=args.get('parent_message_id'), + parent_message_id=args.get("parent_message_id"), user_id=user.id, stream=stream, invoke_from=invoke_from, diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index 898d118480630..99abccf4f9840 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -127,7 +127,7 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, - parent_message_id=args.get('parent_message_id'), + parent_message_id=args.get("parent_message_id"), user_id=user.id, stream=stream, invoke_from=invoke_from, diff --git a/api/core/app/apps/chat/app_generator.py b/api/core/app/apps/chat/app_generator.py index 105d9e20d341e..9ef1366a0f668 100644 --- a/api/core/app/apps/chat/app_generator.py +++ b/api/core/app/apps/chat/app_generator.py @@ -128,7 +128,7 @@ def generate( inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config), query=query, files=file_objs, - parent_message_id=args.get('parent_message_id'), + parent_message_id=args.get("parent_message_id"), user_id=user.id, stream=stream, invoke_from=invoke_from, diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index 4e36a0b3bb2a8..3bfbdac5e85f5 100644 --- a/api/core/app/apps/message_based_app_generator.py +++ b/api/core/app/apps/message_based_app_generator.py @@ -218,7 +218,7 @@ def _init_generate_records( answer_tokens=0, answer_unit_price=0, answer_price_unit=0, - parent_message_id=getattr(application_generate_entity, 'parent_message_id', None), + parent_message_id=getattr(application_generate_entity, "parent_message_id", None), provider_response_latency=0, total_price=0, currency="USD", diff --git a/api/core/prompt/utils/extract_thread_messages.py b/api/core/prompt/utils/extract_thread_messages.py index e16153f99bd72..3bd38dd30b2ba 100644 --- a/api/core/prompt/utils/extract_thread_messages.py +++ b/api/core/prompt/utils/extract_thread_messages.py @@ -19,4 +19,4 @@ def extract_thread_messages(messages: list[dict]) -> list[dict]: thread_messages.append(message) next_message = message.parent_message_id - return thread_messages \ No newline at end of file + return thread_messages From c7aa2b32317ce08c4033b219d28756d594498320 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 11 Sep 2024 18:13:57 +0800 Subject: [PATCH 32/44] chore: db migrate version --- ...9ebb251_add_parent_message_id_to_messages.py} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename api/migrations/versions/{2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py => 2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py} (86%) diff --git a/api/migrations/versions/2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py b/api/migrations/versions/2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py similarity index 86% rename from api/migrations/versions/2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py rename to api/migrations/versions/2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py index e45b366346664..5f082c7a604d9 100644 --- a/api/migrations/versions/2024_09_05_1012-ed5ab9b16a2b_add_parent_message_id_to_messages.py +++ b/api/migrations/versions/2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py @@ -1,18 +1,18 @@ """add parent_message_id to messages -Revision ID: ed5ab9b16a2b -Revises: 030f4915f36a -Create Date: 2024-09-05 10:12:33.799034 +Revision ID: d57ba9ebb251 +Revises: 675b5321501b +Create Date: 2024-09-11 10:12:45.826265 """ -import sqlalchemy as sa from alembic import op - import models as models +import sqlalchemy as sa + # revision identifiers, used by Alembic. -revision = 'ed5ab9b16a2b' -down_revision = '030f4915f36a' +revision = 'd57ba9ebb251' +down_revision = '675b5321501b' branch_labels = None depends_on = None @@ -21,7 +21,7 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('messages', schema=None) as batch_op: batch_op.add_column(sa.Column('parent_message_id', models.types.StringUUID(), nullable=True)) - + # Set parent_message_id for existing messages to uuid_nil() to distinguish them from new messages with actual parent IDs or NULLs op.execute('UPDATE messages SET parent_message_id = uuid_nil() WHERE parent_message_id IS NULL') From 1d68b581fd28d36b07e5a3bd40ff047beb36d953 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 11 Sep 2024 18:14:22 +0800 Subject: [PATCH 33/44] chore: lint python code --- ..._11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/migrations/versions/2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py b/api/migrations/versions/2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py index 5f082c7a604d9..fd957eeafb2b6 100644 --- a/api/migrations/versions/2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py +++ b/api/migrations/versions/2024_09_11_1012-d57ba9ebb251_add_parent_message_id_to_messages.py @@ -5,10 +5,10 @@ Create Date: 2024-09-11 10:12:45.826265 """ -from alembic import op -import models as models import sqlalchemy as sa +from alembic import op +import models as models # revision identifiers, used by Alembic. revision = 'd57ba9ebb251' From 31276c5690099edce8615c2ac79548b27050a3e1 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Fri, 13 Sep 2024 15:27:58 +0800 Subject: [PATCH 34/44] fix: algorithm --- .../__snapshots__/utils.spec.ts.snap | 1863 ++++------------- .../chat/__tests__/realWorldMessages.json | 441 ++++ .../base/chat/__tests__/utils.spec.ts | 253 ++- web/app/components/base/chat/utils.ts | 22 +- 4 files changed, 1064 insertions(+), 1515 deletions(-) create mode 100644 web/app/components/base/chat/__tests__/realWorldMessages.json diff --git a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap index 54e09740f831a..3aad1aadb0297 100644 --- a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap +++ b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap @@ -1,1580 +1,477 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`build chat item tree and get thread messages get thread messages with legacy chat items: threadMessages2 1`] = ` +exports[`build chat item tree and get thread messages should work with real world messages 1`] = ` [ { "children": [ { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105791, + "files": [], + "id": "f9d7ff7c-3a3b-4d9a-a289-657817f4caff", + "message_id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "observation": "", + "position": 1, + "thought": "Sure, I'll play! My number is 57. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], "children": [ { "children": [ { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105795, + "files": [], + "id": "f61a3fce-37ac-4f9d-9935-95f97e598dfe", + "message_id": "73bbad14-d915-499d-87bf-0df14d40779d", + "observation": "", + "position": 1, + "thought": "I choose 83. What's your next number?", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], "children": [ { "children": [ { - "children": [ + "agent_thoughts": [ { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", + "chain_id": null, + "created_at": 1726105799, + "files": [], + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "observation": "", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, }, ], - "id": "3", + "children": [], + "content": "I'll go with 112. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "input": { + "inputs": {}, + "query": "99", + }, "isAnswer": true, - "parentMessageId": "question-3", + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + { + "files": [], + "role": "user", + "text": "99", + }, + { + "files": [], + "role": "assistant", + "text": "I'll go with 112. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.49", + "time": "09/11/2024 09:50 PM", + "tokens": 86, + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658", "siblingIndex": 0, + "workflow_run_id": null, }, ], - "id": "question-3", + "content": "99", + "id": "question-4c5d0841-1206-463e-95d8-71f812877658", "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", + "message_files": [], + "parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d", }, ], - "id": "2", + "content": "I choose 83. What's your next number?", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "73bbad14-d915-499d-87bf-0df14d40779d", + "input": { + "inputs": {}, + "query": "58", + }, "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ + "log": [ { - "children": [ - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "1", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-1", - "prevSibling": "question-1", - "siblingCount": 1, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", }, ], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", + "message_files": [], + "more": { + "latency": "1.33", + "time": "09/11/2024 09:49 PM", + "tokens": 68, + }, + "parentMessageId": "question-73bbad14-d915-499d-87bf-0df14d40779d", "siblingIndex": 0, + "workflow_run_id": null, }, ], - "id": "question-3", + "content": "58", + "id": "question-73bbad14-d915-499d-87bf-0df14d40779d", "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "2", + "message_files": [], + "parentMessageId": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + }, + ], + "content": "Sure, I'll play! My number is 57. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [ + "log": [ { - "children": [ - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "2", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-2", - "prevSibling": undefined, - "siblingCount": 1, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [ { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", }, ], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", + "message_files": [], + "more": { + "latency": "1.56", + "time": "09/11/2024 09:49 PM", + "tokens": 49, + }, + "parentMessageId": "question-ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", "siblingIndex": 0, + "workflow_run_id": null, }, ], - "id": "question-3", + "content": "Let's play a game, I say a number , and you response me with another bigger, yet random-looking number. I'll start first, 38", + "id": "question-ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", + "message_files": [], }, { "children": [ { - "children": [ + "agent_thoughts": [ { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, + "chain_id": null, + "created_at": 1726105809, + "files": [], + "id": "1019cd79-d141-4f9f-880a-fc1441cfd802", + "message_id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "observation": "", + "position": 1, + "thought": "Sure! My number is 54. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, }, ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "3", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-3", - "prevSibling": undefined, - "siblingCount": 1, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [], - "id": "4", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-4", - "prevSibling": undefined, - "siblingCount": 1, - "siblingIndex": 0, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with legacy chat items: tree2 1`] = ` -[ - { - "children": [ - { "children": [ { "children": [ { - "children": [ + "agent_thoughts": [ { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 0, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", + "chain_id": null, + "created_at": 1726105822, + "files": [], + "id": "0773bec7-b992-4a53-92b2-20ebaeae8798", + "message_id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "observation": "", + "position": 1, + "thought": "My number is 4729. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, }, ], - "id": "2", + "children": [], + "content": "My number is 4729. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "input": { + "inputs": {}, + "query": "3306", + }, "isAnswer": true, - "parentMessageId": "question-2", + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4729. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.30", + "time": "09/11/2024 09:50 PM", + "tokens": 66, + }, + "parentMessageId": "question-324bce32-c98c-435d-a66b-bac974ebb5ed", "siblingIndex": 0, + "workflow_run_id": null, }, ], - "id": "question-2", + "content": "3306", + "id": "question-324bce32-c98c-435d-a66b-bac974ebb5ed", "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with mixed chat items: threadMessages3_1 1`] = ` -[ - { - "children": [ - { - "children": [ { "children": [ { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726107812, + "files": [], + "id": "5ca650f3-982c-4399-8b95-9ea241c76707", + "message_id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "observation": "", + "position": 1, + "thought": "My number is 4821. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], "children": [ { "children": [ { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726111024, + "files": [], + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "observation": "", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], "children": [], - "id": "3", + "content": "My number is 1456. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "input": { + "inputs": {}, + "query": "1003", + }, "isAnswer": true, - "parentMessageId": "question-3", + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "1003", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 1456. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.38", + "time": "09/11/2024 11:17 PM", + "tokens": 86, + }, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", "siblingIndex": 0, + "workflow_run_id": null, }, ], - "id": "question-3", + "content": "1003", + "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", "isAnswer": false, - "parentMessageId": "2", + "message_files": [], + "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc", }, ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "4", + "content": "My number is 4821. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "input": { + "inputs": {}, + "query": "3306", + }, "isAnswer": true, - "parentMessageId": "question-4", + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.48", + "time": "09/11/2024 10:23 PM", + "tokens": 66, + }, + "parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc", "siblingIndex": 1, + "workflow_run_id": null, }, ], - "id": "question-4", + "content": "3306", + "id": "question-684b5396-4e91-4043-88e9-aabe48b21acc", "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + }, + ], + "content": "Sure! My number is 54. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [ + "log": [ { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", }, ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-1", - "prevSibling": "question-1", - "siblingCount": 1, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", + "message_files": [], + "more": { + "latency": "1.52", + "time": "09/11/2024 09:50 PM", + "tokens": 46, + }, + "parentMessageId": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", "siblingIndex": 1, + "workflow_run_id": null, }, ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [], - "id": "4", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-4", - "prevSibling": "2", - "siblingCount": 2, - "siblingIndex": 1, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with mixed chat items: threadMessages3_2 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-1", - "prevSibling": "question-1", - "siblingCount": 1, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "nextSibling": "4", - "parentMessageId": "question-2", - "prevSibling": undefined, - "siblingCount": 2, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - { - "children": [], - "id": "3", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-3", - "prevSibling": undefined, - "siblingCount": 1, - "siblingIndex": 0, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with mixed chat items: tree3 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with multi root nodes chat items with legacy chat items: threadMessages5 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-1", - "prevSibling": "question-1", - "siblingCount": 2, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [], - "id": "4", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-4", - "prevSibling": "2", - "siblingCount": 2, - "siblingIndex": 1, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with multi root nodes chat items with legacy chat items: tree5 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": "00000000-0000-0000-0000-000000000000", - }, - { - "children": [ - { - "children": [], - "id": "5", - "isAnswer": true, - "parentMessageId": "question-5", - "siblingIndex": 1, - }, - ], - "id": "question-5", - "isAnswer": false, - "parentMessageId": null, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with multi root nodes chat items: threadMessages4 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": null, - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-1", - "prevSibling": "question-1", - "siblingCount": 2, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [], - "id": "4", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-4", - "prevSibling": "2", - "siblingCount": 2, - "siblingIndex": 1, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages with multi root nodes chat items: tree4 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": null, - }, - { - "children": [ - { - "children": [], - "id": "5", - "isAnswer": true, - "parentMessageId": "question-5", - "siblingIndex": 1, - }, - ], - "id": "question-5", - "isAnswer": false, - "parentMessageId": null, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages: threadMessages1_1 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": null, - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-1", - "prevSibling": "question-1", - "siblingCount": 1, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [], - "id": "4", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-4", - "prevSibling": "2", - "siblingCount": 2, - "siblingIndex": 1, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages: threadMessages1_2 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", - "isAnswer": false, - "parentMessageId": null, - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-1", - "prevSibling": "question-1", - "siblingCount": 1, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "nextSibling": "4", - "parentMessageId": "question-2", - "prevSibling": undefined, - "siblingCount": 2, - "siblingIndex": 0, - }, - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - { - "children": [], - "id": "3", - "isAnswer": true, - "nextSibling": undefined, - "parentMessageId": "question-3", - "prevSibling": undefined, - "siblingCount": 1, - "siblingIndex": 0, - }, -] -`; - -exports[`build chat item tree and get thread messages get thread messages: tree1 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [], - "id": "3", - "isAnswer": true, - "parentMessageId": "question-3", - "siblingIndex": 0, - }, - ], - "id": "question-3", - "isAnswer": false, - "parentMessageId": "2", - }, - ], - "id": "2", - "isAnswer": true, - "parentMessageId": "question-2", - "siblingIndex": 0, - }, - ], - "id": "question-2", - "isAnswer": false, - "parentMessageId": "1", - }, - { - "children": [ - { - "children": [], - "id": "4", - "isAnswer": true, - "parentMessageId": "question-4", - "siblingIndex": 1, - }, - ], - "id": "question-4", - "isAnswer": false, - "parentMessageId": "1", - }, - ], - "id": "1", - "isAnswer": true, - "parentMessageId": "question-1", - "siblingIndex": 0, - }, - ], - "id": "question-1", + "content": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "id": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", "isAnswer": false, - "parentMessageId": null, + "message_files": [], }, ] `; diff --git a/web/app/components/base/chat/__tests__/realWorldMessages.json b/web/app/components/base/chat/__tests__/realWorldMessages.json new file mode 100644 index 0000000000000..858052c77f9b3 --- /dev/null +++ b/web/app/components/base/chat/__tests__/realWorldMessages.json @@ -0,0 +1,441 @@ +[ + { + "id": "question-ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "content": "Let's play a game, I say a number , and you response me with another bigger, yet random-looking number. I'll start first, 38", + "isAnswer": false, + "message_files": [] + }, + { + "id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "content": "Sure, I'll play! My number is 57. Your turn!", + "agent_thoughts": [ + { + "id": "f9d7ff7c-3a3b-4d9a-a289-657817f4caff", + "chain_id": null, + "message_id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "position": 1, + "thought": "Sure, I'll play! My number is 57. Your turn!", + "tool": "", + "tool_labels": {}, + "tool_input": "", + "created_at": 1726105791, + "observation": "", + "files": [] + } + ], + "feedbackDisabled": false, + "isAnswer": true, + "message_files": [], + "log": [ + { + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "files": [] + }, + { + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + "files": [] + } + ], + "workflow_run_id": null, + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38" + }, + "more": { + "time": "09/11/2024 09:49 PM", + "tokens": 49, + "latency": "1.56" + }, + "parentMessageId": "question-ff4c2b43-48a5-47ad-9dc5-08b34ddba61b" + }, + { + "id": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "content": "58", + "isAnswer": false, + "message_files": [], + "parentMessageId": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b" + }, + { + "id": "73bbad14-d915-499d-87bf-0df14d40779d", + "content": "I choose 83. What's your next number?", + "agent_thoughts": [ + { + "id": "f61a3fce-37ac-4f9d-9935-95f97e598dfe", + "chain_id": null, + "message_id": "73bbad14-d915-499d-87bf-0df14d40779d", + "position": 1, + "thought": "I choose 83. What's your next number?", + "tool": "", + "tool_labels": {}, + "tool_input": "", + "created_at": 1726105795, + "observation": "", + "files": [] + } + ], + "feedbackDisabled": false, + "isAnswer": true, + "message_files": [], + "log": [ + { + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "files": [] + }, + { + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + "files": [] + }, + { + "role": "user", + "text": "58", + "files": [] + }, + { + "role": "assistant", + "text": "I choose 83. What's your next number?", + "files": [] + } + ], + "workflow_run_id": null, + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "input": { + "inputs": {}, + "query": "58" + }, + "more": { + "time": "09/11/2024 09:49 PM", + "tokens": 68, + "latency": "1.33" + }, + "parentMessageId": "question-73bbad14-d915-499d-87bf-0df14d40779d" + }, + { + "id": "question-4c5d0841-1206-463e-95d8-71f812877658", + "content": "99", + "isAnswer": false, + "message_files": [], + "parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d" + }, + { + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "content": "I'll go with 112. Your turn!", + "agent_thoughts": [ + { + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "chain_id": null, + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_labels": {}, + "tool_input": "", + "created_at": 1726105799, + "observation": "", + "files": [] + } + ], + "feedbackDisabled": false, + "isAnswer": true, + "message_files": [], + "log": [ + { + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "files": [] + }, + { + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + "files": [] + }, + { + "role": "user", + "text": "58", + "files": [] + }, + { + "role": "assistant", + "text": "I choose 83. What's your next number?", + "files": [] + }, + { + "role": "user", + "text": "99", + "files": [] + }, + { + "role": "assistant", + "text": "I'll go with 112. Your turn!", + "files": [] + } + ], + "workflow_run_id": null, + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "input": { + "inputs": {}, + "query": "99" + }, + "more": { + "time": "09/11/2024 09:50 PM", + "tokens": 86, + "latency": "1.49" + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658" + }, + { + "id": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "content": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "isAnswer": false, + "message_files": [] + }, + { + "id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "content": "Sure! My number is 54. Your turn!", + "agent_thoughts": [ + { + "id": "1019cd79-d141-4f9f-880a-fc1441cfd802", + "chain_id": null, + "message_id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "position": 1, + "thought": "Sure! My number is 54. Your turn!", + "tool": "", + "tool_labels": {}, + "tool_input": "", + "created_at": 1726105809, + "observation": "", + "files": [] + } + ], + "feedbackDisabled": false, + "isAnswer": true, + "message_files": [], + "log": [ + { + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "files": [] + }, + { + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + "files": [] + } + ], + "workflow_run_id": null, + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38" + }, + "more": { + "time": "09/11/2024 09:50 PM", + "tokens": 46, + "latency": "1.52" + }, + "parentMessageId": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd" + }, + { + "id": "question-324bce32-c98c-435d-a66b-bac974ebb5ed", + "content": "3306", + "isAnswer": false, + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd" + }, + { + "id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "content": "My number is 4729. Your turn!", + "agent_thoughts": [ + { + "id": "0773bec7-b992-4a53-92b2-20ebaeae8798", + "chain_id": null, + "message_id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "position": 1, + "thought": "My number is 4729. Your turn!", + "tool": "", + "tool_labels": {}, + "tool_input": "", + "created_at": 1726105822, + "observation": "", + "files": [] + } + ], + "feedbackDisabled": false, + "isAnswer": true, + "message_files": [], + "log": [ + { + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "files": [] + }, + { + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + "files": [] + }, + { + "role": "user", + "text": "3306", + "files": [] + }, + { + "role": "assistant", + "text": "My number is 4729. Your turn!", + "files": [] + } + ], + "workflow_run_id": null, + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "input": { + "inputs": {}, + "query": "3306" + }, + "more": { + "time": "09/11/2024 09:50 PM", + "tokens": 66, + "latency": "1.30" + }, + "parentMessageId": "question-324bce32-c98c-435d-a66b-bac974ebb5ed" + }, + { + "id": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "content": "3306", + "isAnswer": false, + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd" + }, + { + "id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "content": "My number is 4821. Your turn!", + "agent_thoughts": [ + { + "id": "5ca650f3-982c-4399-8b95-9ea241c76707", + "chain_id": null, + "message_id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "position": 1, + "thought": "My number is 4821. Your turn!", + "tool": "", + "tool_labels": {}, + "tool_input": "", + "created_at": 1726107812, + "observation": "", + "files": [] + } + ], + "feedbackDisabled": false, + "isAnswer": true, + "message_files": [], + "log": [ + { + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "files": [] + }, + { + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + "files": [] + }, + { + "role": "user", + "text": "3306", + "files": [] + }, + { + "role": "assistant", + "text": "My number is 4821. Your turn!", + "files": [] + } + ], + "workflow_run_id": null, + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "input": { + "inputs": {}, + "query": "3306" + }, + "more": { + "time": "09/11/2024 10:23 PM", + "tokens": 66, + "latency": "1.48" + }, + "parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc" + }, + { + "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "content": "1003", + "isAnswer": false, + "message_files": [], + "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc" + }, + { + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "content": "My number is 1456. Your turn!", + "agent_thoughts": [ + { + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "chain_id": null, + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_labels": {}, + "tool_input": "", + "created_at": 1726111024, + "observation": "", + "files": [] + } + ], + "feedbackDisabled": false, + "isAnswer": true, + "message_files": [], + "log": [ + { + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "files": [] + }, + { + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + "files": [] + }, + { + "role": "user", + "text": "3306", + "files": [] + }, + { + "role": "assistant", + "text": "My number is 4821. Your turn!", + "files": [] + }, + { + "role": "user", + "text": "1003", + "files": [] + }, + { + "role": "assistant", + "text": "My number is 1456. Your turn!", + "files": [] + } + ], + "workflow_run_id": null, + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "input": { + "inputs": {}, + "query": "1003" + }, + "more": { + "time": "09/11/2024 11:17 PM", + "tokens": 86, + "latency": "1.38" + }, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c" + } +] diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts index 17915bb35c221..e4ac6919845a4 100644 --- a/web/app/components/base/chat/__tests__/utils.spec.ts +++ b/web/app/components/base/chat/__tests__/utils.spec.ts @@ -1,55 +1,258 @@ -import type { IChatItem } from '../chat/type' +import { get } from 'lodash' import { buildChatItemTree, getThreadMessages } from '../utils' +import type { ChatItemInTree } from '../types' import branchedTestMessages from './branchedTestMessages.json' import legacyTestMessages from './legacyTestMessages.json' import mixedTestMessages from './mixedTestMessages.json' import multiRootNodesMessages from './multiRootNodesMessages.json' import multiRootNodesWithLegacyTestMessages from './multiRootNodesWithLegacyTestMessages.json' +import realWorldMessages from './realWorldMessages.json' + +function visitNode(tree: ChatItemInTree | ChatItemInTree[], path: string): ChatItemInTree { + return get(tree, path) +} describe('build chat item tree and get thread messages', () => { - it('get thread messages', () => { - const tree1 = buildChatItemTree(branchedTestMessages as IChatItem[]) - expect(tree1).toMatchSnapshot('tree1') + const tree1 = buildChatItemTree(branchedTestMessages as ChatItemInTree[]) + + it('should build chat item tree1', () => { + const a1 = visitNode(tree1, '0.children.0') + expect(a1.id).toBe('1') + expect(a1.children).toHaveLength(2) + + const a2 = visitNode(a1, 'children.0.children.0') + expect(a2.id).toBe('2') + expect(a2.siblingIndex).toBe(0) + + const a3 = visitNode(a2, 'children.0.children.0') + expect(a3.id).toBe('3') + + const a4 = visitNode(a1, 'children.1.children.0') + expect(a4.id).toBe('4') + expect(a4.siblingIndex).toBe(1) + }) + + it('should get thread messages from tree1, using the last message as the target', () => { + const threadChatItems1_1 = getThreadMessages(tree1) + expect(threadChatItems1_1).toHaveLength(4) + + const q1 = visitNode(threadChatItems1_1, '0') + const a1 = visitNode(threadChatItems1_1, '1') + const q4 = visitNode(threadChatItems1_1, '2') + const a4 = visitNode(threadChatItems1_1, '3') + + expect(q1.id).toBe('question-1') + expect(a1.id).toBe('1') + expect(q4.id).toBe('question-4') + expect(a4.id).toBe('4') + + expect(a4.siblingCount).toBe(2) + expect(a4.siblingIndex).toBe(1) + }) + + it('should get thread messages from tree1, using the message with id 3 as the target', () => { + const threadChatItems1_2 = getThreadMessages(tree1, '3') + expect(threadChatItems1_2).toHaveLength(6) - const threadMessages1_1 = getThreadMessages(tree1) - expect(threadMessages1_1).toMatchSnapshot('threadMessages1_1') + const q1 = visitNode(threadChatItems1_2, '0') + const a1 = visitNode(threadChatItems1_2, '1') + const q2 = visitNode(threadChatItems1_2, '2') + const a2 = visitNode(threadChatItems1_2, '3') + const q3 = visitNode(threadChatItems1_2, '4') + const a3 = visitNode(threadChatItems1_2, '5') - const threadMessages1_2 = getThreadMessages(tree1, '3') - expect(threadMessages1_2).toMatchSnapshot('threadMessages1_2') + expect(q1.id).toBe('question-1') + expect(a1.id).toBe('1') + expect(q2.id).toBe('question-2') + expect(a2.id).toBe('2') + expect(q3.id).toBe('question-3') + expect(a3.id).toBe('3') + + expect(a2.siblingCount).toBe(2) + expect(a2.siblingIndex).toBe(0) }) - it('get thread messages with legacy chat items', () => { - const tree2 = buildChatItemTree(legacyTestMessages as IChatItem[]) - expect(tree2).toMatchSnapshot('tree2') + const tree2 = buildChatItemTree(legacyTestMessages as ChatItemInTree[]) + it('should work with legacy chat items', () => { + expect(tree2).toHaveLength(1) + const q1 = visitNode(tree2, '0') + const a1 = visitNode(q1, 'children.0') + const q2 = visitNode(a1, 'children.0') + const a2 = visitNode(q2, 'children.0') + const q3 = visitNode(a2, 'children.0') + const a3 = visitNode(q3, 'children.0') + const q4 = visitNode(a3, 'children.0') + const a4 = visitNode(q4, 'children.0') + + expect(q1.id).toBe('question-1') + expect(a1.id).toBe('1') + expect(q2.id).toBe('question-2') + expect(a2.id).toBe('2') + expect(q3.id).toBe('question-3') + expect(a3.id).toBe('3') + expect(q4.id).toBe('question-4') + expect(a4.id).toBe('4') + }) + it('should get thread messages from tree2, using the last message as the target', () => { const threadMessages2 = getThreadMessages(tree2) - expect(threadMessages2).toMatchSnapshot('threadMessages2') + expect(threadMessages2).toHaveLength(8) + + const q1 = visitNode(threadMessages2, '0') + const a1 = visitNode(threadMessages2, '1') + const q2 = visitNode(threadMessages2, '2') + const a2 = visitNode(threadMessages2, '3') + const q3 = visitNode(threadMessages2, '4') + const a3 = visitNode(threadMessages2, '5') + const q4 = visitNode(threadMessages2, '6') + const a4 = visitNode(threadMessages2, '7') + + expect(q1.id).toBe('question-1') + expect(a1.id).toBe('1') + expect(q2.id).toBe('question-2') + expect(a2.id).toBe('2') + expect(q3.id).toBe('question-3') + expect(a3.id).toBe('3') + expect(q4.id).toBe('question-4') + expect(a4.id).toBe('4') + + expect(a1.siblingCount).toBe(1) + expect(a1.siblingIndex).toBe(0) + expect(a2.siblingCount).toBe(1) + expect(a2.siblingIndex).toBe(0) + expect(a3.siblingCount).toBe(1) + expect(a3.siblingIndex).toBe(0) + expect(a4.siblingCount).toBe(1) + expect(a4.siblingIndex).toBe(0) }) - it('get thread messages with mixed chat items', () => { - const tree3 = buildChatItemTree(mixedTestMessages as IChatItem[]) - expect(tree3).toMatchSnapshot('tree3') + const tree3 = buildChatItemTree(mixedTestMessages as ChatItemInTree[]) + it('should build mixed chat items tree', () => { + expect(tree3).toHaveLength(1) + const a1 = visitNode(tree3, '0.children.0') + expect(a1.id).toBe('1') + expect(a1.children).toHaveLength(2) + + const a2 = visitNode(a1, 'children.0.children.0') + expect(a2.id).toBe('2') + expect(a2.siblingIndex).toBe(0) + + const a3 = visitNode(a2, 'children.0.children.0') + expect(a3.id).toBe('3') + + const a4 = visitNode(a1, 'children.1.children.0') + expect(a4.id).toBe('4') + expect(a4.siblingIndex).toBe(1) + }) + + it('should get thread messages from tree3, using the last message as the target', () => { const threadMessages3_1 = getThreadMessages(tree3) - expect(threadMessages3_1).toMatchSnapshot('threadMessages3_1') + expect(threadMessages3_1).toHaveLength(4) + + const q1 = visitNode(threadMessages3_1, '0') + const a1 = visitNode(threadMessages3_1, '1') + const q4 = visitNode(threadMessages3_1, '2') + const a4 = visitNode(threadMessages3_1, '3') + expect(q1.id).toBe('question-1') + expect(a1.id).toBe('1') + expect(q4.id).toBe('question-4') + expect(a4.id).toBe('4') + + expect(a4.siblingCount).toBe(2) + expect(a4.siblingIndex).toBe(1) + }) + + it('should get thread messages from tree3, using the message with id 3 as the target', () => { const threadMessages3_2 = getThreadMessages(tree3, '3') - expect(threadMessages3_2).toMatchSnapshot('threadMessages3_2') + expect(threadMessages3_2).toHaveLength(6) + + const q1 = visitNode(threadMessages3_2, '0') + const a1 = visitNode(threadMessages3_2, '1') + const q2 = visitNode(threadMessages3_2, '2') + const a2 = visitNode(threadMessages3_2, '3') + const q3 = visitNode(threadMessages3_2, '4') + const a3 = visitNode(threadMessages3_2, '5') + + expect(q1.id).toBe('question-1') + expect(a1.id).toBe('1') + expect(q2.id).toBe('question-2') + expect(a2.id).toBe('2') + expect(q3.id).toBe('question-3') + expect(a3.id).toBe('3') + + expect(a2.siblingCount).toBe(2) + expect(a2.siblingIndex).toBe(0) }) - it('get thread messages with multi root nodes chat items', () => { - const tree4 = buildChatItemTree(multiRootNodesMessages as IChatItem[]) - expect(tree4).toMatchSnapshot('tree4') + const tree4 = buildChatItemTree(multiRootNodesMessages as ChatItemInTree[]) + it('should build multi root nodes chat items tree', () => { + expect(tree4).toHaveLength(2) + const a5 = visitNode(tree4, '1.children.0') + expect(a5.id).toBe('5') + expect(a5.siblingIndex).toBe(1) + }) + + it('should get thread messages from tree4, using the last message as the target', () => { const threadMessages4 = getThreadMessages(tree4) - expect(threadMessages4).toMatchSnapshot('threadMessages4') + expect(threadMessages4).toHaveLength(2) + + const a1 = visitNode(threadMessages4, '0.children.0') + expect(a1.id).toBe('5') }) - it('get thread messages with multi root nodes chat items with legacy chat items', () => { - const tree5 = buildChatItemTree(multiRootNodesWithLegacyTestMessages as IChatItem[]) - expect(tree5).toMatchSnapshot('tree5') + it('should get thread messages from tree4, using the message with id 2 as the target', () => { + const threadMessages4_1 = getThreadMessages(tree4, '2') + expect(threadMessages4_1).toHaveLength(6) + const a1 = visitNode(threadMessages4_1, '1') + expect(a1.id).toBe('1') + const a2 = visitNode(threadMessages4_1, '3') + expect(a2.id).toBe('2') + const a3 = visitNode(threadMessages4_1, '5') + expect(a3.id).toBe('3') + }) + + const tree5 = buildChatItemTree(multiRootNodesWithLegacyTestMessages as ChatItemInTree[]) + it('should work with multi root nodes chat items with legacy chat items', () => { + expect(tree5).toHaveLength(2) + + const q5 = visitNode(tree5, '1') + expect(q5.id).toBe('question-5') + expect(q5.parentMessageId).toBe(null) + const a5 = visitNode(q5, 'children.0') + expect(a5.id).toBe('5') + expect(a5.children).toHaveLength(0) + }) + + it('should get thread messages from tree5, using the last message as the target', () => { const threadMessages5 = getThreadMessages(tree5) - expect(threadMessages5).toMatchSnapshot('threadMessages5') + expect(threadMessages5).toHaveLength(2) + + const q5 = visitNode(threadMessages5, '0') + const a5 = visitNode(threadMessages5, '1') + + expect(q5.id).toBe('question-5') + expect(a5.id).toBe('5') + + expect(a5.siblingCount).toBe(2) + expect(a5.siblingIndex).toBe(1) + }) + + const tree6 = buildChatItemTree(realWorldMessages as ChatItemInTree[]) + it('should work with real world messages', () => { + expect(tree6).toMatchSnapshot() + }) + + it ('should get thread messages from tree6, using the last message as target', () => { + const threadMessages6_1 = getThreadMessages(tree6) + expect(threadMessages6_1).toHaveLength(6) + }) + + it ('should get thread messages from tree6, using specified message as target', () => { + const threadMessages6_2 = getThreadMessages(tree6, 'ff4c2b43-48a5-47ad-9dc5-08b34ddba61b') + expect(threadMessages6_2).toHaveLength(6) }) }) diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index c905a4508b6e3..9c2acd34dacbc 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -130,7 +130,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch let targetNode: ChatItemInTree | undefined // find path to the target message - const stack = tree.map(rootNode => ({ + const stack = tree.reverse().map(rootNode => ({ node: rootNode, path: [rootNode], })) @@ -147,10 +147,8 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch const parentAnswer = path[index - 2] const siblingCount = !parentAnswer ? tree.length : parentAnswer.children!.length - const prevSibling = !parentAnswer ? tree[index - 1]?.id : parentAnswer.children![item.siblingIndex! - 1]?.children?.[0].id - const nextSibling = !parentAnswer ? tree[index + 1]?.id : parentAnswer.children![item.siblingIndex! + 1]?.children?.[0].id - if (item.id === '9b221880-044f-43be-bd5a-39b340bf84a1') - console.log({ parentAnswer, siblingIndex: item.siblingIndex, siblingCount, prevSibling, nextSibling }) + const prevSibling = !parentAnswer ? tree[item.siblingIndex! - 1]?.children?.[0]?.id : parentAnswer.children![item.siblingIndex! - 1]?.children?.[0].id + const nextSibling = !parentAnswer ? tree[item.siblingIndex! + 1]?.children?.[0]?.id : parentAnswer.children![item.siblingIndex! + 1]?.children?.[0].id return { ...item, siblingCount, prevSibling, nextSibling } }) @@ -174,8 +172,18 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch if (node !== targetNode) ret.push(node) if (node.children?.length) { - const lastDescendant = node.children.at(-1)! - stack.push({ ...lastDescendant, siblingCount: node.children.length }) + const lastChild = node.children.at(-1)! + + if (!lastChild.isAnswer) { + stack.push(lastChild) + continue + } + + const parentAnswer = ret.at(-2) + const siblingCount = parentAnswer?.children?.length + const prevSibling = parentAnswer?.children?.at(-2)?.children?.[0]?.id + + stack.push({ ...lastChild, siblingCount, prevSibling }) } } } From fc5aff3d6245afb33942b4a93e77ddb97ede8aa2 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Fri, 13 Sep 2024 15:54:37 +0800 Subject: [PATCH 35/44] feat: chatflow log supports viewing thread messages --- web/app/components/app/log/list.tsx | 2 +- .../workflow/panel/chat-record/index.tsx | 76 +++++++++++-------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index b116b831d0de1..f952fcdfa6ad1 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -496,7 +496,7 @@ function DetailPanel{t('appLog.detail.loading')}...} diff --git a/web/app/components/workflow/panel/chat-record/index.tsx b/web/app/components/workflow/panel/chat-record/index.tsx index afd20b7358670..596019bbfc17a 100644 --- a/web/app/components/workflow/panel/chat-record/index.tsx +++ b/web/app/components/workflow/panel/chat-record/index.tsx @@ -2,7 +2,6 @@ import { memo, useCallback, useEffect, - useMemo, useState, } from 'react' import { RiCloseLine } from '@remixicon/react' @@ -13,61 +12,75 @@ import { import { useWorkflowRun } from '../../hooks' import UserInput from './user-input' import Chat from '@/app/components/base/chat/chat' -import type { ChatItem } from '@/app/components/base/chat/types' +import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types' import { fetchConversationMessages } from '@/service/debug' import { useStore as useAppStore } from '@/app/components/app/store' import Loading from '@/app/components/base/loading' +import type { IChatItem } from '@/app/components/base/chat/chat/type' +import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' + +const getFormattedChatList = (messages: any[]) => { + const res: ChatItem[] = [] + messages.forEach((item: any) => { + res.push({ + id: `question-${item.id}`, + content: item.query, + isAnswer: false, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], + parentMessageId: item.parent_message_id || undefined, + }) + res.push({ + id: item.id, + content: item.answer, + feedback: item.feedback, + isAnswer: true, + citation: item.metadata?.retriever_resources, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], + workflow_run_id: item.workflow_run_id, + parentMessageId: `question-${item.id}`, + }) + }) + return res +} const ChatRecord = () => { const [fetched, setFetched] = useState(false) - const [chatList, setChatList] = useState([]) + const [chatItemTree, setChatItemTree] = useState([]) + const [threadChatItems, setThreadChatItems] = useState([]) const appDetail = useAppStore(s => s.appDetail) const workflowStore = useWorkflowStore() const { handleLoadBackupDraft } = useWorkflowRun() const historyWorkflowData = useStore(s => s.historyWorkflowData) const currentConversationID = historyWorkflowData?.conversation_id - const chatMessageList = useMemo(() => { - const res: ChatItem[] = [] - if (chatList.length) { - chatList.forEach((item: any) => { - res.push({ - id: `question-${item.id}`, - content: item.query, - isAnswer: false, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], - }) - res.push({ - id: item.id, - content: item.answer, - feedback: item.feedback, - isAnswer: true, - citation: item.metadata?.retriever_resources, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], - workflow_run_id: item.workflow_run_id, - }) - }) - } - return res - }, [chatList]) - const handleFetchConversationMessages = useCallback(async () => { if (appDetail && currentConversationID) { try { setFetched(false) const res = await fetchConversationMessages(appDetail.id, currentConversationID) - setFetched(true) - setChatList((res as any).data) + + const newAllChatItems = getFormattedChatList((res as any).data) + + const tree = buildChatItemTree(newAllChatItems) + setChatItemTree(tree) + setThreadChatItems(getThreadMessages(tree, newAllChatItems.at(-1)?.id)) } catch (e) { - + } + finally { + setFetched(true) } } }, [appDetail, currentConversationID]) + useEffect(() => { handleFetchConversationMessages() }, [currentConversationID, appDetail, handleFetchConversationMessages]) + const switchSibling = useCallback((siblingMessageId: string) => { + setThreadChatItems(getThreadMessages(chatItemTree, siblingMessageId)) + }, [chatItemTree]) + return (
{ config={{ supportCitationHitInfo: true, } as any} - chatList={chatMessageList} + chatList={threadChatItems} chatContainerClassName='px-4' chatContainerInnerClassName='pt-6 w-full max-w-full mx-auto' chatFooterClassName='px-4 rounded-b-2xl' @@ -110,6 +123,7 @@ const ChatRecord = () => { noChatInput allToolIcons={{}} showPromptLog + switchSibling={switchSibling} />
From d05c37028a549becadc9cc00668b3d2bd3b1b405 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Fri, 13 Sep 2024 16:15:19 +0800 Subject: [PATCH 36/44] fix: minor issue --- .../__snapshots__/utils.spec.ts.snap | 1804 +++++++++++++++++ .../base/chat/__tests__/utils.spec.ts | 4 +- web/app/components/base/chat/utils.ts | 2 +- 3 files changed, 1807 insertions(+), 3 deletions(-) diff --git a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap index 3aad1aadb0297..070975bfa747b 100644 --- a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap +++ b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap @@ -1,5 +1,1809 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`build chat item tree and get thread messages should get thread messages from tree6, using specified message as target 1`] = ` +[ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105791, + "files": [], + "id": "f9d7ff7c-3a3b-4d9a-a289-657817f4caff", + "message_id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "observation": "", + "position": 1, + "thought": "Sure, I'll play! My number is 57. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105795, + "files": [], + "id": "f61a3fce-37ac-4f9d-9935-95f97e598dfe", + "message_id": "73bbad14-d915-499d-87bf-0df14d40779d", + "observation": "", + "position": 1, + "thought": "I choose 83. What's your next number?", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105799, + "files": [], + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "observation": "", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "I'll go with 112. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "input": { + "inputs": {}, + "query": "99", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + { + "files": [], + "role": "user", + "text": "99", + }, + { + "files": [], + "role": "assistant", + "text": "I'll go with 112. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.49", + "time": "09/11/2024 09:50 PM", + "tokens": 86, + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "99", + "id": "question-4c5d0841-1206-463e-95d8-71f812877658", + "isAnswer": false, + "message_files": [], + "parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d", + }, + ], + "content": "I choose 83. What's your next number?", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "73bbad14-d915-499d-87bf-0df14d40779d", + "input": { + "inputs": {}, + "query": "58", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + ], + "message_files": [], + "more": { + "latency": "1.33", + "time": "09/11/2024 09:49 PM", + "tokens": 68, + }, + "parentMessageId": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "58", + "id": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "isAnswer": false, + "message_files": [], + "parentMessageId": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + }, + ], + "content": "Sure, I'll play! My number is 57. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.56", + "time": "09/11/2024 09:49 PM", + "tokens": 49, + }, + "parentMessageId": "question-ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "Let's play a game, I say a number , and you response me with another bigger, yet random-looking number. I'll start first, 38", + "id": "question-ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "isAnswer": false, + "message_files": [], + }, + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105791, + "files": [], + "id": "f9d7ff7c-3a3b-4d9a-a289-657817f4caff", + "message_id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "observation": "", + "position": 1, + "thought": "Sure, I'll play! My number is 57. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105795, + "files": [], + "id": "f61a3fce-37ac-4f9d-9935-95f97e598dfe", + "message_id": "73bbad14-d915-499d-87bf-0df14d40779d", + "observation": "", + "position": 1, + "thought": "I choose 83. What's your next number?", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105799, + "files": [], + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "observation": "", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "I'll go with 112. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "input": { + "inputs": {}, + "query": "99", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + { + "files": [], + "role": "user", + "text": "99", + }, + { + "files": [], + "role": "assistant", + "text": "I'll go with 112. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.49", + "time": "09/11/2024 09:50 PM", + "tokens": 86, + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "99", + "id": "question-4c5d0841-1206-463e-95d8-71f812877658", + "isAnswer": false, + "message_files": [], + "parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d", + }, + ], + "content": "I choose 83. What's your next number?", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "73bbad14-d915-499d-87bf-0df14d40779d", + "input": { + "inputs": {}, + "query": "58", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + ], + "message_files": [], + "more": { + "latency": "1.33", + "time": "09/11/2024 09:49 PM", + "tokens": 68, + }, + "parentMessageId": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "58", + "id": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "isAnswer": false, + "message_files": [], + "parentMessageId": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + }, + ], + "content": "Sure, I'll play! My number is 57. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.56", + "time": "09/11/2024 09:49 PM", + "tokens": 49, + }, + "nextSibling": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "parentMessageId": "question-ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "prevSibling": undefined, + "siblingCount": 2, + "siblingIndex": 0, + "workflow_run_id": null, + }, + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105795, + "files": [], + "id": "f61a3fce-37ac-4f9d-9935-95f97e598dfe", + "message_id": "73bbad14-d915-499d-87bf-0df14d40779d", + "observation": "", + "position": 1, + "thought": "I choose 83. What's your next number?", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105799, + "files": [], + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "observation": "", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "I'll go with 112. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "input": { + "inputs": {}, + "query": "99", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + { + "files": [], + "role": "user", + "text": "99", + }, + { + "files": [], + "role": "assistant", + "text": "I'll go with 112. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.49", + "time": "09/11/2024 09:50 PM", + "tokens": 86, + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "99", + "id": "question-4c5d0841-1206-463e-95d8-71f812877658", + "isAnswer": false, + "message_files": [], + "parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d", + }, + ], + "content": "I choose 83. What's your next number?", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "73bbad14-d915-499d-87bf-0df14d40779d", + "input": { + "inputs": {}, + "query": "58", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + ], + "message_files": [], + "more": { + "latency": "1.33", + "time": "09/11/2024 09:49 PM", + "tokens": 68, + }, + "parentMessageId": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "58", + "id": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "isAnswer": false, + "message_files": [], + "parentMessageId": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + }, + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105795, + "files": [], + "id": "f61a3fce-37ac-4f9d-9935-95f97e598dfe", + "message_id": "73bbad14-d915-499d-87bf-0df14d40779d", + "observation": "", + "position": 1, + "thought": "I choose 83. What's your next number?", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105799, + "files": [], + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "observation": "", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "I'll go with 112. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "input": { + "inputs": {}, + "query": "99", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + { + "files": [], + "role": "user", + "text": "99", + }, + { + "files": [], + "role": "assistant", + "text": "I'll go with 112. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.49", + "time": "09/11/2024 09:50 PM", + "tokens": 86, + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "99", + "id": "question-4c5d0841-1206-463e-95d8-71f812877658", + "isAnswer": false, + "message_files": [], + "parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d", + }, + ], + "content": "I choose 83. What's your next number?", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "73bbad14-d915-499d-87bf-0df14d40779d", + "input": { + "inputs": {}, + "query": "58", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + ], + "message_files": [], + "more": { + "latency": "1.33", + "time": "09/11/2024 09:49 PM", + "tokens": 68, + }, + "parentMessageId": "question-73bbad14-d915-499d-87bf-0df14d40779d", + "prevSibling": undefined, + "siblingCount": 1, + "siblingIndex": 0, + "workflow_run_id": null, + }, + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105799, + "files": [], + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "observation": "", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "I'll go with 112. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "input": { + "inputs": {}, + "query": "99", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + { + "files": [], + "role": "user", + "text": "99", + }, + { + "files": [], + "role": "assistant", + "text": "I'll go with 112. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.49", + "time": "09/11/2024 09:50 PM", + "tokens": 86, + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "99", + "id": "question-4c5d0841-1206-463e-95d8-71f812877658", + "isAnswer": false, + "message_files": [], + "parentMessageId": "73bbad14-d915-499d-87bf-0df14d40779d", + }, + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105799, + "files": [], + "id": "9730d587-9268-4683-9dd9-91a1cab9510b", + "message_id": "4c5d0841-1206-463e-95d8-71f812877658", + "observation": "", + "position": 1, + "thought": "I'll go with 112. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "I'll go with 112. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "4c5d0841-1206-463e-95d8-71f812877658", + "input": { + "inputs": {}, + "query": "99", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure, I'll play! My number is 57. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "58", + }, + { + "files": [], + "role": "assistant", + "text": "I choose 83. What's your next number?", + }, + { + "files": [], + "role": "user", + "text": "99", + }, + { + "files": [], + "role": "assistant", + "text": "I'll go with 112. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.49", + "time": "09/11/2024 09:50 PM", + "tokens": 86, + }, + "parentMessageId": "question-4c5d0841-1206-463e-95d8-71f812877658", + "prevSibling": undefined, + "siblingCount": 1, + "siblingIndex": 0, + "workflow_run_id": null, + }, +] +`; + +exports[`build chat item tree and get thread messages should get thread messages from tree6, using the last message as target 1`] = ` +[ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105809, + "files": [], + "id": "1019cd79-d141-4f9f-880a-fc1441cfd802", + "message_id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "observation": "", + "position": 1, + "thought": "Sure! My number is 54. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105822, + "files": [], + "id": "0773bec7-b992-4a53-92b2-20ebaeae8798", + "message_id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "observation": "", + "position": 1, + "thought": "My number is 4729. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 4729. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "input": { + "inputs": {}, + "query": "3306", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4729. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.30", + "time": "09/11/2024 09:50 PM", + "tokens": 66, + }, + "parentMessageId": "question-324bce32-c98c-435d-a66b-bac974ebb5ed", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "3306", + "id": "question-324bce32-c98c-435d-a66b-bac974ebb5ed", + "isAnswer": false, + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + }, + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726107812, + "files": [], + "id": "5ca650f3-982c-4399-8b95-9ea241c76707", + "message_id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "observation": "", + "position": 1, + "thought": "My number is 4821. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726111024, + "files": [], + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "observation": "", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 1456. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "input": { + "inputs": {}, + "query": "1003", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "1003", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 1456. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.38", + "time": "09/11/2024 11:17 PM", + "tokens": 86, + }, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "1003", + "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "isAnswer": false, + "message_files": [], + "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc", + }, + ], + "content": "My number is 4821. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "input": { + "inputs": {}, + "query": "3306", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.48", + "time": "09/11/2024 10:23 PM", + "tokens": 66, + }, + "parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "siblingIndex": 1, + "workflow_run_id": null, + }, + ], + "content": "3306", + "id": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "isAnswer": false, + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + }, + ], + "content": "Sure! My number is 54. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.52", + "time": "09/11/2024 09:50 PM", + "tokens": 46, + }, + "parentMessageId": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "siblingIndex": 1, + "workflow_run_id": null, + }, + ], + "content": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + "id": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "isAnswer": false, + "message_files": [], + }, + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105809, + "files": [], + "id": "1019cd79-d141-4f9f-880a-fc1441cfd802", + "message_id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "observation": "", + "position": 1, + "thought": "Sure! My number is 54. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726105822, + "files": [], + "id": "0773bec7-b992-4a53-92b2-20ebaeae8798", + "message_id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "observation": "", + "position": 1, + "thought": "My number is 4729. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 4729. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "input": { + "inputs": {}, + "query": "3306", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4729. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.30", + "time": "09/11/2024 09:50 PM", + "tokens": 66, + }, + "parentMessageId": "question-324bce32-c98c-435d-a66b-bac974ebb5ed", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "3306", + "id": "question-324bce32-c98c-435d-a66b-bac974ebb5ed", + "isAnswer": false, + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + }, + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726107812, + "files": [], + "id": "5ca650f3-982c-4399-8b95-9ea241c76707", + "message_id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "observation": "", + "position": 1, + "thought": "My number is 4821. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726111024, + "files": [], + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "observation": "", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 1456. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "input": { + "inputs": {}, + "query": "1003", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "1003", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 1456. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.38", + "time": "09/11/2024 11:17 PM", + "tokens": 86, + }, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "1003", + "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "isAnswer": false, + "message_files": [], + "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc", + }, + ], + "content": "My number is 4821. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "input": { + "inputs": {}, + "query": "3306", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.48", + "time": "09/11/2024 10:23 PM", + "tokens": 66, + }, + "parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "siblingIndex": 1, + "workflow_run_id": null, + }, + ], + "content": "3306", + "id": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "isAnswer": false, + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + }, + ], + "content": "Sure! My number is 54. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "input": { + "inputs": {}, + "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.52", + "time": "09/11/2024 09:50 PM", + "tokens": 46, + }, + "nextSibling": undefined, + "parentMessageId": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + "prevSibling": "ff4c2b43-48a5-47ad-9dc5-08b34ddba61b", + "siblingCount": 2, + "siblingIndex": 1, + "workflow_run_id": null, + }, + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726107812, + "files": [], + "id": "5ca650f3-982c-4399-8b95-9ea241c76707", + "message_id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "observation": "", + "position": 1, + "thought": "My number is 4821. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726111024, + "files": [], + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "observation": "", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 1456. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "input": { + "inputs": {}, + "query": "1003", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "1003", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 1456. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.38", + "time": "09/11/2024 11:17 PM", + "tokens": 86, + }, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "1003", + "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "isAnswer": false, + "message_files": [], + "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc", + }, + ], + "content": "My number is 4821. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "input": { + "inputs": {}, + "query": "3306", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.48", + "time": "09/11/2024 10:23 PM", + "tokens": 66, + }, + "parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "siblingIndex": 1, + "workflow_run_id": null, + }, + ], + "content": "3306", + "id": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "isAnswer": false, + "message_files": [], + "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd", + }, + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726107812, + "files": [], + "id": "5ca650f3-982c-4399-8b95-9ea241c76707", + "message_id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "observation": "", + "position": 1, + "thought": "My number is 4821. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [ + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726111024, + "files": [], + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "observation": "", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 1456. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "input": { + "inputs": {}, + "query": "1003", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "1003", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 1456. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.38", + "time": "09/11/2024 11:17 PM", + "tokens": 86, + }, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "1003", + "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "isAnswer": false, + "message_files": [], + "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc", + }, + ], + "content": "My number is 4821. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "684b5396-4e91-4043-88e9-aabe48b21acc", + "input": { + "inputs": {}, + "query": "3306", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.48", + "time": "09/11/2024 10:23 PM", + "tokens": 66, + }, + "nextSibling": undefined, + "parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc", + "prevSibling": "324bce32-c98c-435d-a66b-bac974ebb5ed", + "siblingCount": 2, + "siblingIndex": 1, + "workflow_run_id": null, + }, + { + "children": [ + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726111024, + "files": [], + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "observation": "", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 1456. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "input": { + "inputs": {}, + "query": "1003", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "1003", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 1456. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.38", + "time": "09/11/2024 11:17 PM", + "tokens": 86, + }, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "siblingIndex": 0, + "workflow_run_id": null, + }, + ], + "content": "1003", + "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "isAnswer": false, + "message_files": [], + "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc", + }, + { + "agent_thoughts": [ + { + "chain_id": null, + "created_at": 1726111024, + "files": [], + "id": "095cacab-afad-4387-a41d-1662578b8b13", + "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "observation": "", + "position": 1, + "thought": "My number is 1456. Your turn!", + "tool": "", + "tool_input": "", + "tool_labels": {}, + }, + ], + "children": [], + "content": "My number is 1456. Your turn!", + "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80", + "feedbackDisabled": false, + "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c", + "input": { + "inputs": {}, + "query": "1003", + }, + "isAnswer": true, + "log": [ + { + "files": [], + "role": "user", + "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38", + }, + { + "files": [], + "role": "assistant", + "text": "Sure! My number is 54. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "3306", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 4821. Your turn!", + }, + { + "files": [], + "role": "user", + "text": "1003", + }, + { + "files": [], + "role": "assistant", + "text": "My number is 1456. Your turn!", + }, + ], + "message_files": [], + "more": { + "latency": "1.38", + "time": "09/11/2024 11:17 PM", + "tokens": 86, + }, + "nextSibling": undefined, + "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c", + "prevSibling": undefined, + "siblingCount": 1, + "siblingIndex": 0, + "workflow_run_id": null, + }, +] +`; + exports[`build chat item tree and get thread messages should work with real world messages 1`] = ` [ { diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts index e4ac6919845a4..c602ac8a995bd 100644 --- a/web/app/components/base/chat/__tests__/utils.spec.ts +++ b/web/app/components/base/chat/__tests__/utils.spec.ts @@ -248,11 +248,11 @@ describe('build chat item tree and get thread messages', () => { it ('should get thread messages from tree6, using the last message as target', () => { const threadMessages6_1 = getThreadMessages(tree6) - expect(threadMessages6_1).toHaveLength(6) + expect(threadMessages6_1).toMatchSnapshot() }) it ('should get thread messages from tree6, using specified message as target', () => { const threadMessages6_2 = getThreadMessages(tree6, 'ff4c2b43-48a5-47ad-9dc5-08b34ddba61b') - expect(threadMessages6_2).toHaveLength(6) + expect(threadMessages6_2).toMatchSnapshot() }) }) diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index 9c2acd34dacbc..ad63a15283aad 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -130,7 +130,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch let targetNode: ChatItemInTree | undefined // find path to the target message - const stack = tree.reverse().map(rootNode => ({ + const stack = tree.toReversed().map(rootNode => ({ node: rootNode, path: [rootNode], })) From 5606c17f41d10a8aeaa3a59db633e6df174f0e8e Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sat, 14 Sep 2024 14:44:36 +0800 Subject: [PATCH 37/44] chore: change icon of regenerate button --- .../line/general/MaterialSymbolsRefresh.svg | 1 - .../assets/vender/line/general/refresh.svg | 1 + .../line/general/MaterialSymbolsRefresh.json | 25 ------------------- .../src/vender/line/general/Refresh.json | 23 +++++++++++++++++ ...MaterialSymbolsRefresh.tsx => Refresh.tsx} | 4 +-- .../icons/src/vender/line/general/index.ts | 2 +- .../components/base/regenerate-btn/index.tsx | 4 +-- 7 files changed, 29 insertions(+), 31 deletions(-) delete mode 100644 web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg create mode 100644 web/app/components/base/icons/assets/vender/line/general/refresh.svg delete mode 100644 web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json create mode 100644 web/app/components/base/icons/src/vender/line/general/Refresh.json rename web/app/components/base/icons/src/vender/line/general/{MaterialSymbolsRefresh.tsx => Refresh.tsx} (82%) diff --git a/web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg b/web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg deleted file mode 100644 index ebfcb925db9e2..0000000000000 --- a/web/app/components/base/icons/assets/vender/line/general/MaterialSymbolsRefresh.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/app/components/base/icons/assets/vender/line/general/refresh.svg b/web/app/components/base/icons/assets/vender/line/general/refresh.svg new file mode 100644 index 0000000000000..05cf98682734a --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json b/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json deleted file mode 100644 index cf6668ebd89b9..0000000000000 --- a/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "xmlns": "http://www.w3.org/2000/svg", - "width": "24", - "height": "24", - "viewBox": "0 0 24 24" - }, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "fill": "currentColor", - "d": "M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z" - }, - "children": [] - } - ] - }, - "name": "MaterialSymbolsRefresh" -} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/Refresh.json b/web/app/components/base/icons/src/vender/line/general/Refresh.json new file mode 100644 index 0000000000000..128dcb7d4d713 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/Refresh.json @@ -0,0 +1,23 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "viewBox": "0 0 24 24", + "fill": "currentColor" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M5.46257 4.43262C7.21556 2.91688 9.5007 2 12 2C17.5228 2 22 6.47715 22 12C22 14.1361 21.3302 16.1158 20.1892 17.7406L17 12H20C20 7.58172 16.4183 4 12 4C9.84982 4 7.89777 4.84827 6.46023 6.22842L5.46257 4.43262ZM18.5374 19.5674C16.7844 21.0831 14.4993 22 12 22C6.47715 22 2 17.5228 2 12C2 9.86386 2.66979 7.88416 3.8108 6.25944L7 12H4C4 16.4183 7.58172 20 12 20C14.1502 20 16.1022 19.1517 17.5398 17.7716L18.5374 19.5674Z" + }, + "children": [] + } + ] + }, + "name": "Refresh" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.tsx b/web/app/components/base/icons/src/vender/line/general/Refresh.tsx similarity index 82% rename from web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.tsx rename to web/app/components/base/icons/src/vender/line/general/Refresh.tsx index d9b57eae62c1f..96641f1c4243b 100644 --- a/web/app/components/base/icons/src/vender/line/general/MaterialSymbolsRefresh.tsx +++ b/web/app/components/base/icons/src/vender/line/general/Refresh.tsx @@ -2,7 +2,7 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import data from './MaterialSymbolsRefresh.json' +import data from './Refresh.json' import IconBase from '@/app/components/base/icons/IconBase' import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' @@ -11,6 +11,6 @@ const Icon = React.forwardRef, Omit ) -Icon.displayName = 'MaterialSymbolsRefresh' +Icon.displayName = 'Refresh' export default Icon diff --git a/web/app/components/base/icons/src/vender/line/general/index.ts b/web/app/components/base/icons/src/vender/line/general/index.ts index 4833eb685f7fd..b5c7a7bbc1d9a 100644 --- a/web/app/components/base/icons/src/vender/line/general/index.ts +++ b/web/app/components/base/icons/src/vender/line/general/index.ts @@ -1,4 +1,3 @@ -export { default as MaterialSymbolsRefresh } from './MaterialSymbolsRefresh' export { default as AtSign } from './AtSign' export { default as Bookmark } from './Bookmark' export { default as CheckDone01 } from './CheckDone01' @@ -19,6 +18,7 @@ export { default as Menu01 } from './Menu01' export { default as Pin01 } from './Pin01' export { default as Pin02 } from './Pin02' export { default as Plus02 } from './Plus02' +export { default as Refresh } from './Refresh' export { default as Settings01 } from './Settings01' export { default as Settings04 } from './Settings04' export { default as Target04 } from './Target04' diff --git a/web/app/components/base/regenerate-btn/index.tsx b/web/app/components/base/regenerate-btn/index.tsx index 99f3eb91c64b8..aaf0206df609d 100644 --- a/web/app/components/base/regenerate-btn/index.tsx +++ b/web/app/components/base/regenerate-btn/index.tsx @@ -1,6 +1,6 @@ 'use client' import { t } from 'i18next' -import { MaterialSymbolsRefresh } from '../icons/src/vender/line/general' +import { Refresh } from '../icons/src/vender/line/general' import Tooltip from '@/app/components/base/tooltip' type Props = { @@ -21,7 +21,7 @@ const RegenerateBtn = ({ className, onClick }: Props) => { boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)', }} > - + From c4a13c7ec6824d2ea014298a1d947ffe4c879555 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sat, 14 Sep 2024 16:55:42 +0800 Subject: [PATCH 38/44] chore: only show latest thread messages in log list and chatflow run history --- api/controllers/console/app/message.py | 2 - api/fields/conversation_fields.py | 1 + web/app/components/app/log/list.tsx | 149 ++++++++++-------- web/app/components/base/chat/chat/type.ts | 1 + .../workflow/panel/chat-record/index.tsx | 79 ++++++---- web/models/log.ts | 1 + 6 files changed, 138 insertions(+), 95 deletions(-) diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index fe06201982374..2fba3e0af02be 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -105,8 +105,6 @@ 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) diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 9207314fc2132..3dcd88d1de31d 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -75,6 +75,7 @@ def format(self, value): "metadata": fields.Raw(attribute="message_metadata_dict"), "status": fields.String, "error": fields.String, + "parent_message_id": fields.String, } feedback_stat_fields = {"like": fields.Integer, "dislike": fields.Integer} diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 18e46ef53b385..7f074fe48cecf 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -16,6 +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 s from './style.module.css' import VarPanel from './var-panel' import cn from '@/utils/classnames' @@ -81,72 +82,92 @@ const PARAM_MAP = { frequency_penalty: 'Frequency Penalty', } -// Format interface data for easy display -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') || [], - }) - 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, - } +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, } + } - 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 - })(), - }) + return undefined + })(), + parentMessageId: `question-${item.id}`, }) - return newChatList + 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, + }) +} + +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() } // const displayedParams = CompletionParams.slice(0, -2) @@ -171,6 +192,7 @@ function DetailPanel([]) + const fetchedMessages = useRef([]) const [hasMore, setHasMore] = useState(true) const [varValues, setVarValues] = useState>({}) const fetchData = async () => { @@ -192,7 +214,8 @@ function DetailPanel - : items.length < 8 + : (items.length < 8 && !hasMore) ?
file.belongs_to === 'assistant') || [], + workflow_run_id: item.workflow_run_id, + }) + newChatList.push({ + id: `question-${item.id}`, + content: item.query, + isAnswer: false, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], + }) +} + +function getFormattedChatList(messages: any[]) { + const newChatList: ChatItem[] = [] + let nextMessageId = null + for (const item of messages) { + if (!item.parent_message_id) { + appendQAToChatList(newChatList, item) + break + } + + if (!nextMessageId) { + appendQAToChatList(newChatList, item) + nextMessageId = item.parent_message_id + } + else { + if (item.id === nextMessageId || nextMessageId === UUID_NIL) { + appendQAToChatList(newChatList, item) + nextMessageId = item.parent_message_id + } + } + } + return newChatList.reverse() +} const ChatRecord = () => { const [fetched, setFetched] = useState(false) - const [chatList, setChatList] = useState([]) + const [chatList, setChatList] = useState([]) const appDetail = useAppStore(s => s.appDetail) const workflowStore = useWorkflowStore() const { handleLoadBackupDraft } = useWorkflowRun() const historyWorkflowData = useStore(s => s.historyWorkflowData) const currentConversationID = historyWorkflowData?.conversation_id - const chatMessageList = useMemo(() => { - const res: ChatItem[] = [] - if (chatList.length) { - chatList.forEach((item: any) => { - res.push({ - id: `question-${item.id}`, - content: item.query, - isAnswer: false, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], - }) - res.push({ - id: item.id, - content: item.answer, - feedback: item.feedback, - isAnswer: true, - citation: item.metadata?.retriever_resources, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], - workflow_run_id: item.workflow_run_id, - }) - }) - } - return res - }, [chatList]) - const handleFetchConversationMessages = useCallback(async () => { if (appDetail && currentConversationID) { try { setFetched(false) const res = await fetchConversationMessages(appDetail.id, currentConversationID) - setFetched(true) - setChatList((res as any).data) + setChatList(getFormattedChatList((res as any).data)) } catch (e) { - + console.error(e) + } + finally { + setFetched(true) } } }, [appDetail, currentConversationID]) @@ -101,7 +120,7 @@ const ChatRecord = () => { config={{ supportCitationHitInfo: true, } as any} - chatList={chatMessageList} + chatList={chatList} chatContainerClassName='px-4' chatContainerInnerClassName='pt-6 w-full max-w-full mx-auto' chatFooterClassName='px-4 rounded-b-2xl' diff --git a/web/models/log.ts b/web/models/log.ts index 8da1c4cf4e826..dc557bfe2142b 100644 --- a/web/models/log.ts +++ b/web/models/log.ts @@ -106,6 +106,7 @@ export type MessageContent = { metadata: Metadata agent_thoughts: any[] // TODO workflow_run_id: string + parent_message_id: string | null } export type CompletionConversationGeneralDetail = { From e07b550b8a31f5530b67869fe799a507e10890de Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sat, 14 Sep 2024 17:05:27 +0800 Subject: [PATCH 39/44] chore: lint python code --- api/core/memory/token_buffer_memory.py | 3 ++- api/core/prompt/utils/extract_thread_messages.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/core/memory/token_buffer_memory.py b/api/core/memory/token_buffer_memory.py index d55a7a761d03f..60b36c50f00d3 100644 --- a/api/core/memory/token_buffer_memory.py +++ b/api/core/memory/token_buffer_memory.py @@ -55,7 +55,8 @@ def get_history_prompt_messages( messages = query.limit(message_limit).all() - # instead of all messages from the conversation, we only need to extract messages that belong to the thread of last message + # instead of all messages from the conversation, we only need to extract messages + # that belong to the thread of last message thread_messages = extract_thread_messages(messages) thread_messages.pop(0) messages = list(reversed(thread_messages)) diff --git a/api/core/prompt/utils/extract_thread_messages.py b/api/core/prompt/utils/extract_thread_messages.py index 3bd38dd30b2ba..e8b626499fce1 100644 --- a/api/core/prompt/utils/extract_thread_messages.py +++ b/api/core/prompt/utils/extract_thread_messages.py @@ -15,7 +15,7 @@ def extract_thread_messages(messages: list[dict]) -> list[dict]: thread_messages.append(message) next_message = message.parent_message_id else: - if message.id == next_message or next_message == UUID_NIL: + if next_message in {message.id, UUID_NIL}: thread_messages.append(message) next_message = message.parent_message_id From 2dfda58d79b18c6ee1f9fb82e8dd978fee4c1fed Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sun, 22 Sep 2024 12:13:43 +0800 Subject: [PATCH 40/44] Revert "chore: only show latest thread messages in log list and chatflow run history" This reverts commit c4a13c7ec6824d2ea014298a1d947ffe4c879555. --- api/controllers/console/app/message.py | 2 + api/fields/conversation_fields.py | 1 - web/app/components/app/log/list.tsx | 149 ++++++++---------- web/app/components/base/chat/chat/type.ts | 1 - .../workflow/panel/chat-record/index.tsx | 79 ++++------ web/models/log.ts | 1 - 6 files changed, 95 insertions(+), 138 deletions(-) diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index 2fba3e0af02be..fe06201982374 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -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) diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 3dcd88d1de31d..9207314fc2132 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -75,7 +75,6 @@ def format(self, value): "metadata": fields.Raw(attribute="message_metadata_dict"), "status": fields.String, "error": fields.String, - "parent_message_id": fields.String, } feedback_stat_fields = {"like": fields.Integer, "dislike": fields.Integer} diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 149e877fa4ac2..caec10c4f714b 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -16,7 +16,6 @@ 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 s from './style.module.css' import VarPanel from './var-panel' import cn from '@/utils/classnames' @@ -82,92 +81,72 @@ 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, +// Format interface data for easy display +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') || [], + }) + 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 + })(), + }) }) -} - -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) @@ -192,7 +171,6 @@ function DetailPanel([]) - const fetchedMessages = useRef([]) const [hasMore, setHasMore] = useState(true) const [varValues, setVarValues] = useState>({}) const fetchData = async () => { @@ -214,8 +192,7 @@ function DetailPanel
- : (items.length < 8 && !hasMore) + : items.length < 8 ?
file.belongs_to === 'assistant') || [], - workflow_run_id: item.workflow_run_id, - }) - newChatList.push({ - id: `question-${item.id}`, - content: item.query, - isAnswer: false, - message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], - }) -} - -function getFormattedChatList(messages: any[]) { - const newChatList: ChatItem[] = [] - let nextMessageId = null - for (const item of messages) { - if (!item.parent_message_id) { - appendQAToChatList(newChatList, item) - break - } - - if (!nextMessageId) { - appendQAToChatList(newChatList, item) - nextMessageId = item.parent_message_id - } - else { - if (item.id === nextMessageId || nextMessageId === UUID_NIL) { - appendQAToChatList(newChatList, item) - nextMessageId = item.parent_message_id - } - } - } - return newChatList.reverse() -} const ChatRecord = () => { const [fetched, setFetched] = useState(false) - const [chatList, setChatList] = useState([]) + const [chatList, setChatList] = useState([]) const appDetail = useAppStore(s => s.appDetail) const workflowStore = useWorkflowStore() const { handleLoadBackupDraft } = useWorkflowRun() const historyWorkflowData = useStore(s => s.historyWorkflowData) const currentConversationID = historyWorkflowData?.conversation_id + const chatMessageList = useMemo(() => { + const res: ChatItem[] = [] + if (chatList.length) { + chatList.forEach((item: any) => { + res.push({ + id: `question-${item.id}`, + content: item.query, + isAnswer: false, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], + }) + res.push({ + id: item.id, + content: item.answer, + feedback: item.feedback, + isAnswer: true, + citation: item.metadata?.retriever_resources, + message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], + workflow_run_id: item.workflow_run_id, + }) + }) + } + return res + }, [chatList]) + const handleFetchConversationMessages = useCallback(async () => { if (appDetail && currentConversationID) { try { setFetched(false) const res = await fetchConversationMessages(appDetail.id, currentConversationID) - setChatList(getFormattedChatList((res as any).data)) + setFetched(true) + setChatList((res as any).data) } catch (e) { - console.error(e) - } - finally { - setFetched(true) + } } }, [appDetail, currentConversationID]) @@ -120,7 +101,7 @@ const ChatRecord = () => { config={{ supportCitationHitInfo: true, } as any} - chatList={chatList} + chatList={chatMessageList} chatContainerClassName='px-4' chatContainerInnerClassName='pt-6 w-full max-w-full mx-auto' chatFooterClassName='px-4 rounded-b-2xl' diff --git a/web/models/log.ts b/web/models/log.ts index dc557bfe2142b..8da1c4cf4e826 100644 --- a/web/models/log.ts +++ b/web/models/log.ts @@ -106,7 +106,6 @@ export type MessageContent = { metadata: Metadata agent_thoughts: any[] // TODO workflow_run_id: string - parent_message_id: string | null } export type CompletionConversationGeneralDetail = { From b68c1766ac36ce62aaf77a07ea1204374aa4ab66 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sun, 22 Sep 2024 12:20:41 +0800 Subject: [PATCH 41/44] Revert "chore: only show latest thread messages in log list and chatflow run history" This reverts commit c4a13c7ec6824d2ea014298a1d947ffe4c879555. --- api/fields/conversation_fields.py | 1 - web/app/components/app/log/list.tsx | 1 - web/models/log.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 3dcd88d1de31d..9207314fc2132 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -75,7 +75,6 @@ def format(self, value): "metadata": fields.Raw(attribute="message_metadata_dict"), "status": fields.String, "error": fields.String, - "parent_message_id": fields.String, } feedback_stat_fields = {"like": fields.Integer, "dislike": fields.Integer} diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index d7270e6361250..d9cae7ac36ad0 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -174,7 +174,6 @@ function DetailPanel>({}) diff --git a/web/models/log.ts b/web/models/log.ts index 7b1377b3740ab..dc557bfe2142b 100644 --- a/web/models/log.ts +++ b/web/models/log.ts @@ -106,7 +106,6 @@ export type MessageContent = { metadata: Metadata agent_thoughts: any[] // TODO workflow_run_id: string - is_regenerated: boolean parent_message_id: string | null } From 94c7b10eea53c439cd50659d4a5a20cedb61dc19 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Sun, 22 Sep 2024 13:46:12 +0800 Subject: [PATCH 42/44] fix: api not returning parent_message_id --- api/fields/conversation_fields.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 9207314fc2132..3dcd88d1de31d 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -75,6 +75,7 @@ def format(self, value): "metadata": fields.Raw(attribute="message_metadata_dict"), "status": fields.String, "error": fields.String, + "parent_message_id": fields.String, } feedback_stat_fields = {"like": fields.Integer, "dislike": fields.Integer} From 4a8ce56fbc46bf5fdeccf55ce87a2d5b3fe8b7b1 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Wed, 25 Sep 2024 11:24:18 +0800 Subject: [PATCH 43/44] fix: message list order not correct --- api/controllers/console/app/message.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index 2fba3e0af02be..fe06201982374 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -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) From e5fc46d1cd3193d3a0be3d2d6ad18a8696803d04 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Tue, 22 Oct 2024 16:23:11 +0800 Subject: [PATCH 44/44] fix: frontend test failure --- web/__mocks__/mime.js | 0 web/babel.config.js | 3 +++ web/jest.config.ts | 1 + 3 files changed, 4 insertions(+) create mode 100644 web/__mocks__/mime.js create mode 100644 web/babel.config.js diff --git a/web/__mocks__/mime.js b/web/__mocks__/mime.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/web/babel.config.js b/web/babel.config.js new file mode 100644 index 0000000000000..e6196ef3d3f53 --- /dev/null +++ b/web/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['@babel/preset-env'], +} diff --git a/web/jest.config.ts b/web/jest.config.ts index d7c68308cb8c5..232f90252d9d8 100644 --- a/web/jest.config.ts +++ b/web/jest.config.ts @@ -99,6 +99,7 @@ const config: Config = { // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module moduleNameMapper: { '^@/components/(.*)$': '/components/$1', + '^lodash-es$': 'lodash', }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader