diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index f2a43a8693..4774c78316 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -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" }, diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index ad22c06fe6..0666f7868f 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -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": "元のユーザーメッセージを見つけることができませんでした" }, diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index c231102a23..0c4d9a426d 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -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": "Не удалось найти исходное сообщение пользователя" }, diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 81b7173845..5f8d13c02d 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -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": "无法找到原始用户消息" }, diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 195f1b5276..4facd5557b 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -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": "無法找到原始用戶訊息" }, diff --git a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx index 38f687e6d2..58dbd7e7d4 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx @@ -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 ( - + <> + { + e.stopPropagation() + showErrorDetail() + }}> + {t('common.detail')} + + } + /> + 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 + return ( + <> + { + e.stopPropagation() + showErrorDetail() + }}> + {t('common.detail')} + + } + /> + setShowDetailModal(false)} error={block.error} /> + + ) } - return + return ( + <> + { + e.stopPropagation() + showErrorDetail() + }}> + {t('common.detail')} + + } + /> + setShowDetailModal(false)} error={block.error} /> + + ) } +interface ErrorDetailModalProps { + open: boolean + onClose: () => void + error?: Record +} + +const ErrorDetailModal: React.FC = ({ 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
{t('error.unknown')}
+ + return ( + + {error.message && ( + + {t('error.message')}: + {error.message} + + )} + + {error.url && ( + + {t('error.requestUrl')}: + {error.url} + + )} + + {error.requestBody && ( + + {t('error.requestBody')}: + + {JSON.stringify(error.requestBody, null, 2)} + + + )} + + {error.stack && ( + + {t('error.stack')}: + +
{error.stack}
+
+
+ )} +
+ ) + } + + return ( + + {t('common.copy')} + , + + ]} + width={600}> + {renderErrorDetails(error)} + + ) +} + +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; diff --git a/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts index d4fe37e947..2c7ac234a8 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts @@ -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) { diff --git a/src/renderer/src/utils/error.ts b/src/renderer/src/utils/error.ts index 54639a333a..d617124582 100644 --- a/src/renderer/src/utils/error.ts +++ b/src/renderer/src/utils/error.ts @@ -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 }