继续收敛润色偏好 IPC 错误

保留提醒偏好读取和保存详情并显示回退状态

补充偏好错误处理文档
This commit is contained in:
kdletters
2026-10-05 22:35:24 +08:00
parent 9b5e6a8f75
commit ea70e3fe73
4 changed files with 56 additions and 20 deletions
@@ -41,7 +41,10 @@ import {
import { createPortal } from 'react-dom';
import RichTextInput from '../../components/RichTextInput';
import { captureClientError } from '../../services/errorReporting';
import {
captureClientError,
visibleClientErrorMessage,
} from '../../services/errorReporting';
import type { UserContentPart } from '../../view/project-development/chat/generated/UserContentPart';
import {
chatPromptDraftKey,
@@ -655,6 +658,9 @@ function ResourceReferenceEditor({
// 聊天特有的「发送前提醒」:提醒偏好、本轮已确认草稿指纹与表单拦截。
const [reminderOpen, setReminderOpen] = useState(false);
const [reminderPolishing, setReminderPolishing] = useState(false);
const [reminderPreferenceError, setReminderPreferenceError] = useState<
string | null
>(null);
// 偏好正式数据在 Rust:初始值只是「未关闭」的占位,挂载后由快照与事件补齐。
const [reminderDisabled, setReminderDisabled] = useState(false);
const acknowledgedDraftKeyRef = useRef<string | null>(null);
@@ -681,6 +687,7 @@ function ResourceReferenceEditor({
let unsubscribe: (() => void) | null = null;
void subscribeChatPromptPolishReminderDisabled((disabled) => {
if (disposed) return;
setReminderPreferenceError(null);
reminderPreferenceGenerationRef.current += 1;
setReminderDisabled(disabled);
})
@@ -688,18 +695,41 @@ function ResourceReferenceEditor({
if (disposed) release();
else unsubscribe = release;
})
.catch(() => undefined);
.catch((error) => {
if (!disposed) {
setReminderPreferenceError(
`润色提醒偏好读取失败:${visibleClientErrorMessage(error)}`,
);
void captureClientError(error, {
source: 'chat-prompt-polish',
action: 'read-reminder-disabled',
});
}
});
// 快照读取与订阅并发,但只有「读取期间没有任何更新」时才允许写回。
const generationAtRead = reminderPreferenceGenerationRef.current;
void readChatPromptPolishReminderDisabled().then((disabled) => {
if (
disposed ||
reminderPreferenceGenerationRef.current !== generationAtRead
) {
return;
}
setReminderDisabled(disabled);
});
void readChatPromptPolishReminderDisabled()
.then((disabled) => {
if (
disposed ||
reminderPreferenceGenerationRef.current !== generationAtRead
) {
return;
}
setReminderPreferenceError(null);
setReminderDisabled(disabled);
})
.catch((error) => {
if (!disposed) {
setReminderPreferenceError(
`润色提醒偏好读取失败:${visibleClientErrorMessage(error)}`,
);
void captureClientError(error, {
source: 'chat-prompt-polish',
action: 'read-reminder-disabled',
});
}
});
return () => {
disposed = true;
unsubscribe?.();
@@ -805,9 +835,15 @@ function ResourceReferenceEditor({
reminderPreferenceGenerationRef.current += 1;
setReminderDisabled(disabled);
void writeChatPromptPolishReminderDisabled(disabled)
.then((persisted) => setReminderDisabled(persisted))
.then((persisted) => {
setReminderPreferenceError(null);
setReminderDisabled(persisted);
})
.catch((error) => {
setReminderDisabled(!disabled);
setReminderPreferenceError(
`润色提醒偏好保存失败:${visibleClientErrorMessage(error)}`,
);
captureClientError(error, {
source: 'chat-prompt-polish',
action: 'set-reminder-disabled',
@@ -888,13 +924,15 @@ function ResourceReferenceEditor({
{/* 「与原文相同 / 已截断」这类提示也要可见:只报失败会让「润色没变化」看起来像按钮坏了。 */}
{showPolishAction &&
!reminderOpen &&
(polishing || polishError || polishNotice) ? (
(polishing || polishError || polishNotice || reminderPreferenceError) ? (
<span
className="resource-reference-input-status"
role="status"
aria-live="polite"
>
{polishing ? '润色中…' : (polishError ?? polishNotice)}
{polishing
? '润色中…'
: (polishError ?? polishNotice ?? reminderPreferenceError)}
</span>
) : null}
{providers.map((provider) =>
@@ -22,12 +22,8 @@ export const CHAT_PROMPT_POLISH_REMINDER_MIN_TEXT_LENGTH = 40;
/** 读取 Rust 偏好投影;原生桥不可用时按未关闭处理。 */
export async function readChatPromptPolishReminderDisabled(): Promise<boolean> {
if (!resolveTauriInvoke()) return false;
try {
const snapshot = await readWorkspacePreferences();
return snapshot.chatPromptPolishReminderDisabled;
} catch {
return false;
}
const snapshot = await readWorkspacePreferences();
return snapshot.chatPromptPolishReminderDisabled;
}
/** 写入 Rust 偏好并返回落盘后的投影;原生桥不可用时只当成次生效。 */
@@ -141,6 +141,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接
- 共享异步操作 hook(包括 Prompt Polish)捕获异常时,操作指引可以保留,但固定失败文案后必须附精确脱敏正文。
- 资源画布历史任务账本读取属于用户可见的恢复路径:异常正文必须进入提示条;只有响应形状错误这类没有底层异常的情况才使用明确的契约提示。
- 异步业务 facade 不得把底层异常折成“空结果”来逃避错误处理;只有真实的空回包、未配置或浏览器桥不可用等无正文事实允许返回空值,Tauri/网络/IPC 异常必须上抛到统一脱敏出口。
- 可选偏好可以安全回退默认值,但回退不能隐藏错误:偏好读写 IPC 失败必须同时保留安全正文、记录诊断并在当前输入上下文给出提示。
- Direct 活动回合事件桥注册失败即使仍有一次性快照,也必须标明事件更新通道已失联并展示安全正文,不能只显示旧快照而让用户误以为活动状态持续可靠。
- 首页富文本剪贴板读取的“无内容”与“原生 IPC 失败”必须区分;后者展示安全正文,不能静默当作空剪贴板。
- 账号邀请码复制与充值状态确认的失败提示也必须附安全正文;操作指引不能替换底层剪贴板、网络或支付查询原因。
@@ -62,6 +62,7 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时,
- 共享 Prompt Polish hook 的注入请求/规范化/回填异常原来只显示固定失败提示;现保留精确脱敏后的异常正文,聊天输入和资源润色共用同一口径。
- 资源画布历史生成任务账本读取失败原来统一显示“暂时无法恢复历史任务”;现在保留 Tauri/文件读取正文,非数组响应仍单独保留契约错误提示。
- 发现默认 `requestChatPromptPolish` 还会把 Tauri 润色命令异常吞成 `null`,导致共享 hook 无法看到平台正文;现仅对空回包/桥不可用保留 `null`,原生异常继续上抛并统一脱敏。
- 润色提醒偏好读取失败原来被当成“未关闭”默认值,写入失败只进错误上报池;现把读取/保存的 IPC 正文显示在输入区状态提示中,同时仍维持安全默认和回退行为。
- Direct 活动回合事件桥注册失败原来只退回启动快照,面板没有说明后续更新已失联;现保留事件桥 IPC 正文并在标题栏/活动项目面板显示。
- 首页富文本输入的原生剪贴板图片/文本读取失败原来直接按空剪贴板处理;现通过输入区错误提示保留脱敏后的剪贴板 IPC 正文。
- 账号邀请码复制和微信充值状态确认仍有固定“复制失败/暂时没能确认到账”路径;现保留剪贴板/支付查询错误正文并继续给操作指引。