fix: 消息级错误可见化——API 失败(如 402 余额耗尽)不再渲染为空白回合
This commit is contained in:
@@ -135,11 +135,6 @@ export default function App(): JSX.Element {
|
||||
// 主进程侧 openView(插件 main 工厂 / 深链按钮)→ 渲染层切视图
|
||||
useEffect(() => window.api.onPluginsView((change) => handleOpenView(change.view, change.params)), [handleOpenView])
|
||||
|
||||
// TEMP-DEBUG: 移除(改为 App.tsx 模块级触发)
|
||||
|
||||
// 主进程侧 openView(插件 main 工厂 / 深链按钮)→ 渲染层切视图
|
||||
useEffect(() => window.api.onPluginsView((change) => handleOpenView(change.view, change.params)), [handleOpenView])
|
||||
|
||||
// 插件列表变化(扫描/重载/启停)→ 刷新外置插件页(iframe 换 src 整页重载)
|
||||
useEffect(
|
||||
() =>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { StreamingText } from './StreamingText'
|
||||
import { Thinking, thinkingRows } from './Thinking'
|
||||
import { ToolChips, type ToolCallView } from './ToolChips'
|
||||
import { useEntrance } from '../hooks/useEntrance'
|
||||
import { LuFoldVertical, LuGitFork, LuHistory, LuLoaderCircle, LuUndo2 } from 'react-icons/lu'
|
||||
import { LuFoldVertical, LuGitFork, LuHistory, LuLoaderCircle, LuTriangleAlert, LuUndo2 } from 'react-icons/lu'
|
||||
import { formatTokens } from '../lib/utils'
|
||||
|
||||
interface ContentBlock {
|
||||
@@ -41,6 +41,29 @@ function lastThinkingLine(text: string): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 消息级错误(stopReason:'error',如 API 402 余额耗尽/限流):无此错误返回空串 */
|
||||
function readMessageError(raw: unknown): string {
|
||||
if (!raw || typeof raw !== 'object') return ''
|
||||
const m = raw as { stopReason?: unknown; errorMessage?: unknown }
|
||||
if (m.stopReason !== 'error' || typeof m.errorMessage !== 'string' || !m.errorMessage.trim()) return ''
|
||||
return m.errorMessage
|
||||
}
|
||||
|
||||
/** 提炼错误文案的可读部分:'402: {"message":"Insufficient Balance",…}' → '402: Insufficient Balance' */
|
||||
function readableMessageError(err: string): string {
|
||||
const match = err.match(/^(\d{3}):\s*([\s\S]*)$/)
|
||||
const body = match ? match[2] : err
|
||||
try {
|
||||
const parsed = JSON.parse(body) as { message?: unknown }
|
||||
if (parsed && typeof parsed.message === 'string' && parsed.message) {
|
||||
return match ? `${match[1]}: ${parsed.message}` : parsed.message
|
||||
}
|
||||
} catch {
|
||||
// 非 JSON 体,保持原文
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/** 工作计时:working=true 起表,结束后冻结读数。
|
||||
* working 中途抖动(pi 轮次间隙 streaming/turnActive 抖动)不锁存:
|
||||
* 恢复 true 时解除冻结,继续从起点计时。 */
|
||||
@@ -166,6 +189,8 @@ function AssistantTurnBase({
|
||||
let thinking = 0
|
||||
let stream: UiMessage['stream'] | null = null
|
||||
let answerText = ''
|
||||
// 消息级错误(API 失败等):回合内最后一条错误保留展示,否则错误轮会渲染成空白
|
||||
let messageError = ''
|
||||
|
||||
// 流式中的消息 = 答案雏形(最后一条);其余消息全部是过程。
|
||||
// 只有回合结束(settled)且没有流式占位时,最后一条已完成消息的正文才是答案。
|
||||
@@ -177,6 +202,8 @@ function AssistantTurnBase({
|
||||
stream = message.stream
|
||||
continue
|
||||
}
|
||||
const msgErr = readMessageError(message.raw)
|
||||
if (msgErr) messageError = msgErr
|
||||
const asAnswer = settled && streamPos === -1 && idx === messages.length - 1
|
||||
for (const block of readBlocks(message.raw)) {
|
||||
const thinkingText = block.type === 'thinking' ? (block.text ?? block.thinking) : undefined
|
||||
@@ -436,6 +463,19 @@ function AssistantTurnBase({
|
||||
{/* 回合进行中但没有流式占位:工具执行或自动压缩的静默间隙,给一个存活指示 */}
|
||||
{turnActive && !stream && <Loading label="处理中" variant="Dots" />}
|
||||
|
||||
{/* 消息级错误(stopReason:'error',如 API 402 余额耗尽 / 限流):常驻可见,
|
||||
不随过程区折叠——否则失败轮在界面上就是一片空白,无从排查 */}
|
||||
{messageError && (
|
||||
<div className="mt-2 flex items-start gap-2.5 rounded-control border border-red/40 bg-red-tint px-3.5 py-2.5 text-[12.5px] text-red">
|
||||
<LuTriangleAlert size={14} className="mt-0.5 shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="mb-0.5 font-medium">模型请求失败</div>
|
||||
<div className="break-all text-red/80">{readableMessageError(messageError)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* 结论底部的操作行:复制 + 会话分叉。末回合走会话末尾分叉(onFork),
|
||||
历史回合以「本回合之后第一条用户消息」为锚点(forkEntryId + onForkAt),
|
||||
新会话都包含到本回合答案为止。常驻不闪现。 */}
|
||||
|
||||
Reference in New Issue
Block a user