feat: support preview of MCP call results (#5236)

This commit is contained in:
shiquda 2025-04-25 08:59:41 +08:00 committed by GitHub
parent 5a44f6aca8
commit 62b6584d65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 89 additions and 21 deletions

View File

@ -562,7 +562,9 @@
"tools": {
"completed": "Completed",
"invoking": "Invoking",
"error": "Error occurred"
"error": "Error occurred",
"raw": "Raw",
"preview": "Preview"
},
"topic.added": "New topic added",
"upgrade.success.button": "Restart",

View File

@ -561,7 +561,9 @@
"tools": {
"completed": "完了",
"invoking": "呼び出し中",
"error": "エラーが発生しました"
"error": "エラーが発生しました",
"raw": "生データ",
"preview": "プレビュー"
},
"topic.added": "新しいトピックが追加されました",
"upgrade.success.button": "再起動",

View File

@ -562,7 +562,9 @@
"tools": {
"completed": "Завершено",
"invoking": "Вызов",
"error": "Произошла ошибка"
"error": "Произошла ошибка",
"raw": "Исходный",
"preview": "Предпросмотр"
},
"topic.added": "Новый топик добавлен",
"upgrade.success.button": "Перезапустить",

View File

@ -562,7 +562,9 @@
"tools": {
"completed": "已完成",
"invoking": "调用中",
"error": "发生错误"
"error": "发生错误",
"raw": "原始",
"preview": "预览"
},
"topic.added": "话题添加成功",
"upgrade.success.button": "重启",

View File

@ -562,7 +562,9 @@
"tools": {
"completed": "已完成",
"invoking": "調用中",
"error": "發生錯誤"
"error": "發生錯誤",
"raw": "原始碼",
"preview": "預覽"
},
"topic.added": "新話題已新增",
"upgrade.success.button": "重新啟動",

View File

@ -503,7 +503,9 @@
"switch.disabled": "Παρακαλείστε να περιμένετε τη λήξη της τρέχουσας απάντησης",
"tools": {
"completed": "Ολοκληρώθηκε",
"invoking": "κλήση σε εξέλιξη"
"invoking": "κλήση σε εξέλιξη",
"raw": "Ακατέργαστο",
"preview": "Προεπισκόπηση"
},
"topic.added": "Η θεματική προστέθηκε επιτυχώς",
"upgrade.success.button": "Επανεκκίνηση",

View File

@ -503,7 +503,9 @@
"switch.disabled": "Espere a que se complete la respuesta actual antes de realizar la operación",
"tools": {
"completed": "Completado",
"invoking": "En llamada"
"invoking": "En llamada",
"raw": "Crudo",
"preview": "Vista previa"
},
"topic.added": "Tema agregado con éxito",
"upgrade.success.button": "Reiniciar",

View File

@ -503,7 +503,9 @@
"switch.disabled": "Veuillez attendre la fin de la réponse actuelle avant de procéder",
"tools": {
"completed": "Terminé",
"invoking": "En cours d'exécution"
"invoking": "En cours d'exécution",
"raw": "Brut",
"preview": "Aperçu"
},
"topic.added": "Thème ajouté avec succès",
"upgrade.success.button": "Redémarrer",

View File

@ -503,7 +503,9 @@
"switch.disabled": "Aguarde a conclusão da resposta atual antes de operar",
"tools": {
"completed": "Completo",
"invoking": "Em execução"
"invoking": "Em execução",
"raw": "Bruto",
"preview": "Pré-visualização"
},
"topic.added": "Tópico adicionado com sucesso",
"upgrade.success.button": "Reiniciar",

View File

@ -1,7 +1,7 @@
import { CheckOutlined, ExpandOutlined, LoadingOutlined, WarningOutlined } from '@ant-design/icons'
import { useSettings } from '@renderer/hooks/useSettings'
import { Message } from '@renderer/types'
import { Collapse, message as antdMessage, Modal, Tooltip } from 'antd'
import { Collapse, message as antdMessage, Modal, Tabs, Tooltip } from 'antd'
import { isEmpty } from 'lodash'
import { FC, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -116,6 +116,24 @@ const MessageTools: FC<Props> = ({ message }) => {
return items
}
const renderPreview = (content: string) => {
if (!content) return null
try {
const parsedResult = JSON.parse(content)
switch (parsedResult.content[0]?.type) {
case 'text':
return <PreviewBlock>{parsedResult.content[0].text}</PreviewBlock>
// TODO: support other types
default:
return <PreviewBlock>{content}</PreviewBlock>
}
} catch (e) {
console.error('failed to render the preview of mcp results:', e)
return <PreviewBlock>{content}</PreviewBlock>
}
}
return (
<>
<CollapseContainer
@ -139,18 +157,42 @@ const MessageTools: FC<Props> = ({ message }) => {
styles={{ body: { maxHeight: '80vh', overflow: 'auto' } }}>
{expandedResponse && (
<ExpandedResponseContainer style={{ fontFamily, fontSize }}>
<ActionButton
className="copy-expanded-button"
onClick={() => {
if (expandedResponse) {
navigator.clipboard.writeText(expandedResponse.content)
antdMessage.success({ content: t('message.copied'), key: 'copy-expanded' })
{/* mode swtich tabs */}
<Tabs
tabBarExtraContent={
<ActionButton
className="copy-expanded-button"
onClick={() => {
navigator.clipboard.writeText(
typeof expandedResponse.content === 'string'
? expandedResponse.content
: JSON.stringify(expandedResponse.content, null, 2)
)
antdMessage.success({ content: t('message.copied'), key: 'copy-expanded' })
}}
aria-label={t('common.copy')}>
<i className="iconfont icon-copy"></i>
</ActionButton>
}
items={[
{
key: 'preview',
label: t('message.tools.preview'),
children: renderPreview(expandedResponse.content)
},
{
key: 'raw',
label: t('message.tools.raw'),
children: (
<CodeBlock>
{typeof expandedResponse.content === 'string'
? expandedResponse.content
: JSON.stringify(expandedResponse.content, null, 2)}
</CodeBlock>
)
}
}}
aria-label={t('common.copy')}>
<i className="iconfont icon-copy"></i>
</ActionButton>
<CodeBlock>{expandedResponse.content}</CodeBlock>
]}
/>
</ExpandedResponseContainer>
)}
</Modal>
@ -267,6 +309,14 @@ const ToolResponseContainer = styled.div`
position: relative;
`
const PreviewBlock = styled.div`
margin: 0;
white-space: pre-wrap;
word-break: break-word;
color: var(--color-text);
user-select: text;
`
const CodeBlock = styled.pre`
margin: 0;
white-space: pre-wrap;