mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-02 02:09:03 +08:00
Fix/7973 (#8059)
* fix: 7973 查看原始数据的按钮没有了 * refactor(MessageTools): replace PreviewBlock with CollapsedContent for improved preview rendering --------- Co-authored-by: suyao <sy20010504@gmail.com>
This commit is contained in:
parent
094eb5c17e
commit
e0eac6ab7e
@ -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",
|
||||
|
||||
@ -764,7 +764,8 @@
|
||||
"invoking": "呼び出し中",
|
||||
"pending": "保留中",
|
||||
"preview": "プレビュー",
|
||||
"autoApproveEnabled": "このツールは自動承認が有効になっています"
|
||||
"autoApproveEnabled": "このツールは自動承認が有効になっています",
|
||||
"raw": "生データ"
|
||||
},
|
||||
"topic.added": "新しいトピックが追加されました",
|
||||
"upgrade.success.button": "再起動",
|
||||
|
||||
@ -764,7 +764,8 @@
|
||||
"invoking": "Вызов",
|
||||
"pending": "Ожидание",
|
||||
"preview": "Предпросмотр",
|
||||
"autoApproveEnabled": "Для этого инструмента включен автоматический одобрен"
|
||||
"autoApproveEnabled": "Для этого инструмента включен автоматический одобрен",
|
||||
"raw": "Исходный"
|
||||
},
|
||||
"topic.added": "Новый топик добавлен",
|
||||
"upgrade.success.button": "Перезапустить",
|
||||
|
||||
@ -764,7 +764,8 @@
|
||||
"invoking": "调用中",
|
||||
"pending": "等待中",
|
||||
"preview": "预览",
|
||||
"autoApproveEnabled": "此工具已启用自动批准"
|
||||
"autoApproveEnabled": "此工具已启用自动批准",
|
||||
"raw": "原始"
|
||||
},
|
||||
"topic.added": "话题添加成功",
|
||||
"upgrade.success.button": "重启",
|
||||
|
||||
@ -764,7 +764,8 @@
|
||||
"invoking": "調用中",
|
||||
"pending": "等待中",
|
||||
"preview": "預覽",
|
||||
"autoApproveEnabled": "此工具已啟用自動批准"
|
||||
"autoApproveEnabled": "此工具已啟用自動批准",
|
||||
"raw": "原始碼"
|
||||
},
|
||||
"topic.added": "新話題已新增",
|
||||
"upgrade.success.button": "重新啟動",
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user