mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 05:11:24 +08:00
feat(i18n): add error detail translations and enhance error handling UI
- Added new translation keys for error details in multiple languages, including 'detail', 'details', 'message', 'requestBody', 'requestUrl', 'stack', and 'status'. - Updated the ErrorBlock component to display a modal with detailed error information, allowing users to view and copy error details easily. - Improved the user experience by providing a clear and accessible way to understand error messages and their context.
This commit is contained in:
parent
fee6ad58d1
commit
4918628131
@ -741,6 +741,7 @@
|
||||
"delete": "Delete",
|
||||
"delete_confirm": "Are you sure you want to delete?",
|
||||
"description": "Description",
|
||||
"detail": "Detail",
|
||||
"disabled": "Disabled",
|
||||
"docs": "Docs",
|
||||
"download": "Download",
|
||||
@ -825,6 +826,8 @@
|
||||
},
|
||||
"response": "Something went wrong. Please check if you have set your API key in the Settings > Providers"
|
||||
},
|
||||
"detail": "Error Details",
|
||||
"details": "Details",
|
||||
"http": {
|
||||
"400": "Request failed. Please check if the request parameters are correct. If you have changed the model settings, please reset them to the default settings",
|
||||
"401": "Authentication failed. Please check if your API key is correct",
|
||||
@ -836,6 +839,7 @@
|
||||
"503": "Service unavailable. Please try again later",
|
||||
"504": "Gateway timeout. Please try again later"
|
||||
},
|
||||
"message": "Error Message",
|
||||
"missing_user_message": "Cannot switch model response: The original user message has been deleted. Please send a new message to get a response with this model.",
|
||||
"model": {
|
||||
"exists": "Model already exists",
|
||||
@ -848,6 +852,10 @@
|
||||
"description": "Failed to render message content. Please check if the message content format is correct",
|
||||
"title": "Render Error"
|
||||
},
|
||||
"requestBody": "Request Body",
|
||||
"requestUrl": "Request URL",
|
||||
"stack": "Stack Trace",
|
||||
"status": "Status Code",
|
||||
"unknown": "Unknown error",
|
||||
"user_message_not_found": "Cannot find original user message to resend"
|
||||
},
|
||||
|
||||
@ -741,6 +741,7 @@
|
||||
"delete": "削除",
|
||||
"delete_confirm": "削除してもよろしいですか?",
|
||||
"description": "説明",
|
||||
"detail": "詳細",
|
||||
"disabled": "無効",
|
||||
"docs": "ドキュメント",
|
||||
"download": "ダウンロード",
|
||||
@ -825,6 +826,8 @@
|
||||
},
|
||||
"response": "エラーが発生しました。APIキーが設定されていない場合は、設定 > プロバイダーでキーを設定してください"
|
||||
},
|
||||
"detail": "エラーの詳細",
|
||||
"details": "詳細",
|
||||
"http": {
|
||||
"400": "リクエストに失敗しました。リクエストパラメータが正しいか確認してください。モデルの設定を変更した場合は、デフォルトの設定にリセットしてください",
|
||||
"401": "認証に失敗しました。APIキーが正しいか確認してください",
|
||||
@ -836,6 +839,7 @@
|
||||
"503": "サービスが利用できません。後でもう一度試してください",
|
||||
"504": "ゲートウェイタイムアウトが発生しました。後でもう一度試してください"
|
||||
},
|
||||
"message": "エラーメッセージ",
|
||||
"missing_user_message": "モデル応答を切り替えられません:元のユーザーメッセージが削除されました。このモデルで応答を得るには、新しいメッセージを送信してください",
|
||||
"model": {
|
||||
"exists": "モデルが既に存在します",
|
||||
@ -848,6 +852,9 @@
|
||||
"description": "メッセージの内容のレンダリングに失敗しました。メッセージの内容の形式が正しいか確認してください",
|
||||
"title": "レンダリングエラー"
|
||||
},
|
||||
"requestBody": "要求されたコンテンツ",
|
||||
"stack": "スタック情報",
|
||||
"status": "ステータスコード",
|
||||
"unknown": "不明なエラー",
|
||||
"user_message_not_found": "元のユーザーメッセージを見つけることができませんでした"
|
||||
},
|
||||
|
||||
@ -741,6 +741,7 @@
|
||||
"delete": "Удалить",
|
||||
"delete_confirm": "Вы уверены, что хотите удалить?",
|
||||
"description": "Описание",
|
||||
"detail": "Подробности",
|
||||
"disabled": "Отключено",
|
||||
"docs": "Документы",
|
||||
"download": "Скачать",
|
||||
@ -825,6 +826,8 @@
|
||||
},
|
||||
"response": "Что-то пошло не так. Пожалуйста, проверьте, установлен ли ваш ключ API в Настройки > Провайдеры"
|
||||
},
|
||||
"detail": "Детали ошибки",
|
||||
"details": "Подробности",
|
||||
"http": {
|
||||
"400": "Не удалось выполнить запрос. Пожалуйста, проверьте, правильно ли настроены параметры запроса. Если вы изменили настройки модели, пожалуйста, сбросьте их до значений по умолчанию",
|
||||
"401": "Не удалось пройти аутентификацию. Пожалуйста, проверьте, правильно ли настроен ваш ключ API",
|
||||
@ -836,6 +839,7 @@
|
||||
"503": "Серверная ошибка. Пожалуйста, попробуйте позже",
|
||||
"504": "Серверная ошибка. Пожалуйста, попробуйте позже"
|
||||
},
|
||||
"message": "Сообщение об ошибке",
|
||||
"missing_user_message": "Невозможно изменить модель ответа: исходное сообщение пользователя было удалено. Пожалуйста, отправьте новое сообщение, чтобы получить ответ от этой модели",
|
||||
"model": {
|
||||
"exists": "Модель уже существует",
|
||||
@ -848,6 +852,9 @@
|
||||
"description": "Не удалось рендерить содержимое сообщения. Пожалуйста, проверьте, правильно ли формат содержимого сообщения",
|
||||
"title": "Ошибка рендеринга"
|
||||
},
|
||||
"requestBody": "Запрашиваемый контент",
|
||||
"stack": "Информация стека",
|
||||
"status": "Код статуса",
|
||||
"unknown": "Неизвестная ошибка",
|
||||
"user_message_not_found": "Не удалось найти исходное сообщение пользователя"
|
||||
},
|
||||
|
||||
@ -741,6 +741,7 @@
|
||||
"delete": "删除",
|
||||
"delete_confirm": "确定要删除吗?",
|
||||
"description": "描述",
|
||||
"detail": "详情",
|
||||
"disabled": "已禁用",
|
||||
"docs": "文档",
|
||||
"download": "下载",
|
||||
@ -825,6 +826,8 @@
|
||||
},
|
||||
"response": "出错了,如果没有配置 API 密钥,请前往设置 > 模型提供商中配置密钥"
|
||||
},
|
||||
"detail": "错误详情",
|
||||
"details": "详细信息",
|
||||
"http": {
|
||||
"400": "请求错误,请检查请求参数是否正确。如果修改了模型设置,请重置到默认设置",
|
||||
"401": "身份验证失败,请检查 API 密钥是否正确",
|
||||
@ -836,6 +839,7 @@
|
||||
"503": "服务不可用,请稍后再试",
|
||||
"504": "网关超时,请稍后再试"
|
||||
},
|
||||
"message": "错误信息",
|
||||
"missing_user_message": "无法切换模型响应:原始用户消息已被删除。请发送新消息以获取此模型的响应",
|
||||
"model": {
|
||||
"exists": "模型已存在",
|
||||
@ -848,6 +852,10 @@
|
||||
"description": "消息内容渲染失败,请检查消息内容格式是否正确",
|
||||
"title": "渲染错误"
|
||||
},
|
||||
"requestBody": "请求内容",
|
||||
"requestUrl": "请求路径",
|
||||
"stack": "堆栈信息",
|
||||
"status": "状态码",
|
||||
"unknown": "未知错误",
|
||||
"user_message_not_found": "无法找到原始用户消息"
|
||||
},
|
||||
|
||||
@ -741,6 +741,7 @@
|
||||
"delete": "刪除",
|
||||
"delete_confirm": "確定要刪除嗎?",
|
||||
"description": "描述",
|
||||
"detail": "詳情",
|
||||
"disabled": "已停用",
|
||||
"docs": "文件",
|
||||
"download": "下載",
|
||||
@ -825,6 +826,8 @@
|
||||
},
|
||||
"response": "出現錯誤。如果尚未設定 API 金鑰,請前往設定 > 模型提供者中設定金鑰"
|
||||
},
|
||||
"detail": "錯誤詳情",
|
||||
"details": "詳細信息",
|
||||
"http": {
|
||||
"400": "請求錯誤,請檢查請求參數是否正確。如果修改了模型設定,請重設到預設設定",
|
||||
"401": "身份驗證失敗,請檢查 API 金鑰是否正確",
|
||||
@ -836,6 +839,7 @@
|
||||
"503": "服務無法使用,請稍後再試",
|
||||
"504": "閘道器超時,請稍後再試"
|
||||
},
|
||||
"message": "錯誤訊息",
|
||||
"missing_user_message": "無法切換模型回應:原始用戶訊息已被刪除。請發送新訊息以獲得此模型回應。",
|
||||
"model": {
|
||||
"exists": "模型已存在",
|
||||
@ -848,6 +852,9 @@
|
||||
"description": "消息內容渲染失敗,請檢查消息內容格式是否正確",
|
||||
"title": "渲染錯誤"
|
||||
},
|
||||
"requestBody": "請求內容",
|
||||
"stack": "堆棧信息",
|
||||
"status": "狀態碼",
|
||||
"unknown": "未知錯誤",
|
||||
"user_message_not_found": "無法找到原始用戶訊息"
|
||||
},
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import CodeViewer from '@renderer/components/CodeViewer'
|
||||
import { useTimer } from '@renderer/hooks/useTimer'
|
||||
import { getHttpMessageLabel } from '@renderer/i18n/label'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { removeBlocksThunk } from '@renderer/store/thunk/messageThunk'
|
||||
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
|
||||
import { Alert as AntdAlert } from 'antd'
|
||||
import React from 'react'
|
||||
import { Alert as AntdAlert, Button, Modal } from 'antd'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -21,6 +22,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }>
|
||||
const { t, i18n } = useTranslation()
|
||||
const dispatch = useAppDispatch()
|
||||
const { setTimeoutTimer } = useTimer()
|
||||
const [showDetailModal, setShowDetailModal] = useState(false)
|
||||
|
||||
const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504]
|
||||
|
||||
@ -28,27 +30,225 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }>
|
||||
setTimeoutTimer('onRemoveBlock', () => dispatch(removeBlocksThunk(message.topicId, message.id, [block.id])), 350)
|
||||
}
|
||||
|
||||
const showErrorDetail = () => {
|
||||
setShowDetailModal(true)
|
||||
}
|
||||
|
||||
if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) {
|
||||
return (
|
||||
<Alert
|
||||
description={getHttpMessageLabel(block.error.status)}
|
||||
message={block.error.message}
|
||||
type="error"
|
||||
closable
|
||||
onClose={onRemoveBlock}
|
||||
/>
|
||||
<>
|
||||
<Alert
|
||||
description={getHttpMessageLabel(block.error.status)}
|
||||
message={block.error.message}
|
||||
type="error"
|
||||
closable
|
||||
onClose={onRemoveBlock}
|
||||
onClick={showErrorDetail}
|
||||
style={{ cursor: 'pointer' }}
|
||||
action={
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
showErrorDetail()
|
||||
}}>
|
||||
{t('common.detail')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ErrorDetailModal open={showDetailModal} onClose={() => setShowDetailModal(false)} error={block.error} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (block?.error?.message) {
|
||||
const errorKey = `error.${block.error.message}`
|
||||
const pauseErrorLanguagePlaceholder = i18n.exists(errorKey) ? t(errorKey) : block.error.message
|
||||
return <Alert description={pauseErrorLanguagePlaceholder} type="error" closable onClose={onRemoveBlock} />
|
||||
return (
|
||||
<>
|
||||
<Alert
|
||||
description={pauseErrorLanguagePlaceholder}
|
||||
type="error"
|
||||
closable
|
||||
onClose={onRemoveBlock}
|
||||
onClick={showErrorDetail}
|
||||
style={{ cursor: 'pointer' }}
|
||||
action={
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
showErrorDetail()
|
||||
}}>
|
||||
{t('common.detail')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ErrorDetailModal open={showDetailModal} onClose={() => setShowDetailModal(false)} error={block.error} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return <Alert description={t('error.chat.response')} type="error" closable onClose={onRemoveBlock} />
|
||||
return (
|
||||
<>
|
||||
<Alert
|
||||
description={t('error.chat.response')}
|
||||
type="error"
|
||||
closable
|
||||
onClose={onRemoveBlock}
|
||||
onClick={showErrorDetail}
|
||||
style={{ cursor: 'pointer' }}
|
||||
action={
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
showErrorDetail()
|
||||
}}>
|
||||
{t('common.detail')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ErrorDetailModal open={showDetailModal} onClose={() => setShowDetailModal(false)} error={block.error} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface ErrorDetailModalProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
error?: Record<string, any>
|
||||
}
|
||||
|
||||
const ErrorDetailModal: React.FC<ErrorDetailModalProps> = ({ open, onClose, error }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const copyErrorDetails = () => {
|
||||
if (!error) return
|
||||
|
||||
const errorText = `
|
||||
${t('error.message')}: ${error.message || 'N/A'}
|
||||
${t('error.requestUrl')}: ${error.url || 'N/A'}
|
||||
${t('error.requestBody')}: ${error.requestBody ? JSON.stringify(error.requestBody, null, 2) : 'N/A'}
|
||||
${t('error.stack')}: ${error.stack || 'N/A'}
|
||||
`.trim()
|
||||
|
||||
navigator.clipboard.writeText(errorText)
|
||||
}
|
||||
|
||||
const renderErrorDetails = (error: any) => {
|
||||
if (!error) return <div>{t('error.unknown')}</div>
|
||||
|
||||
return (
|
||||
<ErrorDetailList>
|
||||
{error.message && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.message')}:</ErrorDetailLabel>
|
||||
<ErrorDetailValue>{error.message}</ErrorDetailValue>
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
{error.url && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.requestUrl')}:</ErrorDetailLabel>
|
||||
<ErrorDetailValue>{error.url}</ErrorDetailValue>
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
{error.requestBody && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.requestBody')}:</ErrorDetailLabel>
|
||||
<CodeViewer className="source-view" language="json" expanded={true} unwrapped={true}>
|
||||
{JSON.stringify(error.requestBody, null, 2)}
|
||||
</CodeViewer>
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
{error.stack && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.stack')}:</ErrorDetailLabel>
|
||||
<StackTrace>
|
||||
<pre>{error.stack}</pre>
|
||||
</StackTrace>
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
</ErrorDetailList>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('error.detail')}
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
footer={[
|
||||
<Button key="copy" onClick={copyErrorDetails}>
|
||||
{t('common.copy')}
|
||||
</Button>,
|
||||
<Button key="close" onClick={onClose}>
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
]}
|
||||
width={600}>
|
||||
<ErrorDetailContainer>{renderErrorDetails(error)}</ErrorDetailContainer>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const ErrorDetailContainer = styled.div`
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
`
|
||||
|
||||
const ErrorDetailList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const ErrorDetailItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const ErrorDetailLabel = styled.div`
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
const ErrorDetailValue = styled.div`
|
||||
font-family: var(--code-font-family);
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
background: var(--color-code-background);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
word-break: break-word;
|
||||
color: var(--color-text);
|
||||
`
|
||||
|
||||
const StackTrace = styled.div`
|
||||
background: var(--color-background-soft);
|
||||
border: 1px solid var(--color-error);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: var(--code-font-family);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--color-error);
|
||||
}
|
||||
`
|
||||
|
||||
const Alert = styled(AntdAlert)`
|
||||
margin: 0.5rem 0 !important;
|
||||
padding: 10px;
|
||||
|
||||
@ -19,7 +19,7 @@ import { isAbortError, serializeError } from '@renderer/utils/error'
|
||||
import { createBaseMessageBlock, createErrorBlock } from '@renderer/utils/messageUtils/create'
|
||||
import { findAllBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||
import { isFocused, isOnHomePage } from '@renderer/utils/window'
|
||||
import { AISDKError } from 'ai'
|
||||
import { AISDKError, NoOutputGeneratedError } from 'ai'
|
||||
|
||||
import { BlockManager } from '../BlockManager'
|
||||
|
||||
@ -82,6 +82,9 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
|
||||
|
||||
onError: async (error: AISDKError) => {
|
||||
logger.debug('onError', error)
|
||||
if (NoOutputGeneratedError.isInstance(error)) {
|
||||
return
|
||||
}
|
||||
const isErrorTypeAbort = isAbortError(error)
|
||||
const serializableError = serializeError(error)
|
||||
if (isErrorTypeAbort) {
|
||||
|
||||
@ -103,7 +103,13 @@ export const serializeError = (error: AISDKError) => {
|
||||
} catch (e: any) {
|
||||
logger.warn('Error parsing error response body:', e)
|
||||
}
|
||||
return { ...baseError, status: error.statusCode, url: error.url, message: content }
|
||||
return {
|
||||
...baseError,
|
||||
status: error.statusCode,
|
||||
url: error.url,
|
||||
message: content,
|
||||
requestBody: error.requestBodyValues
|
||||
}
|
||||
}
|
||||
return baseError
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user