Commit b70c57fa by Archer Committed by GitHub

chatbox tip (#7341)

parent 6c12f1ab
Subproject commit e7951abea8762970704556d99bbd5697da95f11c Subproject commit d0c7829e79ec8040d92537970e22b0d6feb43427
...@@ -47,6 +47,7 @@ import type { StartChatFnProps, generatingMessageProps } from '../../type'; ...@@ -47,6 +47,7 @@ import type { StartChatFnProps, generatingMessageProps } from '../../type';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type { ChatAuthTargetInput } from '@/web/core/chat/utils'; import type { ChatAuthTargetInput } from '@/web/core/chat/utils';
import { useChatAuthApiTarget } from '@/web/core/chat/utils'; import { useChatAuthApiTarget } from '@/web/core/chat/utils';
import { getChatItemErrorText } from '@/global/core/chat/utils';
type HumanChatSiteItemType = Extract<ChatSiteItemType, { obj: ChatRoleEnum.Human }>; type HumanChatSiteItemType = Extract<ChatSiteItemType, { obj: ChatRoleEnum.Human }>;
...@@ -805,12 +806,8 @@ export const useChatGenerate = ({ ...@@ -805,12 +806,8 @@ export const useChatGenerate = ({
const responseData = mergeNodeResponseDataByIdAndParent(item.responseData || []); const responseData = mergeNodeResponseDataByIdAndParent(item.responseData || []);
if (!abortSignal?.signal?.aborted) { if (!abortSignal?.signal?.aborted) {
const uncaughtErr = responseData.find((r) => r.error && !r.errorCaptured)?.error; const errorText = getChatItemErrorText(responseData)?.errorText;
const lastUncapturedErrorText = [...responseData] const errorMsg = errorText ? t(errorText) : undefined;
.reverse()
.find((r) => r.errorText && !r.errorCaptured)?.errorText;
const err = uncaughtErr ?? lastUncapturedErrorText;
const errorMsg = err ? t(getErrText(err)) : undefined;
return { return {
...item, ...item,
......
...@@ -427,7 +427,11 @@ export const WorkflowResultRows = ({ ...@@ -427,7 +427,11 @@ export const WorkflowResultRows = ({
/> />
)} )}
<Row label={t('chat:response.tool_params_result')} value={activeModule.toolParamsResult} /> <Row label={t('chat:response.tool_params_result')} value={activeModule.toolParamsResult} />
<Row label={t('chat:response.tool_result')} value={activeModule.toolRes} /> <Row
label={t('chat:response.tool_result')}
value={activeModule.toolRes}
renderStringAsMarkdown={false}
/>
</> </>
); );
}; };
...@@ -46,13 +46,15 @@ export const Row = ({ ...@@ -46,13 +46,15 @@ export const Row = ({
value, value,
rawDom, rawDom,
rawDomBoxProps, rawDomBoxProps,
contentBoxProps contentBoxProps,
renderStringAsMarkdown = true
}: { }: {
label: string; label: string;
value?: string | number | boolean | object; value?: string | number | boolean | object;
rawDom?: ReactNode; rawDom?: ReactNode;
rawDomBoxProps?: BoxProps; rawDomBoxProps?: BoxProps;
contentBoxProps?: BoxProps; contentBoxProps?: BoxProps;
renderStringAsMarkdown?: boolean;
}) => { }) => {
const { t } = useSafeTranslation(); const { t } = useSafeTranslation();
const val = value || rawDom; const val = value || rawDom;
...@@ -63,10 +65,10 @@ export const Row = ({ ...@@ -63,10 +65,10 @@ export const Row = ({
return `~~~json\n${JSON.stringify(value, null, 2)}\n~~~`; return `~~~json\n${JSON.stringify(value, null, 2)}\n~~~`;
} }
if (typeof value === 'string') { if (typeof value === 'string') {
return t(value); return renderStringAsMarkdown ? t(value) : value;
} }
return `${value}`; return `${value}`;
}, [isObject, t, value]); }, [isObject, renderStringAsMarkdown, t, value]);
if (rawDom) { if (rawDom) {
return ( return (
...@@ -99,7 +101,13 @@ export const Row = ({ ...@@ -99,7 +101,13 @@ export const Row = ({
...contentBoxProps?.sx ...contentBoxProps?.sx
}} }}
> >
{typeof value === 'string' && !renderStringAsMarkdown ? (
<Box whiteSpace={'pre-wrap'} overflowWrap={'anywhere'}>
{formatValue}
</Box>
) : (
<Markdown className={markdownStyles.markdown} source={formatValue} /> <Markdown className={markdownStyles.markdown} source={formatValue} />
)}
</Box> </Box>
</RowRender> </RowRender>
); );
......
...@@ -3,6 +3,7 @@ import { Box, Flex, useDisclosure } from '@chakra-ui/react'; ...@@ -3,6 +3,7 @@ import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants'; import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import Avatar from '@fastgpt/web/components/common/Avatar'; import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIconButton from '@fastgpt/web/components/common/Icon/button'; import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation'; import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation';
import type { SideTabItemType } from './types'; import type { SideTabItemType } from './types';
...@@ -38,6 +39,7 @@ const NormalSideTabItem = ({ ...@@ -38,6 +39,7 @@ const NormalSideTabItem = ({
}) => { }) => {
const { t } = useSafeTranslation(); const { t } = useSafeTranslation();
const leftPad = getSideTabLeftPadding(index); const leftPad = getSideTabLeftPadding(index);
const nodeName = t(sideBarItem.moduleName as any, sideBarItem.moduleNameArgs);
return ( return (
<Flex <Flex
...@@ -68,6 +70,7 @@ const NormalSideTabItem = ({ ...@@ -68,6 +70,7 @@ const NormalSideTabItem = ({
borderRadius={'4px'} borderRadius={'4px'}
/> />
<Box ml={2} flex={'1 1 0'} minW={0}> <Box ml={2} flex={'1 1 0'} minW={0}>
<MyTooltip label={nodeName} showOnlyWhenOverflow>
<Box <Box
fontSize={'12px'} fontSize={'12px'}
lineHeight={'16px'} lineHeight={'16px'}
...@@ -78,8 +81,9 @@ const NormalSideTabItem = ({ ...@@ -78,8 +81,9 @@ const NormalSideTabItem = ({
whiteSpace={'nowrap'} whiteSpace={'nowrap'}
textOverflow={'ellipsis'} textOverflow={'ellipsis'}
> >
{t(sideBarItem.moduleName as any, sideBarItem.moduleNameArgs)} {nodeName}
</Box> </Box>
</MyTooltip>
<Box <Box
fontSize={'11px'} fontSize={'11px'}
lineHeight={'16px'} lineHeight={'16px'}
......
...@@ -53,6 +53,27 @@ const getNodeErrorText = (item: ChatHistoryItemResType) => { ...@@ -53,6 +53,27 @@ const getNodeErrorText = (item: ChatHistoryItemResType) => {
}; };
/** /**
* 按历史记录加载口径提取聊天气泡错误。
*
* 只有根节点失败才代表本轮对话失败;带 parentId 的响应和内嵌 children 都属于
* ToolCall/Agent 的工具执行详情,错误会作为工具结果交回上层,不应提升为聊天错误。
*/
export const getChatItemErrorText = (
responseData: ChatHistoryItemResType[] = []
): ErrorTextItemType | undefined =>
responseData.reduce<ErrorTextItemType | undefined>((errorText, item) => {
if (item.parentId) return errorText;
const nodeErrorText = getNodeErrorText(item);
if (!nodeErrorText) return errorText;
return {
moduleName: item.moduleName,
errorText: nodeErrorText
};
}, undefined);
/**
* 聊天列表预览只需要从 nodeResponse rows 中提取标签和错误摘要。 * 聊天列表预览只需要从 nodeResponse rows 中提取标签和错误摘要。
* *
* 不包含 `historyPreview` 和 `error`:前者只用于详情弹窗,后者可能是大对象;列表错误展示优先 * 不包含 `historyPreview` 和 `error`:前者只用于详情弹窗,后者可能是大对象;列表错误展示优先
...@@ -129,8 +150,7 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) { ...@@ -129,8 +150,7 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) {
llmModuleAccount, llmModuleAccount,
historyPreviewLength, historyPreviewLength,
totalQuoteList, totalQuoteList,
toolCiteLinks, toolCiteLinks
errorText
} = flatResData.reduce( } = flatResData.reduce(
(acc, item) => { (acc, item) => {
// LLM // LLM
...@@ -164,14 +184,6 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) { ...@@ -164,14 +184,6 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) {
} }
} }
const nodeErrorText = getNodeErrorText(item);
if (nodeErrorText) {
acc.errorText = {
moduleName: item.moduleName,
errorText: nodeErrorText
};
}
return acc; return acc;
}, },
{ {
...@@ -179,11 +191,11 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) { ...@@ -179,11 +191,11 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) {
totalQuoteList: [] as SearchDataResponseQuoteListItemType[], totalQuoteList: [] as SearchDataResponseQuoteListItemType[],
toolCiteLinks: [] as ToolCiteLinksType[], toolCiteLinks: [] as ToolCiteLinksType[],
linkDedupe: new Set<string>(), linkDedupe: new Set<string>(),
errorText: undefined as ErrorTextItemType | undefined,
llmModuleAccount: 0, llmModuleAccount: 0,
historyPreviewLength: undefined as number | undefined historyPreviewLength: undefined as number | undefined
} }
); );
const errorText = getChatItemErrorText(historyItem.responseData);
// Filter quote list to only include citations actually referenced in the response text // Filter quote list to only include citations actually referenced in the response text
const responseText = historyItem.value.map((v) => v.text?.content || '').join(''); const responseText = historyItem.value.map((v) => v.text?.content || '').join('');
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
SANDBOX_SHELL_TOOL_NAME SANDBOX_SHELL_TOOL_NAME
} from '@fastgpt/global/core/ai/sandbox/tools'; } from '@fastgpt/global/core/ai/sandbox/tools';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils'; import { addStatisticalDataToHistoryItem, getChatItemErrorText } from '@/global/core/chat/utils';
describe('addStatisticalDataToHistoryItem', () => { describe('addStatisticalDataToHistoryItem', () => {
it('marks sandbox usage from streaming tool call cards before responseData is loaded', () => { it('marks sandbox usage from streaming tool call cards before responseData is loaded', () => {
...@@ -364,6 +364,83 @@ describe('addStatisticalDataToHistoryItem', () => { ...@@ -364,6 +364,83 @@ describe('addStatisticalDataToHistoryItem', () => {
}); });
}); });
it('ignores a failed tool child when later tool calls complete', () => {
const responseData: NonNullable<ChatItemMiniType['responseData']> = [
{
id: 'agent-response',
nodeId: 'agent-node',
moduleName: 'Agent',
moduleType: FlowNodeTypeEnum.agent
},
{
id: 'tool-response-1',
parentId: 'agent-response',
nodeId: 'tool-node-1',
moduleName: 'Tool 1',
moduleType: FlowNodeTypeEnum.tool,
toolRes: 'done'
},
{
id: 'tool-response-2',
parentId: 'agent-response',
nodeId: 'tool-node-2',
moduleName: 'Tool 2',
moduleType: FlowNodeTypeEnum.tool,
error: 'tool failed'
},
{
id: 'tool-response-3',
parentId: 'agent-response',
nodeId: 'tool-node-3',
moduleName: 'Tool 3',
moduleType: FlowNodeTypeEnum.tool,
toolRes: 'done'
},
{
id: 'tool-response-4',
parentId: 'agent-response',
nodeId: 'tool-node-4',
moduleName: 'Tool 4',
moduleType: FlowNodeTypeEnum.tool,
toolRes: 'done'
}
];
const historyItem: ChatItemMiniType = {
obj: ChatRoleEnum.AI,
value: [{ text: { content: 'done' } }],
responseData
};
expect(getChatItemErrorText(responseData)).toBeUndefined();
expect(addStatisticalDataToHistoryItem(historyItem).errorText).toBeUndefined();
});
it('ignores tool errors nested inside an agent response', () => {
const historyItem: ChatItemMiniType = {
obj: ChatRoleEnum.AI,
value: [{ text: { content: 'done' } }],
responseData: [
{
id: 'agent-response',
nodeId: 'agent-node',
moduleName: 'Agent',
moduleType: FlowNodeTypeEnum.agent,
childrenResponses: [
{
id: 'tool-response',
nodeId: 'tool-node',
moduleName: 'Tool',
moduleType: FlowNodeTypeEnum.tool,
errorText: 'tool failed'
}
]
}
]
};
expect(addStatisticalDataToHistoryItem(historyItem).errorText).toBeUndefined();
});
it('does not use HTTP result error as chat bubble error text when node error is absent', () => { it('does not use HTTP result error as chat bubble error text when node error is absent', () => {
const historyItem: ChatItemMiniType = { const historyItem: ChatItemMiniType = {
obj: ChatRoleEnum.AI, obj: ChatRoleEnum.AI,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment