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';
import { cloneDeep } from 'lodash-es';
import type { ChatAuthTargetInput } 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 }>;
......@@ -805,12 +806,8 @@ export const useChatGenerate = ({
const responseData = mergeNodeResponseDataByIdAndParent(item.responseData || []);
if (!abortSignal?.signal?.aborted) {
const uncaughtErr = responseData.find((r) => r.error && !r.errorCaptured)?.error;
const lastUncapturedErrorText = [...responseData]
.reverse()
.find((r) => r.errorText && !r.errorCaptured)?.errorText;
const err = uncaughtErr ?? lastUncapturedErrorText;
const errorMsg = err ? t(getErrText(err)) : undefined;
const errorText = getChatItemErrorText(responseData)?.errorText;
const errorMsg = errorText ? t(errorText) : undefined;
return {
...item,
......
......@@ -427,7 +427,11 @@ export const WorkflowResultRows = ({
/>
)}
<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 = ({
value,
rawDom,
rawDomBoxProps,
contentBoxProps
contentBoxProps,
renderStringAsMarkdown = true
}: {
label: string;
value?: string | number | boolean | object;
rawDom?: ReactNode;
rawDomBoxProps?: BoxProps;
contentBoxProps?: BoxProps;
renderStringAsMarkdown?: boolean;
}) => {
const { t } = useSafeTranslation();
const val = value || rawDom;
......@@ -63,10 +65,10 @@ export const Row = ({
return `~~~json\n${JSON.stringify(value, null, 2)}\n~~~`;
}
if (typeof value === 'string') {
return t(value);
return renderStringAsMarkdown ? t(value) : value;
}
return `${value}`;
}, [isObject, t, value]);
}, [isObject, renderStringAsMarkdown, t, value]);
if (rawDom) {
return (
......@@ -99,7 +101,13 @@ export const Row = ({
...contentBoxProps?.sx
}}
>
<Markdown className={markdownStyles.markdown} source={formatValue} />
{typeof value === 'string' && !renderStringAsMarkdown ? (
<Box whiteSpace={'pre-wrap'} overflowWrap={'anywhere'}>
{formatValue}
</Box>
) : (
<Markdown className={markdownStyles.markdown} source={formatValue} />
)}
</Box>
</RowRender>
);
......
......@@ -3,6 +3,7 @@ import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import Avatar from '@fastgpt/web/components/common/Avatar';
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 type { SideTabItemType } from './types';
......@@ -38,6 +39,7 @@ const NormalSideTabItem = ({
}) => {
const { t } = useSafeTranslation();
const leftPad = getSideTabLeftPadding(index);
const nodeName = t(sideBarItem.moduleName as any, sideBarItem.moduleNameArgs);
return (
<Flex
......@@ -68,18 +70,20 @@ const NormalSideTabItem = ({
borderRadius={'4px'}
/>
<Box ml={2} flex={'1 1 0'} minW={0}>
<Box
fontSize={'12px'}
lineHeight={'16px'}
fontWeight={500}
color={'myGray.900'}
letterSpacing={'0.5px'}
overflow={'hidden'}
whiteSpace={'nowrap'}
textOverflow={'ellipsis'}
>
{t(sideBarItem.moduleName as any, sideBarItem.moduleNameArgs)}
</Box>
<MyTooltip label={nodeName} showOnlyWhenOverflow>
<Box
fontSize={'12px'}
lineHeight={'16px'}
fontWeight={500}
color={'myGray.900'}
letterSpacing={'0.5px'}
overflow={'hidden'}
whiteSpace={'nowrap'}
textOverflow={'ellipsis'}
>
{nodeName}
</Box>
</MyTooltip>
<Box
fontSize={'11px'}
lineHeight={'16px'}
......
......@@ -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 中提取标签和错误摘要。
*
* 不包含 `historyPreview` 和 `error`:前者只用于详情弹窗,后者可能是大对象;列表错误展示优先
......@@ -129,8 +150,7 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) {
llmModuleAccount,
historyPreviewLength,
totalQuoteList,
toolCiteLinks,
errorText
toolCiteLinks
} = flatResData.reduce(
(acc, item) => {
// LLM
......@@ -164,14 +184,6 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) {
}
}
const nodeErrorText = getNodeErrorText(item);
if (nodeErrorText) {
acc.errorText = {
moduleName: item.moduleName,
errorText: nodeErrorText
};
}
return acc;
},
{
......@@ -179,11 +191,11 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemMiniType) {
totalQuoteList: [] as SearchDataResponseQuoteListItemType[],
toolCiteLinks: [] as ToolCiteLinksType[],
linkDedupe: new Set<string>(),
errorText: undefined as ErrorTextItemType | undefined,
llmModuleAccount: 0,
historyPreviewLength: undefined as number | undefined
}
);
const errorText = getChatItemErrorText(historyItem.responseData);
// Filter quote list to only include citations actually referenced in the response text
const responseText = historyItem.value.map((v) => v.text?.content || '').join('');
......
......@@ -6,7 +6,7 @@ import {
SANDBOX_SHELL_TOOL_NAME
} from '@fastgpt/global/core/ai/sandbox/tools';
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', () => {
it('marks sandbox usage from streaming tool call cards before responseData is loaded', () => {
......@@ -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', () => {
const historyItem: ChatItemMiniType = {
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