mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-02-05 18:41:10 +08:00
* feat(error): add clickable error detail modal for model detection - Add onErrorClick callback to HealthStatusIndicator component - Make error icon clickable to show detailed error modal - Export ErrorDetailModal for reuse across components - Use SerializedError type for better error information display - Fix tooltip i18n keys and flex layout for consistent spacing * fix(error): only show clickable error indicator when failed results exist - Only pass onErrorClick callback when there are failed results - Conditionally render ErrorDetailModal only when needed - Improve performance by avoiding unnecessary state for success cases * refactor(error): extract serializeHealthCheckError utility function - Extract duplicated error serialization logic into serializeHealthCheckError() utility function in error.ts - Update HealthCheckService.ts, hook.ts, and ProviderSetting.tsx to use the new utility function - Fix invalid CSS values: align-items: left -> center, align: left -> flex-start - Use HealthStatus.FAILED constant instead of hardcoded 'failed' string * fix(settings): update tooltip message key for API check Change the tooltip fallback translation key from the provider API check to the models check. This ensures the displayed message matches the new i18n key and avoids showing an incorrect/provider-specific string when the API key connectivity error message is absent. * refactor(error): extract ErrorDetailModal as independent component * refactor(error): extract ErrorDetailModal as independent component * refactor(error): update ErrorDetailModal with full features from ErrorBlock * refactor(error): fix export duplication and add React performance optimizations * refactor(error): add React performance optimizations - Memoize ErrorDetailModal child components (BuiltinError, AiSdkErrorBase, TruncatedCodeViewer, AiSdkError) - Add useCallback for handleErrorClick in HealthStatusIndicator - Add useCallback for copyErrorDetails in ErrorDetailModal - Optimize ModelListItem button inline functions with useCallback * refactor(health): move HealthStatusIndicatorProps back to indicator.tsx - HealthStatusIndicatorProps was only used in indicator.tsx, no need to export - Keep HealthResult in types.ts as it's used by useHealthStatus hook * fix(health): import HealthResult from types instead of redefining - useHealthStatus depends on HealthResult from ./types - Only HealthStatusIndicatorProps belongs to indicator.tsx * refactor(health): move click handler to outer wrapper * fix(lint): resolve react-hooks/exhaustive-deps warnings in ModelListItem - wrap healthResults in useMemo to avoid unnecessary re-renders - remove redundant ErrorDetailModal export from ErrorBlock * refactor(error): optimize ErrorDetailModal and replace duplicate code - Use parseDataUrl from @shared/utils instead of manual base64 detection - Replace duplicate ErrorDetailModal in ErrorBlock with the shared component - Optimize onErrorClick with useMemo for better performance
106 lines
2.8 KiB
TypeScript
106 lines
2.8 KiB
TypeScript
import { CheckCircleFilled, CloseCircleFilled, ExclamationCircleFilled, LoadingOutlined } from '@ant-design/icons'
|
|
import { HealthStatus } from '@renderer/types/healthCheck'
|
|
import { Flex, Tooltip, Typography } from 'antd'
|
|
import React, { memo, useCallback } from 'react'
|
|
import styled from 'styled-components'
|
|
|
|
import type { HealthResult } from './types'
|
|
import { useHealthStatus } from './useHealthStatus'
|
|
|
|
interface HealthStatusIndicatorProps {
|
|
results: HealthResult[]
|
|
loading?: boolean
|
|
showLatency?: boolean
|
|
onErrorClick?: (result: HealthResult) => void
|
|
}
|
|
|
|
const HealthStatusIndicator: React.FC<HealthStatusIndicatorProps> = ({
|
|
results,
|
|
loading = false,
|
|
showLatency = false,
|
|
onErrorClick
|
|
}) => {
|
|
const { overallStatus, tooltip, latencyText } = useHealthStatus({
|
|
results,
|
|
showLatency
|
|
})
|
|
|
|
const handleClick = useCallback(() => {
|
|
if (!onErrorClick) return
|
|
const failedResult = results.find((r) => r.status === HealthStatus.FAILED)
|
|
if (failedResult) {
|
|
onErrorClick(failedResult)
|
|
}
|
|
}, [onErrorClick, results])
|
|
|
|
if (loading) {
|
|
return (
|
|
<IndicatorWrapper $type="checking">
|
|
<LoadingOutlined spin />
|
|
</IndicatorWrapper>
|
|
)
|
|
}
|
|
|
|
if (overallStatus === 'not_checked') return null
|
|
|
|
const isClickable = onErrorClick && results.some((r) => r.status === HealthStatus.FAILED)
|
|
|
|
let icon: React.ReactNode = null
|
|
switch (overallStatus) {
|
|
case 'success':
|
|
icon = <CheckCircleFilled />
|
|
break
|
|
case 'error':
|
|
case 'partial': {
|
|
const IconComponent = overallStatus === 'error' ? CloseCircleFilled : ExclamationCircleFilled
|
|
icon = <IconComponent />
|
|
break
|
|
}
|
|
default:
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<Flex align="center" gap={6}>
|
|
{latencyText && <LatencyText type="secondary">{latencyText}</LatencyText>}
|
|
<Tooltip title={tooltip} styles={{ body: { userSelect: 'text' } }}>
|
|
<IndicatorWrapper
|
|
$type={overallStatus}
|
|
$clickable={isClickable}
|
|
onClick={isClickable ? handleClick : undefined}>
|
|
{icon}
|
|
</IndicatorWrapper>
|
|
</Tooltip>
|
|
</Flex>
|
|
)
|
|
}
|
|
|
|
const IndicatorWrapper = styled.div<{ $type: string; $clickable?: boolean }>`
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 14px;
|
|
cursor: ${({ $clickable }) => ($clickable ? 'pointer' : 'auto')};
|
|
color: ${({ $type }) => {
|
|
switch ($type) {
|
|
case 'success':
|
|
return 'var(--color-status-success)'
|
|
case 'error':
|
|
return 'var(--color-status-error)'
|
|
case 'partial':
|
|
return 'var(--color-status-warning)'
|
|
case 'checking':
|
|
default:
|
|
return 'var(--color-text)'
|
|
}
|
|
}};
|
|
`
|
|
|
|
const LatencyText = styled(Typography.Text)`
|
|
margin-left: 10px;
|
|
color: var(--color-text-secondary);
|
|
font-size: 12px;
|
|
`
|
|
|
|
export default memo(HealthStatusIndicator)
|