* fix: 7973 查看原始数据的按钮没有了

* refactor(MessageTools): replace PreviewBlock with CollapsedContent for improved preview rendering

---------

Co-authored-by: suyao <sy20010504@gmail.com>
This commit is contained in:
karl 2025-07-14 21:50:55 +08:00 committed by GitHub
parent 094eb5c17e
commit e0eac6ab7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 215 additions and 91 deletions

View File

@ -764,7 +764,8 @@
"invoking": "Invoking",
"pending": "Pending",
"preview": "Preview",
"autoApproveEnabled": "Auto-approve enabled for this tool"
"autoApproveEnabled": "Auto-approve enabled for this tool",
"raw": "Raw"
},
"topic.added": "New topic added",
"upgrade.success.button": "Restart",

View File

@ -764,7 +764,8 @@
"invoking": "呼び出し中",
"pending": "保留中",
"preview": "プレビュー",
"autoApproveEnabled": "このツールは自動承認が有効になっています"
"autoApproveEnabled": "このツールは自動承認が有効になっています",
"raw": "生データ"
},
"topic.added": "新しいトピックが追加されました",
"upgrade.success.button": "再起動",

View File

@ -764,7 +764,8 @@
"invoking": "Вызов",
"pending": "Ожидание",
"preview": "Предпросмотр",
"autoApproveEnabled": "Для этого инструмента включен автоматический одобрен"
"autoApproveEnabled": "Для этого инструмента включен автоматический одобрен",
"raw": "Исходный"
},
"topic.added": "Новый топик добавлен",
"upgrade.success.button": "Перезапустить",

View File

@ -764,7 +764,8 @@
"invoking": "调用中",
"pending": "等待中",
"preview": "预览",
"autoApproveEnabled": "此工具已启用自动批准"
"autoApproveEnabled": "此工具已启用自动批准",
"raw": "原始"
},
"topic.added": "话题添加成功",
"upgrade.success.button": "重启",

View File

@ -764,7 +764,8 @@
"invoking": "調用中",
"pending": "等待中",
"preview": "預覽",
"autoApproveEnabled": "此工具已啟用自動批准"
"autoApproveEnabled": "此工具已啟用自動批准",
"raw": "原始碼"
},
"topic.added": "新話題已新增",
"upgrade.success.button": "重新啟動",

View File

@ -1,11 +1,11 @@
import { CheckOutlined, CloseOutlined, LoadingOutlined, WarningOutlined } from '@ant-design/icons'
import { CheckOutlined, CloseOutlined, ExpandOutlined, LoadingOutlined, WarningOutlined } from '@ant-design/icons'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { useSettings } from '@renderer/hooks/useSettings'
import type { ToolMessageBlock } from '@renderer/types/newMessage'
import { isToolAutoApproved } from '@renderer/utils/mcp-tools'
import { cancelToolAction, confirmToolAction } from '@renderer/utils/userConfirmation'
import { Button, Collapse, ConfigProvider, Dropdown, Flex, message as antdMessage, Tooltip } from 'antd'
import { Button, Collapse, ConfigProvider, Dropdown, Flex, message as antdMessage, Modal, Tabs, Tooltip } from 'antd'
import { message } from 'antd'
import Logger from 'electron-log/renderer'
import { ChevronDown, ChevronRight, CirclePlay, CircleX, PauseCircle, ShieldCheck } from 'lucide-react'
@ -26,6 +26,7 @@ const MessageTools: FC<Props> = ({ block }) => {
const { t } = useTranslation()
const { messageFont, fontSize } = useSettings()
const { mcpServers, updateMCPServer } = useMCPServers()
const [expandedResponse, setExpandedResponse] = useState<{ content: string; title: string } | null>(null)
const toolResponse = block.metadata?.rawMcpToolResponse
@ -221,6 +222,20 @@ const MessageTools: FC<Props> = ({ block }) => {
<StatusIndicator status={status} hasError={hasError}>
{renderStatusIndicator(status, hasError)}
</StatusIndicator>
<Tooltip title={t('common.expand')} mouseEnterDelay={0.5}>
<ActionButton
className="message-action-button"
onClick={(e) => {
e.stopPropagation()
setExpandedResponse({
content: JSON.stringify(response, null, 2),
title: tool.name
})
}}
aria-label={t('common.expand')}>
<ExpandOutlined />
</ActionButton>
</Tooltip>
{!isPending && !isInvoking && (
<Tooltip title={t('common.copy')} mouseEnterDelay={0.5}>
<ActionButton
@ -259,92 +274,173 @@ const MessageTools: FC<Props> = ({ block }) => {
return items
}
return (
<ConfigProvider
theme={{
components: {
Button: {
borderRadiusSM: 6
}
}
}}>
<ToolContainer>
<ToolContentWrapper className={status}>
<CollapseContainer
ghost
activeKey={activeKeys}
size="small"
onChange={handleCollapseChange}
className="message-tools-container"
items={getCollapseItems()}
expandIconPosition="end"
expandIcon={({ isActive }) => (
<ExpandIcon $isActive={isActive} size={18} color="var(--color-text-3)" strokeWidth={1.5} />
)}
/>
{(isPending || isInvoking) && (
<ActionsBar>
<ActionLabel>
{isPending ? t('settings.mcp.tools.autoApprove.tooltip.confirm') : t('message.tools.invoking')}
</ActionLabel>
const renderPreview = (content: string) => {
if (!content) return null
<ActionButtonsGroup>
{isPending && (
<Button
color="danger"
variant="filled"
size="small"
onClick={() => {
handleCancelTool()
}}>
<CircleX size={15} className="lucide-custom" />
{t('common.cancel')}
</Button>
)}
{isInvoking && toolResponse?.id ? (
<Button
size="small"
color="danger"
variant="solid"
className="abort-button"
onClick={(e) => {
e.stopPropagation()
handleAbortTool()
}}>
<PauseCircle className="lucide-custom" size={14} />
{t('chat.input.pause')}
</Button>
) : (
<StyledDropdownButton
size="small"
type="primary"
icon={<ChevronDown size={14} />}
onClick={() => {
handleConfirmTool()
}}
menu={{
items: [
{
key: 'autoApprove',
label: t('settings.mcp.tools.autoApprove'),
onClick: () => {
handleAutoApprove()
try {
const parsedResult = JSON.parse(content)
switch (parsedResult.content[0]?.type) {
case 'text':
return (
<CollapsedContent
isExpanded={true}
resultString={JSON.stringify(JSON.parse(parsedResult.content[0].text), null, 2)}
/>
)
default:
return <CollapsedContent isExpanded={true} resultString={JSON.stringify(parsedResult, null, 2)} />
}
} catch (e) {
console.error('failed to render the preview of mcp results:', e)
return <CollapsedContent isExpanded={true} resultString={JSON.stringify(e, null, 2)} />
}
}
return (
<>
<ConfigProvider
theme={{
components: {
Button: {
borderRadiusSM: 6
}
}
}}>
<ToolContainer>
<ToolContentWrapper className={status}>
<CollapseContainer
ghost
activeKey={activeKeys}
size="small"
onChange={handleCollapseChange}
className="message-tools-container"
items={getCollapseItems()}
expandIconPosition="end"
expandIcon={({ isActive }) => (
<ExpandIcon $isActive={isActive} size={18} color="var(--color-text-3)" strokeWidth={1.5} />
)}
/>
{(isPending || isInvoking) && (
<ActionsBar>
<ActionLabel>
{isPending ? t('settings.mcp.tools.autoApprove.tooltip.confirm') : t('message.tools.invoking')}
</ActionLabel>
<ActionButtonsGroup>
{isPending && (
<Button
color="danger"
variant="filled"
size="small"
onClick={() => {
handleCancelTool()
}}>
<CircleX size={15} className="lucide-custom" />
{t('common.cancel')}
</Button>
)}
{isInvoking && toolResponse?.id ? (
<Button
size="small"
color="danger"
variant="solid"
className="abort-button"
onClick={(e) => {
e.stopPropagation()
handleAbortTool()
}}>
<PauseCircle className="lucide-custom" size={14} />
{t('chat.input.pause')}
</Button>
) : (
<StyledDropdownButton
size="small"
type="primary"
icon={<ChevronDown size={14} />}
onClick={() => {
handleConfirmTool()
}}
menu={{
items: [
{
key: 'autoApprove',
label: t('settings.mcp.tools.autoApprove'),
onClick: () => {
handleAutoApprove()
}
}
}
]
}}>
<CirclePlay size={15} className="lucide-custom" />
<CountdownText>
{t('settings.mcp.tools.run', 'Run')} ({countdown}s)
</CountdownText>
</StyledDropdownButton>
)}
</ActionButtonsGroup>
</ActionsBar>
)}
</ToolContentWrapper>
</ToolContainer>
</ConfigProvider>
]
}}>
<CirclePlay size={15} className="lucide-custom" />
<CountdownText>
{t('settings.mcp.tools.run', 'Run')} ({countdown}s)
</CountdownText>
</StyledDropdownButton>
)}
</ActionButtonsGroup>
</ActionsBar>
)}
</ToolContentWrapper>
</ToolContainer>
</ConfigProvider>
<Modal
title={expandedResponse?.title}
open={!!expandedResponse}
onCancel={() => setExpandedResponse(null)}
footer={null}
width="80%"
centered
transitionName="animation-move-down"
styles={{ body: { maxHeight: '80vh', overflow: 'auto' } }}>
{expandedResponse && (
<ExpandedResponseContainer
style={{
fontFamily: messageFont === 'serif' ? 'var(--font-family-serif)' : 'var(--font-family)',
fontSize
}}>
<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: (
<CollapsedContent
isExpanded={true}
resultString={
typeof expandedResponse.content === 'string'
? expandedResponse.content
: JSON.stringify(expandedResponse.content, null, 2)
}
/>
)
}
]}
/>
</ExpandedResponseContainer>
)}
</Modal>
</>
)
}
@ -563,4 +659,27 @@ const ToolResponseContainer = styled.div`
position: relative;
`
const ExpandedResponseContainer = styled.div`
background: var(--color-bg-1);
border-radius: 8px;
padding: 16px;
position: relative;
.copy-expanded-button {
position: absolute;
top: 10px;
right: 10px;
background-color: var(--color-bg-2);
border-radius: 4px;
z-index: 1;
}
pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
color: var(--color-text);
}
`
export default memo(MessageTools)