fix: Telegram HTML 解析錯誤 + 簽核後內容保留

修復:
1. telegram_gateway.py - HTML 轉義 (html.escape) 防止 "Can't parse entities"
2. openclaw-state-machine.tsx - 簽核後顯示結果 2 秒再導航

問題根因:
- URL 和用戶輸入內容可能包含 <, >, & 破壞 HTML
- 簽核後立即刷新列表,已簽核項目消失

Memory: feedback_approval_preserve_content.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-26 15:32:23 +08:00
parent 505ceab895
commit b79e5f1a1a
2 changed files with 107 additions and 28 deletions

View File

@@ -107,6 +107,14 @@ export function OpenClawStateMachine({
const [selectedIndex, setSelectedIndex] = useState<number | null>(null)
const selectedApproval = selectedIndex !== null ? pendingApprovals[selectedIndex] : null
// 2026-03-26: 簽核後保留內容顯示 (feedback_approval_preserve_content.md)
// 儲存剛簽核/拒絕的項目,顯示結果後再清除
const [processedApproval, setProcessedApproval] = useState<{
approval: ApprovalRequest
action: 'approved' | 'rejected'
timestamp: Date
} | null>(null)
// Timer refs for cleanup
const pollTimerRef = useRef<NodeJS.Timeout | null>(null)
@@ -416,35 +424,95 @@ export function OpenClawStateMachine({
{/* Approval Modal - 全屏審核對話框 (Phase 17 UI/UX 修復) */}
<ApprovalModal
open={selectedIndex !== null}
onClose={() => setSelectedIndex(null)}
title="審核詳情"
onPrev={handlePrevApproval}
onNext={handleNextApproval}
current={selectedIndex !== null ? selectedIndex + 1 : undefined}
total={pendingApprovals.length}
open={selectedIndex !== null || processedApproval !== null}
onClose={() => {
setSelectedIndex(null)
setProcessedApproval(null)
}}
title={processedApproval ? (processedApproval.action === 'approved' ? '✅ 已批准' : '❌ 已拒絕') : '審核詳情'}
onPrev={processedApproval ? undefined : handlePrevApproval}
onNext={processedApproval ? undefined : handleNextApproval}
current={selectedIndex !== null && !processedApproval ? selectedIndex + 1 : undefined}
total={!processedApproval ? pendingApprovals.length : undefined}
>
{selectedApproval && (
{/* 2026-03-26: 顯示剛處理的項目結果 */}
{processedApproval && (
<div className="relative">
{/* 結果覆蓋層 */}
<div className={cn(
"absolute inset-0 z-10 flex items-center justify-center rounded-xl",
processedApproval.action === 'approved'
? "bg-status-healthy/10"
: "bg-status-critical/10"
)}>
<div className={cn(
"text-center px-6 py-4 rounded-lg backdrop-blur-sm",
processedApproval.action === 'approved'
? "bg-status-healthy/20 text-status-healthy"
: "bg-status-critical/20 text-status-critical"
)}>
<div className="text-4xl mb-2">
{processedApproval.action === 'approved' ? '✅' : '❌'}
</div>
<div className="font-bold text-lg">
{processedApproval.action === 'approved' ? '已批准執行' : '已拒絕執行'}
</div>
<div className="text-sm opacity-75 mt-1">
{processedApproval.timestamp.toLocaleTimeString()}
</div>
</div>
</div>
{/* 原始內容 (保留顯示) */}
<ApprovalCard
request={processedApproval.approval}
onApprove={async () => {}}
onReject={async () => {}}
holdDuration={1000}
fullHeight
/>
</div>
)}
{/* 正常待審核項目 */}
{selectedApproval && !processedApproval && (
<ApprovalCard
request={selectedApproval}
onApprove={async () => {
// 2026-03-26: 保留簽核後內容顯示 2 秒
setProcessedApproval({
approval: selectedApproval,
action: 'approved',
timestamp: new Date(),
})
const newCount = await handleApprove(selectedApproval.id) ?? 0
// 簽核後: 根據 API 返回的新數量決定導航
if (newCount > 0 && selectedIndex !== null) {
// 還有待審核項目,保持 Modal 開啟
// 如果當前 index 超出範圍,調整到最後一筆
if (selectedIndex >= newCount) {
setSelectedIndex(newCount - 1)
// 顯示結果 2 秒後導航
setTimeout(() => {
setProcessedApproval(null)
if (newCount > 0 && selectedIndex !== null) {
if (selectedIndex >= newCount) {
setSelectedIndex(newCount - 1)
}
} else {
setSelectedIndex(null)
}
// 否則保持 index 不變,列表刷新後自動顯示下一筆
} else {
// 沒有待審核項目了,關閉 Modal
setSelectedIndex(null)
}
}, 2000)
}}
onReject={async () => {
// 2026-03-26: 保留拒絕後內容顯示 2 秒
setProcessedApproval({
approval: selectedApproval,
action: 'rejected',
timestamp: new Date(),
})
await handleReject(selectedApproval.id)
setSelectedIndex(null)
// 顯示結果 2 秒後關閉
setTimeout(() => {
setProcessedApproval(null)
setSelectedIndex(null)
}, 2000)
}}
holdDuration={1000}
fullHeight