mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 22:52:08 +08:00
fix: improve BashTool command display and enhance ToolTitle layout (#11572)
* fix: improve BashTool command display and enhance ToolTitle layout * style(ant.css): fix overflow in collapse header text * fix(i18n): translate toolPendingFallback in multiple languages --------- Co-authored-by: icarus <eurfelux@gmail.com>
This commit is contained in:
parent
3b1155b538
commit
cc676d4bef
@ -215,6 +215,10 @@
|
|||||||
border-top: none !important;
|
border-top: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-collapse-header-text {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-slider .ant-slider-handle::after {
|
.ant-slider .ant-slider-handle::after {
|
||||||
box-shadow: 0 1px 4px 0px rgb(128 128 128 / 50%) !important;
|
box-shadow: 0 1px 4px 0px rgb(128 128 128 / 50%) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "Tool request was denied.",
|
"denied": "Tool request was denied.",
|
||||||
"timeout": "Tool request timed out before receiving approval."
|
"timeout": "Tool request timed out before receiving approval."
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "Tool",
|
||||||
"waiting": "Waiting for tool permission decision..."
|
"waiting": "Waiting for tool permission decision..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "工具请求已被拒绝。",
|
"denied": "工具请求已被拒绝。",
|
||||||
"timeout": "工具请求在收到批准前超时。"
|
"timeout": "工具请求在收到批准前超时。"
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "工具",
|
||||||
"waiting": "等待工具权限决定..."
|
"waiting": "等待工具权限决定..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "工具請求已被拒絕。",
|
"denied": "工具請求已被拒絕。",
|
||||||
"timeout": "工具請求在收到核准前逾時。"
|
"timeout": "工具請求在收到核准前逾時。"
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "工具",
|
||||||
"waiting": "等待工具權限決定..."
|
"waiting": "等待工具權限決定..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "Tool-Anfrage wurde abgelehnt.",
|
"denied": "Tool-Anfrage wurde abgelehnt.",
|
||||||
"timeout": "Tool-Anfrage ist abgelaufen, bevor eine Genehmigung eingegangen ist."
|
"timeout": "Tool-Anfrage ist abgelaufen, bevor eine Genehmigung eingegangen ist."
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "Werkzeug",
|
||||||
"waiting": "Warten auf Entscheidung über Tool-Berechtigung..."
|
"waiting": "Warten auf Entscheidung über Tool-Berechtigung..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "Το αίτημα για εργαλείο απορρίφθηκε.",
|
"denied": "Το αίτημα για εργαλείο απορρίφθηκε.",
|
||||||
"timeout": "Το αίτημα για το εργαλείο έληξε πριν λάβει έγκριση."
|
"timeout": "Το αίτημα για το εργαλείο έληξε πριν λάβει έγκριση."
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "Εργαλείο",
|
||||||
"waiting": "Αναμονή για απόφαση άδειας εργαλείου..."
|
"waiting": "Αναμονή για απόφαση άδειας εργαλείου..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "La solicitud de herramienta fue denegada.",
|
"denied": "La solicitud de herramienta fue denegada.",
|
||||||
"timeout": "La solicitud de herramienta expiró antes de recibir la aprobación."
|
"timeout": "La solicitud de herramienta expiró antes de recibir la aprobación."
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "Herramienta",
|
||||||
"waiting": "Esperando la decisión de permiso de la herramienta..."
|
"waiting": "Esperando la decisión de permiso de la herramienta..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "La demande d'outil a été refusée.",
|
"denied": "La demande d'outil a été refusée.",
|
||||||
"timeout": "La demande d'outil a expiré avant d'obtenir l'approbation."
|
"timeout": "La demande d'outil a expiré avant d'obtenir l'approbation."
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "Outil",
|
||||||
"waiting": "En attente de la décision d'autorisation de l'outil..."
|
"waiting": "En attente de la décision d'autorisation de l'outil..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "ツールリクエストは拒否されました。",
|
"denied": "ツールリクエストは拒否されました。",
|
||||||
"timeout": "ツールリクエストは承認を受ける前にタイムアウトしました。"
|
"timeout": "ツールリクエストは承認を受ける前にタイムアウトしました。"
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "ツール",
|
||||||
"waiting": "ツールの許可決定を待っています..."
|
"waiting": "ツールの許可決定を待っています..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "Solicitação de ferramenta foi negada.",
|
"denied": "Solicitação de ferramenta foi negada.",
|
||||||
"timeout": "A solicitação da ferramenta expirou antes de receber aprovação."
|
"timeout": "A solicitação da ferramenta expirou antes de receber aprovação."
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "Ferramenta",
|
||||||
"waiting": "Aguardando decisão de permissão da ferramenta..."
|
"waiting": "Aguardando decisão de permissão da ferramenta..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"denied": "Запрос на инструмент был отклонён.",
|
"denied": "Запрос на инструмент был отклонён.",
|
||||||
"timeout": "Запрос на инструмент превысил время ожидания до получения подтверждения."
|
"timeout": "Запрос на инструмент превысил время ожидания до получения подтверждения."
|
||||||
},
|
},
|
||||||
|
"toolPendingFallback": "Инструмент",
|
||||||
"waiting": "Ожидание решения о разрешении на использование инструмента..."
|
"waiting": "Ожидание решения о разрешении на использование инструмента..."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import { Terminal } from 'lucide-react'
|
|||||||
import { ToolTitle } from './GenericTools'
|
import { ToolTitle } from './GenericTools'
|
||||||
import type { BashToolInput as BashToolInputType, BashToolOutput as BashToolOutputType } from './types'
|
import type { BashToolInput as BashToolInputType, BashToolOutput as BashToolOutputType } from './types'
|
||||||
|
|
||||||
const MAX_TAG_LENGTH = 100
|
|
||||||
|
|
||||||
export function BashTool({
|
export function BashTool({
|
||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
@ -17,12 +15,10 @@ export function BashTool({
|
|||||||
// 如果有输出,计算输出行数
|
// 如果有输出,计算输出行数
|
||||||
const outputLines = output ? output.split('\n').length : 0
|
const outputLines = output ? output.split('\n').length : 0
|
||||||
|
|
||||||
// 处理命令字符串的截断,添加空值检查
|
// 处理命令字符串,添加空值检查
|
||||||
const command = input?.command ?? ''
|
const command = input?.command ?? ''
|
||||||
const needsTruncate = command.length > MAX_TAG_LENGTH
|
|
||||||
const displayCommand = needsTruncate ? `${command.slice(0, MAX_TAG_LENGTH)}...` : command
|
|
||||||
|
|
||||||
const tagContent = <Tag className="whitespace-pre-wrap break-all font-mono">{displayCommand}</Tag>
|
const tagContent = <Tag className="!m-0 max-w-full truncate font-mono">{command}</Tag>
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: 'tool',
|
key: 'tool',
|
||||||
@ -34,16 +30,12 @@ export function BashTool({
|
|||||||
params={input?.description}
|
params={input?.description}
|
||||||
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
||||||
/>
|
/>
|
||||||
<div className="mt-1">
|
<div className="mt-1 max-w-full">
|
||||||
{needsTruncate ? (
|
|
||||||
<Popover
|
<Popover
|
||||||
content={<div className="max-w-xl whitespace-pre-wrap break-all font-mono">{command}</div>}
|
content={<div className="max-w-xl whitespace-pre-wrap break-all font-mono text-xs">{command}</div>}
|
||||||
trigger="hover">
|
trigger="hover">
|
||||||
{tagContent}
|
{tagContent}
|
||||||
</Popover>
|
</Popover>
|
||||||
) : (
|
|
||||||
tagContent
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -18,9 +18,9 @@ export function ToolTitle({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center gap-1 ${className}`}>
|
<div className={`flex items-center gap-1 ${className}`}>
|
||||||
{icon}
|
{icon && <span className="flex flex-shrink-0">{icon}</span>}
|
||||||
{label && <span className="font-medium text-sm">{label}</span>}
|
{label && <span className="flex-shrink-0 font-medium text-sm">{label}</span>}
|
||||||
{params && <span className="flex-shrink-0 text-muted-foreground text-xs">{params}</span>}
|
{params && <span className="min-w-0 truncate text-muted-foreground text-xs">{params}</span>}
|
||||||
{stats && <span className="flex-shrink-0 text-muted-foreground text-xs">{stats}</span>}
|
{stats && <span className="flex-shrink-0 text-muted-foreground text-xs">{stats}</span>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { useAppSelector } from '@renderer/store'
|
||||||
|
import { selectPendingPermission } from '@renderer/store/toolPermissions'
|
||||||
import type { NormalToolResponse } from '@renderer/types'
|
import type { NormalToolResponse } from '@renderer/types'
|
||||||
import type { CollapseProps } from 'antd'
|
import type { CollapseProps } from 'antd'
|
||||||
import { Collapse } from 'antd'
|
import { Collapse, Spin } from 'antd'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
// 导出所有类型
|
// 导出所有类型
|
||||||
export * from './types'
|
export * from './types'
|
||||||
@ -83,17 +86,41 @@ function ToolContent({ toolName, input, output }: { toolName: AgentToolsType; in
|
|||||||
// 统一的组件渲染入口
|
// 统一的组件渲染入口
|
||||||
export function MessageAgentTools({ toolResponse }: { toolResponse: NormalToolResponse }) {
|
export function MessageAgentTools({ toolResponse }: { toolResponse: NormalToolResponse }) {
|
||||||
const { arguments: args, response, tool, status } = toolResponse
|
const { arguments: args, response, tool, status } = toolResponse
|
||||||
logger.info('Rendering agent tool response', {
|
logger.debug('Rendering agent tool response', {
|
||||||
tool: tool,
|
tool: tool,
|
||||||
arguments: args,
|
arguments: args,
|
||||||
|
status,
|
||||||
response
|
response
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const pendingPermission = useAppSelector((state) =>
|
||||||
|
selectPendingPermission(state.toolPermissions, toolResponse.toolCallId)
|
||||||
|
)
|
||||||
|
|
||||||
if (status === 'pending') {
|
if (status === 'pending') {
|
||||||
|
if (pendingPermission) {
|
||||||
return <ToolPermissionRequestCard toolResponse={toolResponse} />
|
return <ToolPermissionRequestCard toolResponse={toolResponse} />
|
||||||
}
|
}
|
||||||
|
return <ToolPendingIndicator toolName={tool?.name} description={tool?.description} />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolContent toolName={tool.name as AgentToolsType} input={args as ToolInput} output={response as ToolOutput} />
|
<ToolContent toolName={tool.name as AgentToolsType} input={args as ToolInput} output={response as ToolOutput} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ToolPendingIndicator({ toolName, description }: { toolName?: string; description?: string }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const label = toolName || t('agent.toolPermission.toolPendingFallback', 'Tool')
|
||||||
|
const detail = description?.trim() || t('agent.toolPermission.executing')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full max-w-xl items-center gap-3 rounded-xl border border-default-200 bg-default-100 px-4 py-3 shadow-sm">
|
||||||
|
<Spin size="small" />
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="font-semibold text-default-700 text-sm">{label}</span>
|
||||||
|
<span className="text-default-500 text-xs">{detail}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user