mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-21 09:32:24 +00:00
🧠 fix: Format Reasoning Exports (#14116)
* fix: Format reasoning exports * chore: Sort export imports * fix: Refine reasoning export formatting * chore: Simplify export test localization * fix: Handle export summary labels
This commit is contained in:
parent
13a01dee9b
commit
1f7b38e972
4 changed files with 487 additions and 120 deletions
239
client/src/hooks/Conversations/__tests__/format.test.ts
Normal file
239
client/src/hooks/Conversations/__tests__/format.test.ts
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import { ContentTypes, ToolCallTypes } from 'librechat-data-provider';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import type { ExportableContentPart, MessageContentExport } from '../format';
|
||||
import type { LocalizeFunction } from '~/common';
|
||||
import { formatMessageText, formatMessageContent, handledExportContentTypes } from '../format';
|
||||
|
||||
const translations: Record<string, string> = {
|
||||
com_endpoint_thinking: 'Thinking',
|
||||
com_ui_export_agent_update: 'Agent Update',
|
||||
com_ui_export_audio: 'Audio',
|
||||
com_ui_export_file_search: 'File Search',
|
||||
com_ui_export_image: 'Image',
|
||||
com_ui_export_retrieval: 'Retrieval',
|
||||
com_ui_export_summary: 'Summary',
|
||||
com_ui_export_tool: 'Tool',
|
||||
com_ui_export_video: 'Video',
|
||||
com_ui_run_code: 'Run code',
|
||||
};
|
||||
|
||||
const localize: LocalizeFunction = ((key: string) => translations[key] ?? key) as LocalizeFunction;
|
||||
|
||||
const imageFile = {
|
||||
file_id: 'file-1',
|
||||
filename: 'chart.png',
|
||||
filepath: '/images/chart.png',
|
||||
height: 480,
|
||||
width: 640,
|
||||
};
|
||||
|
||||
const contentByType: Record<ContentTypes, ExportableContentPart> = {
|
||||
[ContentTypes.TEXT]: {
|
||||
type: ContentTypes.TEXT,
|
||||
text: 'Visible answer',
|
||||
},
|
||||
[ContentTypes.THINK]: {
|
||||
type: ContentTypes.THINK,
|
||||
think: 'Reasoning step',
|
||||
},
|
||||
[ContentTypes.TEXT_DELTA]: {
|
||||
type: ContentTypes.TEXT_DELTA,
|
||||
text_delta: 'Streaming text',
|
||||
},
|
||||
[ContentTypes.TOOL_CALL]: {
|
||||
type: ContentTypes.TOOL_CALL,
|
||||
tool_call: {
|
||||
id: 'call-1',
|
||||
type: ToolCallTypes.CODE_INTERPRETER,
|
||||
code_interpreter: {
|
||||
input: 'print(1)',
|
||||
outputs: [],
|
||||
},
|
||||
},
|
||||
} as ExportableContentPart,
|
||||
[ContentTypes.IMAGE_FILE]: {
|
||||
type: ContentTypes.IMAGE_FILE,
|
||||
image_file: imageFile,
|
||||
} as ExportableContentPart,
|
||||
[ContentTypes.IMAGE_URL]: {
|
||||
type: ContentTypes.IMAGE_URL,
|
||||
image_url: 'https://example.com/image.png',
|
||||
},
|
||||
[ContentTypes.VIDEO_URL]: {
|
||||
type: ContentTypes.VIDEO_URL,
|
||||
video_url: { url: 'https://example.com/video.mp4' },
|
||||
},
|
||||
[ContentTypes.INPUT_AUDIO]: {
|
||||
type: ContentTypes.INPUT_AUDIO,
|
||||
input_audio: {
|
||||
data: 'base64-audio',
|
||||
format: 'wav',
|
||||
},
|
||||
},
|
||||
[ContentTypes.AGENT_UPDATE]: {
|
||||
type: ContentTypes.AGENT_UPDATE,
|
||||
agent_update: {
|
||||
index: 0,
|
||||
runId: 'run-1',
|
||||
agentId: 'agent-1',
|
||||
},
|
||||
},
|
||||
[ContentTypes.SUMMARY]: {
|
||||
type: ContentTypes.SUMMARY,
|
||||
content: [{ type: ContentTypes.TEXT, text: 'Summary text' }],
|
||||
},
|
||||
[ContentTypes.ERROR]: {
|
||||
type: ContentTypes.ERROR,
|
||||
error: 'Something failed',
|
||||
},
|
||||
};
|
||||
|
||||
const expectedMarkdownByType: Record<ContentTypes, MessageContentExport> = {
|
||||
[ContentTypes.TEXT]: ['Assistant', 'Visible answer'],
|
||||
[ContentTypes.THINK]: [
|
||||
'Assistant',
|
||||
'<details>\n<summary>Thinking</summary>\n\nReasoning step\n</details>',
|
||||
],
|
||||
[ContentTypes.TEXT_DELTA]: ['Assistant', 'Streaming text'],
|
||||
[ContentTypes.TOOL_CALL]: ['Run code', '{"input":"print(1)","outputs":[]}'],
|
||||
[ContentTypes.IMAGE_FILE]: ['Image', JSON.stringify(imageFile)],
|
||||
[ContentTypes.IMAGE_URL]: ['Image', 'https://example.com/image.png'],
|
||||
[ContentTypes.VIDEO_URL]: ['Video', 'https://example.com/video.mp4'],
|
||||
[ContentTypes.INPUT_AUDIO]: ['Audio', '{"data":"base64-audio","format":"wav"}'],
|
||||
[ContentTypes.AGENT_UPDATE]: ['Agent Update', '{"index":0,"runId":"run-1","agentId":"agent-1"}'],
|
||||
[ContentTypes.SUMMARY]: ['Summary', 'Summary text'],
|
||||
[ContentTypes.ERROR]: ['Assistant', 'Something failed'],
|
||||
};
|
||||
|
||||
describe('handledExportContentTypes', () => {
|
||||
it('accounts for every content type before formatting exports', () => {
|
||||
expect([...handledExportContentTypes].sort()).toEqual(Object.values(ContentTypes).sort());
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatMessageContent', () => {
|
||||
it.each(Object.values(ContentTypes))('formats %s content explicitly', (type) => {
|
||||
expect(
|
||||
formatMessageContent({
|
||||
sender: 'Assistant',
|
||||
content: contentByType[type],
|
||||
format: 'md',
|
||||
localize,
|
||||
}),
|
||||
).toEqual(expectedMarkdownByType[type]);
|
||||
});
|
||||
|
||||
it('formats reasoning content as readable markdown instead of raw JSON', () => {
|
||||
const result = formatMessageContent({
|
||||
sender: 'DeepInfra',
|
||||
content: {
|
||||
type: ContentTypes.THINK,
|
||||
think: 'Thinking Process:\n\n1. Analyze the request',
|
||||
},
|
||||
format: 'md',
|
||||
localize,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
'DeepInfra',
|
||||
'<details>\n<summary>Thinking</summary>\n\nThinking Process:\n\n1. Analyze the request\n</details>',
|
||||
]);
|
||||
expect(result[1]).not.toContain('{"type":"think"');
|
||||
});
|
||||
|
||||
it('formats reasoning content readably for text exports', () => {
|
||||
expect(
|
||||
formatMessageContent({
|
||||
sender: 'Assistant',
|
||||
content: { type: ContentTypes.THINK, think: 'Check assumptions' },
|
||||
localize,
|
||||
}),
|
||||
).toEqual(['Assistant', 'Thinking:\nCheck assumptions']);
|
||||
});
|
||||
|
||||
it('strips THINK delimiters before formatting reasoning exports', () => {
|
||||
expect(
|
||||
formatMessageContent({
|
||||
sender: 'Assistant',
|
||||
content: { type: ContentTypes.THINK, think: '<think>\nCheck assumptions\n</think>' },
|
||||
format: 'md',
|
||||
localize,
|
||||
}),
|
||||
).toEqual([
|
||||
'Assistant',
|
||||
'<details>\n<summary>Thinking</summary>\n\nCheck assumptions\n</details>',
|
||||
]);
|
||||
});
|
||||
|
||||
it('concatenates summary chunks without inserted paragraphs', () => {
|
||||
expect(
|
||||
formatMessageContent({
|
||||
sender: 'Assistant',
|
||||
content: {
|
||||
type: ContentTypes.SUMMARY,
|
||||
content: [
|
||||
{ type: ContentTypes.TEXT, text: 'The ' },
|
||||
{ type: ContentTypes.TEXT, text: 'answer' },
|
||||
],
|
||||
},
|
||||
format: 'md',
|
||||
localize,
|
||||
}),
|
||||
).toEqual(['Summary', 'The answer']);
|
||||
});
|
||||
|
||||
it('formats legacy summary string content', () => {
|
||||
expect(
|
||||
formatMessageContent({
|
||||
sender: 'Assistant',
|
||||
content: {
|
||||
type: ContentTypes.SUMMARY,
|
||||
content: 'Legacy summary text',
|
||||
},
|
||||
format: 'md',
|
||||
localize,
|
||||
}),
|
||||
).toEqual(['Summary', 'Legacy summary text']);
|
||||
});
|
||||
|
||||
it('localizes export section labels', () => {
|
||||
const localized = ((key: string) => `localized:${key}`) as LocalizeFunction;
|
||||
|
||||
expect(
|
||||
formatMessageContent({
|
||||
sender: 'Assistant',
|
||||
content: contentByType[ContentTypes.VIDEO_URL],
|
||||
format: 'md',
|
||||
localize: localized,
|
||||
}),
|
||||
).toEqual(['localized:com_ui_export_video', 'https://example.com/video.mp4']);
|
||||
|
||||
expect(
|
||||
formatMessageContent({
|
||||
sender: 'Assistant',
|
||||
content: contentByType[ContentTypes.AGENT_UPDATE],
|
||||
format: 'md',
|
||||
localize: localized,
|
||||
}),
|
||||
).toEqual([
|
||||
'localized:com_ui_export_agent_update',
|
||||
'{"index":0,"runId":"run-1","agentId":"agent-1"}',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatMessageText', () => {
|
||||
it('formats markdown messages with reasoning and visible text parts', () => {
|
||||
const message = {
|
||||
sender: 'DeepInfra',
|
||||
content: [
|
||||
{ type: ContentTypes.THINK, think: 'Hidden chain' },
|
||||
{ type: ContentTypes.TEXT, text: 'Final answer' },
|
||||
],
|
||||
} as Partial<TMessage>;
|
||||
|
||||
expect(formatMessageText({ message, format: 'md', localize })).toBe(
|
||||
'**DeepInfra**\n<details>\n<summary>Thinking</summary>\n\nHidden chain\n</details>\n\n\n**DeepInfra**\nFinal answer',
|
||||
);
|
||||
});
|
||||
});
|
||||
231
client/src/hooks/Conversations/format.ts
Normal file
231
client/src/hooks/Conversations/format.ts
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
import { ContentTypes, ToolCallTypes } from 'librechat-data-provider';
|
||||
|
||||
import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
|
||||
import type { LocalizeFunction } from '~/common';
|
||||
|
||||
export type ExportFormat = 'text' | 'md';
|
||||
export type MessageContentExport = [] | [sender: string, text: string];
|
||||
|
||||
type TextValue = string | { value?: string | null } | null | undefined;
|
||||
type TextDeltaContentPart = {
|
||||
type: ContentTypes.TEXT_DELTA;
|
||||
text?: TextValue;
|
||||
text_delta?: TextValue;
|
||||
};
|
||||
type SummaryContentPart = Extract<TMessageContentParts, { type: ContentTypes.SUMMARY }>;
|
||||
type ExportSummaryContentPart = Omit<SummaryContentPart, 'content'> & {
|
||||
content?: Array<{ type: ContentTypes.TEXT; text?: TextValue }> | string;
|
||||
text?: TextValue;
|
||||
};
|
||||
|
||||
export type ExportableContentPart =
|
||||
| Exclude<TMessageContentParts, SummaryContentPart>
|
||||
| ExportSummaryContentPart
|
||||
| TextDeltaContentPart;
|
||||
|
||||
export const handledExportContentTypes = [
|
||||
ContentTypes.TEXT,
|
||||
ContentTypes.THINK,
|
||||
ContentTypes.TEXT_DELTA,
|
||||
ContentTypes.TOOL_CALL,
|
||||
ContentTypes.IMAGE_FILE,
|
||||
ContentTypes.IMAGE_URL,
|
||||
ContentTypes.VIDEO_URL,
|
||||
ContentTypes.INPUT_AUDIO,
|
||||
ContentTypes.AGENT_UPDATE,
|
||||
ContentTypes.SUMMARY,
|
||||
ContentTypes.ERROR,
|
||||
] satisfies readonly ContentTypes[];
|
||||
|
||||
const getTextValue = (value: TextValue): string => {
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
return value?.value ?? '';
|
||||
};
|
||||
|
||||
const stringify = (value: unknown): string => JSON.stringify(value ?? null);
|
||||
|
||||
const hasFormattedContent = (content: MessageContentExport): content is [string, string] =>
|
||||
content.length > 0;
|
||||
|
||||
const exportLabelKeys = {
|
||||
tool: 'com_ui_export_tool',
|
||||
image: 'com_ui_export_image',
|
||||
audio: 'com_ui_export_audio',
|
||||
video: 'com_ui_export_video',
|
||||
summary: 'com_ui_export_summary',
|
||||
retrieval: 'com_ui_export_retrieval',
|
||||
fileSearch: 'com_ui_export_file_search',
|
||||
agentUpdate: 'com_ui_export_agent_update',
|
||||
} satisfies Record<string, Parameters<LocalizeFunction>[0]>;
|
||||
|
||||
const stripThinkTags = (reasoning: string): string =>
|
||||
reasoning
|
||||
.replace(/^<think>\s*/, '')
|
||||
.replace(/\s*<\/think>$/, '')
|
||||
.trim();
|
||||
|
||||
const formatReasoning = ({
|
||||
reasoning,
|
||||
format,
|
||||
localize,
|
||||
}: {
|
||||
reasoning: string;
|
||||
format: ExportFormat;
|
||||
localize: LocalizeFunction;
|
||||
}): string => {
|
||||
const label = localize('com_endpoint_thinking');
|
||||
if (format === 'md') {
|
||||
return `<details>\n<summary>${label}</summary>\n\n${reasoning}\n</details>`;
|
||||
}
|
||||
return `${label}:\n${reasoning}`;
|
||||
};
|
||||
|
||||
const getUrlText = (value: string | { url?: string } | undefined): string => {
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
return value?.url ?? stringify(value);
|
||||
};
|
||||
|
||||
const getSummaryText = (content: ExportSummaryContentPart): string | undefined => {
|
||||
if (Array.isArray(content.content)) {
|
||||
return content.content.map((part) => getTextValue(part.text)).join('');
|
||||
}
|
||||
if (typeof content.content === 'string') {
|
||||
return content.content;
|
||||
}
|
||||
const text = getTextValue(content.text);
|
||||
if (text.length > 0) {
|
||||
return text;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export function formatMessageContent({
|
||||
sender,
|
||||
content,
|
||||
format = 'text',
|
||||
localize,
|
||||
}: {
|
||||
sender: string;
|
||||
content?: ExportableContentPart;
|
||||
format?: ExportFormat;
|
||||
localize: LocalizeFunction;
|
||||
}): MessageContentExport {
|
||||
if (!content) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.ERROR) {
|
||||
return [sender, content.error ?? getTextValue(content.text)];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.TEXT) {
|
||||
const text = getTextValue(content.text);
|
||||
if (text.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [sender, text];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.THINK) {
|
||||
const reasoning = stripThinkTags(getTextValue(content.think));
|
||||
if (reasoning.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [sender, formatReasoning({ reasoning, format, localize })];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.TEXT_DELTA) {
|
||||
const text = getTextValue(content.text_delta ?? content.text);
|
||||
if (text.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [sender, text];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.TOOL_CALL) {
|
||||
const toolCall = content.tool_call;
|
||||
if (!toolCall) {
|
||||
return [localize(exportLabelKeys.tool), stringify(toolCall)];
|
||||
}
|
||||
if (toolCall.type === ToolCallTypes.CODE_INTERPRETER) {
|
||||
return [localize('com_ui_run_code'), stringify(toolCall.code_interpreter)];
|
||||
}
|
||||
if (toolCall.type === ToolCallTypes.RETRIEVAL) {
|
||||
return [localize(exportLabelKeys.retrieval), stringify(toolCall)];
|
||||
}
|
||||
if (toolCall.type === ToolCallTypes.FILE_SEARCH) {
|
||||
return [localize(exportLabelKeys.fileSearch), stringify(toolCall)];
|
||||
}
|
||||
return [localize(exportLabelKeys.tool), stringify(toolCall)];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.IMAGE_FILE) {
|
||||
return [localize(exportLabelKeys.image), stringify(content.image_file)];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.IMAGE_URL) {
|
||||
return [localize(exportLabelKeys.image), getUrlText(content.image_url)];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.VIDEO_URL) {
|
||||
return [localize(exportLabelKeys.video), getUrlText(content.video_url)];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.INPUT_AUDIO) {
|
||||
return [localize(exportLabelKeys.audio), stringify(content.input_audio)];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.AGENT_UPDATE) {
|
||||
return [localize(exportLabelKeys.agentUpdate), stringify(content.agent_update)];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.SUMMARY) {
|
||||
const summary = getSummaryText(content);
|
||||
return [localize(exportLabelKeys.summary), summary ?? stringify(content)];
|
||||
}
|
||||
|
||||
return [sender, stringify(content)];
|
||||
}
|
||||
|
||||
export function formatMessageText({
|
||||
message,
|
||||
format = 'text',
|
||||
localize,
|
||||
}: {
|
||||
message: Partial<TMessage> | undefined;
|
||||
format?: ExportFormat;
|
||||
localize: LocalizeFunction;
|
||||
}): string {
|
||||
if (!message) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const formatText = (sender: string, text: string) => {
|
||||
if (format === 'text') {
|
||||
return `>> ${sender}:\n${text}`;
|
||||
}
|
||||
return `**${sender}**\n${text}`;
|
||||
};
|
||||
|
||||
if (!message.content) {
|
||||
return formatText(message.sender || '', message.text || '');
|
||||
}
|
||||
|
||||
return message.content
|
||||
.filter((content) => content != null)
|
||||
.map((content) =>
|
||||
formatMessageContent({
|
||||
sender: message.sender || '',
|
||||
content,
|
||||
format,
|
||||
localize,
|
||||
}),
|
||||
)
|
||||
.filter(hasFormattedContent)
|
||||
.map((text) => formatText(text[0], text[1]))
|
||||
.join('\n\n\n');
|
||||
}
|
||||
|
|
@ -1,26 +1,15 @@
|
|||
import download from 'downloadjs';
|
||||
import { useCallback } from 'react';
|
||||
import download from 'downloadjs';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import exportFromJSON from 'export-from-json';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
buildTree,
|
||||
QueryKeys,
|
||||
ContentTypes,
|
||||
ToolCallTypes,
|
||||
imageGenTools,
|
||||
isImageVisionTool,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
TMessageContentParts,
|
||||
TConversation,
|
||||
TMessage,
|
||||
TPreset,
|
||||
} from 'librechat-data-provider';
|
||||
import { buildTree, QueryKeys } from 'librechat-data-provider';
|
||||
import type { TConversation, TMessage, TPreset } from 'librechat-data-provider';
|
||||
import useBuildMessageTree from '~/hooks/Messages/useBuildMessageTree';
|
||||
import { useScreenshot } from '~/hooks/ScreenshotContext';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { formatMessageText } from './format';
|
||||
import { cleanupPreset } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type ExportValues = {
|
||||
fieldName: string;
|
||||
|
|
@ -58,106 +47,6 @@ export default function useExportConversation({
|
|||
return dataTree?.length === 0 ? null : (dataTree ?? null);
|
||||
}, [paramId, conversation?.conversationId, queryClient]);
|
||||
|
||||
const getMessageText = (message: Partial<TMessage> | undefined, format = 'text') => {
|
||||
if (!message) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const formatText = (sender: string, text: string) => {
|
||||
if (format === 'text') {
|
||||
return `>> ${sender}:\n${text}`;
|
||||
}
|
||||
return `**${sender}**\n${text}`;
|
||||
};
|
||||
|
||||
if (!message.content) {
|
||||
return formatText(message.sender || '', message.text || '');
|
||||
}
|
||||
|
||||
return message.content
|
||||
.filter((content) => content != null)
|
||||
.map((content) => getMessageContent(message.sender || '', content))
|
||||
.filter((text) => text.length > 0)
|
||||
.map((text) => {
|
||||
return formatText(text[0], text[1]);
|
||||
})
|
||||
.join('\n\n\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Format and return message texts according to the type of content.
|
||||
* Currently, content whose type is `TOOL_CALL` basically returns JSON as is.
|
||||
* In the future, different formatted text may be returned for each type.
|
||||
*/
|
||||
const getMessageContent = (sender: string, content?: TMessageContentParts): string[] => {
|
||||
if (!content) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.ERROR) {
|
||||
// ERROR
|
||||
return [
|
||||
sender,
|
||||
typeof content[ContentTypes.TEXT] === 'object'
|
||||
? (content[ContentTypes.TEXT].value ?? '')
|
||||
: (content[ContentTypes.TEXT] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.TEXT) {
|
||||
// TEXT
|
||||
const textPart = content[ContentTypes.TEXT];
|
||||
const text = typeof textPart === 'string' ? textPart : (textPart?.value ?? '');
|
||||
if (text.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [sender, text];
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.TOOL_CALL) {
|
||||
const type = content[ContentTypes.TOOL_CALL].type;
|
||||
|
||||
if (type === ToolCallTypes.CODE_INTERPRETER) {
|
||||
// CODE_INTERPRETER
|
||||
const toolCall = content[ContentTypes.TOOL_CALL];
|
||||
const code_interpreter = toolCall[ToolCallTypes.CODE_INTERPRETER];
|
||||
return [localize('com_ui_run_code'), JSON.stringify(code_interpreter)];
|
||||
}
|
||||
|
||||
if (type === ToolCallTypes.RETRIEVAL) {
|
||||
// RETRIEVAL
|
||||
const toolCall = content[ContentTypes.TOOL_CALL];
|
||||
return ['Retrieval', JSON.stringify(toolCall)];
|
||||
}
|
||||
|
||||
if (
|
||||
type === ToolCallTypes.FUNCTION &&
|
||||
imageGenTools.has(content[ContentTypes.TOOL_CALL].function.name)
|
||||
) {
|
||||
// IMAGE_GENERATION
|
||||
const toolCall = content[ContentTypes.TOOL_CALL];
|
||||
return ['Tool', JSON.stringify(toolCall)];
|
||||
}
|
||||
|
||||
if (type === ToolCallTypes.FUNCTION) {
|
||||
// IMAGE_VISION
|
||||
const toolCall = content[ContentTypes.TOOL_CALL];
|
||||
if (isImageVisionTool(toolCall)) {
|
||||
return ['Tool', JSON.stringify(toolCall)];
|
||||
}
|
||||
return ['Tool', JSON.stringify(toolCall)];
|
||||
}
|
||||
}
|
||||
|
||||
if (content.type === ContentTypes.IMAGE_FILE) {
|
||||
// IMAGE
|
||||
const imageFile = content[ContentTypes.IMAGE_FILE];
|
||||
return ['Image', JSON.stringify(imageFile)];
|
||||
}
|
||||
|
||||
return [sender, JSON.stringify(content)];
|
||||
};
|
||||
|
||||
const exportScreenshot = async () => {
|
||||
let data;
|
||||
try {
|
||||
|
|
@ -261,7 +150,7 @@ export default function useExportConversation({
|
|||
data += '\n## History\n';
|
||||
if (Array.isArray(messages)) {
|
||||
for (const message of messages) {
|
||||
data += `${getMessageText(message, 'md')}\n`;
|
||||
data += `${formatMessageText({ message, format: 'md', localize })}\n`;
|
||||
if (message?.error) {
|
||||
data += '*(This is an error message)*\n';
|
||||
}
|
||||
|
|
@ -271,7 +160,7 @@ export default function useExportConversation({
|
|||
data += '\n\n';
|
||||
}
|
||||
} else {
|
||||
data += `${getMessageText(messages, 'md')}\n`;
|
||||
data += `${formatMessageText({ message: messages, format: 'md', localize })}\n`;
|
||||
if (messages.error) {
|
||||
data += '*(This is an error message)*\n';
|
||||
}
|
||||
|
|
@ -317,7 +206,7 @@ export default function useExportConversation({
|
|||
data += '\nHistory\n########################\n';
|
||||
if (Array.isArray(messages)) {
|
||||
for (const message of messages) {
|
||||
data += `${getMessageText(message)}\n`;
|
||||
data += `${formatMessageText({ message, localize })}\n`;
|
||||
if (message?.error) {
|
||||
data += '(This is an error message)\n';
|
||||
}
|
||||
|
|
@ -327,7 +216,7 @@ export default function useExportConversation({
|
|||
data += '\n\n';
|
||||
}
|
||||
} else {
|
||||
data += `${getMessageText(messages)}\n`;
|
||||
data += `${formatMessageText({ message: messages, localize })}\n`;
|
||||
if (messages.error) {
|
||||
data += '(This is an error message)\n';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1153,7 +1153,15 @@
|
|||
"com_ui_expand_chat": "Expand Chat",
|
||||
"com_ui_expand_editor": "Expand editor",
|
||||
"com_ui_expand_thoughts": "Expand Thoughts",
|
||||
"com_ui_export_agent_update": "Agent Update",
|
||||
"com_ui_export_audio": "Audio",
|
||||
"com_ui_export_convo_modal": "Export Conversation Modal",
|
||||
"com_ui_export_file_search": "File Search",
|
||||
"com_ui_export_image": "Image",
|
||||
"com_ui_export_retrieval": "Retrieval",
|
||||
"com_ui_export_summary": "Summary",
|
||||
"com_ui_export_tool": "Tool",
|
||||
"com_ui_export_video": "Video",
|
||||
"com_ui_failed": "Failed",
|
||||
"com_ui_favorite": "Add to favorites",
|
||||
"com_ui_feedback_more": "More...",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue