Conversation
- 拆分 ForceProjectDialog 为多个子组件 (SessionInfo, CountdownTimer, ProjectSelector) - 提取 useCountdown hook 用于倒计时逻辑 - 点击项目直接绑定,无需确认按钮 - 优化项目按钮样式,更加明显 (amber 边框和背景) - 在请求列表中为等待绑定的请求显示 "等待绑定" 状态 - 在请求详情页添加项目绑定横幅,支持重新绑定 - 完善国际化支持 - 修复弹窗 z-index 问题,移至 SidebarProvider 外部 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 Walkthrough概览本PR重构了ForceProjectDialog的倒计时逻辑,引入可复用的useCountdown钩子和专门的超时处理器,提取独立的子组件(SessionInfo、CountdownTimer、ProjectSelector),并在请求详情页面和列表页面增加项目绑定功能,扩展了国际化支持。 变更
代码审查工作量评估🎯 4(复杂) | ⏱️ ~45 分钟 可能相关的PR
建议的审核者
诗歌
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@web/src/components/force-project-dialog.tsx`:
- Around line 178-182: 当前超时处理器 handleTimeout 只调用 onClose() 导致行为与翻译
sessions.timeoutWarning 不一致;在 handleTimeout 中检测到 event 存在时应调用
rejectSession(event)(然后再调用 onClose()),以在超时自动拒绝会话;如果不想自动拒绝则相反地更新
sessions.timeoutWarning 翻译文案以准确反映“仅关闭弹窗不拒绝请求”行为。
In `@web/src/hooks/use-countdown.ts`:
- Around line 58-62: The effect in use-countdown uses remainingTime and
onComplete and can re-run and call onComplete again if onComplete identity
changes while remainingTime is 0; fix this by adding a ref flag (e.g.,
calledRef) inside the useCountdown hook to ensure onComplete is invoked only
once per countdown: check remainingTime === 0 && !calledRef.current before
calling onComplete, set calledRef.current = true after calling, and reset
calledRef.current = false when the countdown restarts (e.g., when remainingTime
becomes > 0 or when the start/reset function is invoked); keep the useEffect
dependency array as [remainingTime, onComplete] but rely on the ref to prevent
duplicate calls.
🧹 Nitpick comments (4)
web/src/hooks/use-countdown.ts (1)
41-56: 依赖数组中包含remainingTime会导致每秒重建 interval。当前实现在每次
remainingTime变化时都会清除并重新创建 interval,虽然功能正确,但会产生不必要的开销。可以使用useRef来避免将remainingTime加入依赖数组。♻️ 建议的优化方案
+import { useEffect, useState, useCallback, useRef } from 'react'; -import { useEffect, useState, useCallback } from 'react'; export function useCountdown({ initialSeconds, onComplete, autoStart = true, }: UseCountdownOptions): UseCountdownReturn { const [remainingTime, setRemainingTime] = useState(initialSeconds); const [isRunning, setIsRunning] = useState(autoStart); + const onCompleteRef = useRef(onComplete); + + useEffect(() => { + onCompleteRef.current = onComplete; + }, [onComplete]); // ... reset, start, stop callbacks ... useEffect(() => { - if (!isRunning || remainingTime <= 0) return; + if (!isRunning) return; const interval = setInterval(() => { setRemainingTime((prev) => { if (prev <= 1) { clearInterval(interval); setIsRunning(false); + onCompleteRef.current?.(); return 0; } return prev - 1; }); }, 1000); return () => clearInterval(interval); - }, [isRunning, remainingTime]); + }, [isRunning]); - useEffect(() => { - if (remainingTime === 0 && onComplete) { - onComplete(); - } - }, [remainingTime, onComplete]); return { remainingTime, isRunning, reset, start, stop }; }web/src/pages/requests/detail.tsx (2)
55-58:needsProjectBinding的条件检查可以更加健壮。当前条件
!request.projectID || request.projectID === 0是正确的,但考虑到!request.projectID已经涵盖了0、null、undefined的情况,|| request.projectID === 0是冗余的。♻️ 简化条件表达式
const needsProjectBinding = request && ( request.status === 'REJECTED' || - (request.status === 'PENDING' && forceProjectBinding && (!request.projectID || request.projectID === 0)) + (request.status === 'PENDING' && forceProjectBinding && !request.projectID) );
198-240: 绑定横幅 UI 实现良好,但可考虑添加 toast 反馈。当前实现通过按钮状态变化(加载中 → 成功图标)来显示绑定结果。建议结合 toast 通知,特别是绑定失败时,让用户获得更明确的错误反馈。
另外,Line 86-88 的错误处理只是
console.error,用户无法感知失败原因:} catch (error) { console.error('Failed to bind project:', error); setSelectedProjectId(0); // 考虑添加: toast.error(t('sessions.bindFailed')); }web/src/pages/requests/index.tsx (1)
342-365: 可选:抽出 isPendingBinding 逻辑避免重复。
当前在 RequestStatusBadge 与 LogRow 各自计算,后续规则变化需要双改;可抽成 helper 或直接传入 boolean。
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
web/src/components/force-project-dialog.tsxweb/src/components/layout/app-layout.tsxweb/src/hooks/use-countdown.tsweb/src/locales/en.jsonweb/src/locales/zh.jsonweb/src/pages/requests/detail.tsxweb/src/pages/requests/index.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
web/src/hooks/use-countdown.ts (1)
launcher/script.js (1)
seconds(121-121)
🔇 Additional comments (11)
web/src/components/layout/app-layout.tsx (1)
15-38: LGTM! 将 ForceProjectDialog 移至 SidebarProvider 外部是修复 z-index 问题的正确做法。使用 Fragment 包装并将弹窗渲染为 SidebarProvider 的兄弟元素,确保弹窗不会被侧边栏的层叠上下文影响。代码结构清晰,注释说明了这样做的原因。
web/src/locales/zh.json (1)
142-142: LGTM! 新增的国际化键与英文版本保持一致。翻译内容准确且符合中文习惯用法,键名结构与
en.json保持同步。Also applies to: 292-301
web/src/pages/requests/detail.tsx (1)
72-90: LGTM! 项目绑定处理逻辑完整。错误处理恰当,失败时重置
selectedProjectId,成功后刷新请求数据并显示反馈。useCallback的依赖数组也是正确的。web/src/locales/en.json (1)
142-142: LGTM! 国际化键结构清晰,命名一致。新增的状态和会话相关翻译文案准确描述了功能场景。
Also applies to: 292-301
web/src/components/force-project-dialog.tsx (2)
35-163: LGTM! 子组件拆分提高了可维护性。将
SessionInfo、CountdownTimer、ProjectSelector提取为独立组件是良好的重构实践,每个组件职责单一,便于测试和复用。类型定义也很清晰。
255-261: 点击项目直接绑定的交互设计符合 PR 目标。
ProjectSelector的onSelect直接调用handleConfirm实现了"点击项目直接绑定,无需确认按钮"的需求。disabled属性正确地在任一操作进行时禁用所有按钮,防止重复操作。web/src/pages/requests/index.tsx (5)
95-96: 强制绑定开关读取清晰。
将设置值归一为布尔后再下游使用,逻辑直观。
280-281: 向 LogRow 透传开关到位。
确保行级渲染能够感知强制绑定配置。
465-492: 行级 pendingBinding 判断合理。
状态/项目 ID 组合判定与需求一致。
571-571: 绑定等待态的样式区分清晰。
hover 与 marquee 的抑制条件合理,视觉层级清楚。Also applies to: 579-585, 587-590
665-669: 状态徽标参数补齐。
确保徽标能呈现绑定等待态。
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| const handleTimeout = useCallback(() => { | ||
| if (event) { | ||
| onClose(); | ||
| } | ||
| }, [event, onClose]); |
There was a problem hiding this comment.
超时处理与警告文案不一致。
警告文案 sessions.timeoutWarning 说明"如果未在规定时间内选择项目,请求将被拒绝",但 handleTimeout 只是关闭弹窗而没有调用 rejectSession。这可能导致用户体验与预期不符。
🐛 建议在超时时自动拒绝会话
const handleTimeout = useCallback(() => {
if (event) {
+ rejectSession.mutateAsync(event.sessionID).catch((error) => {
+ console.error('Failed to reject session on timeout:', error);
+ });
onClose();
}
- }, [event, onClose]);
+ }, [event, onClose, rejectSession]);或者,如果超时只是关闭弹窗而不拒绝请求,则需要更新 timeoutWarning 的翻译文案以准确描述行为。
🤖 Prompt for AI Agents
In `@web/src/components/force-project-dialog.tsx` around lines 178 - 182, 当前超时处理器
handleTimeout 只调用 onClose() 导致行为与翻译 sessions.timeoutWarning 不一致;在 handleTimeout
中检测到 event 存在时应调用 rejectSession(event)(然后再调用
onClose()),以在超时自动拒绝会话;如果不想自动拒绝则相反地更新 sessions.timeoutWarning
翻译文案以准确反映“仅关闭弹窗不拒绝请求”行为。
| useEffect(() => { | ||
| if (remainingTime === 0 && onComplete) { | ||
| onComplete(); | ||
| } | ||
| }, [remainingTime, onComplete]); |
There was a problem hiding this comment.
onComplete 可能在 remainingTime 已为 0 时被重复调用。
如果 onComplete 回调函数引用发生变化(例如父组件重新渲染),且此时 remainingTime 已经是 0,这个 effect 会再次触发 onComplete。
🐛 建议使用 ref 或添加调用标记
+ const hasCompletedRef = useRef(false);
useEffect(() => {
- if (remainingTime === 0 && onComplete) {
+ if (remainingTime === 0 && onComplete && !hasCompletedRef.current) {
+ hasCompletedRef.current = true;
onComplete();
}
}, [remainingTime, onComplete]);
+ // 在 reset 中重置标记
const reset = useCallback(
(seconds?: number) => {
setRemainingTime(seconds ?? initialSeconds);
setIsRunning(autoStart);
+ hasCompletedRef.current = false;
},
[initialSeconds, autoStart],
);🤖 Prompt for AI Agents
In `@web/src/hooks/use-countdown.ts` around lines 58 - 62, The effect in
use-countdown uses remainingTime and onComplete and can re-run and call
onComplete again if onComplete identity changes while remainingTime is 0; fix
this by adding a ref flag (e.g., calledRef) inside the useCountdown hook to
ensure onComplete is invoked only once per countdown: check remainingTime === 0
&& !calledRef.current before calling onComplete, set calledRef.current = true
after calling, and reset calledRef.current = false when the countdown restarts
(e.g., when remainingTime becomes > 0 or when the start/reset function is
invoked); keep the useEffect dependency array as [remainingTime, onComplete] but
rely on the ref to prevent duplicate calls.
Summary
Changes
force-project-dialog.tsx: 拆分为 SessionInfo, CountdownTimer, ProjectSelector 子组件use-countdown.ts: 新增倒计时 hookrequests/index.tsx: 添加等待绑定状态显示 (amber 样式)requests/detail.tsx: 添加项目绑定横幅,支持重新绑定app-layout.tsx: 将弹窗移至 SidebarProvider 外部,修复 z-index 问题locales/*.json: 添加国际化键Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
发布说明
新功能
用户界面改进
国际化
✏️ Tip: You can customize this high-level summary in your review settings.