Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (3)
Walkthrough이 PR은 소켓으로 수신되는 메시지에 Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/(route)/chat/[id]/ChatPage.tsx (1)
81-108:⚠️ Potential issue | 🟠 Major상태값만으로는 초고속 연타를 완전히 막지 못합니다.
setIsAwaitingResponse(true)는 다음 렌더에서 반영되므로, 같은 렌더 사이클에서 다시 들어온 submit은 아직false인 값을 봅니다. 빠른 Enter 연타나 이중 클릭이면 같은 이벤트 루프 내에서 여러 번send()가 호출될 수 있어서, UI state와 별도로useRef기반 동기 락을 두는 편이 안전합니다.제안된
awaitingResponseRef를 이용한lockAwaitingResponse()/unlockAwaitingResponse()방식이 적절합니다. 동기적으로 ref를 업데이트하면 같은 이벤트 사이클 내 후속 호출들을 즉시 차단할 수 있습니다.제안 코드
const [messages, setMessages] = useState<ChatMessage[]>([]); const [isAwaitingResponse, setIsAwaitingResponse] = useState(false); @@ const reactionRequestSeqRef = useRef<Record<string, number>>({}); const responseUnlockTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); + const awaitingResponseRef = useRef(false); + const unlockAwaitingResponse = useCallback(() => { + awaitingResponseRef.current = false; + setIsAwaitingResponse(false); + }, []); + + const lockAwaitingResponse = useCallback(() => { + awaitingResponseRef.current = true; + setIsAwaitingResponse(true); + }, []); + const clearResponseUnlockTimer = useCallback(() => { if (!responseUnlockTimerRef.current) return; clearTimeout(responseUnlockTimerRef.current); responseUnlockTimerRef.current = null; }, []); @@ const scheduleResponseUnlock = useCallback(() => { clearResponseUnlockTimer(); responseUnlockTimerRef.current = setTimeout(() => { - setIsAwaitingResponse(false); + unlockAwaitingResponse(); responseUnlockTimerRef.current = null; }, RESPONSE_IDLE_UNLOCK_MS); - }, [clearResponseUnlockTimer]); + }, [clearResponseUnlockTimer, unlockAwaitingResponse]); @@ const handleSubmit = (value: string) => { @@ - if (isAwaitingResponse) return; + if (awaitingResponseRef.current) return; @@ - setIsAwaitingResponse(true); + lockAwaitingResponse(); clearResponseUnlockTimer(); @@ try { send(trimmedValue); } catch (err) { clearResponseUnlockTimer(); - setIsAwaitingResponse(false); + unlockAwaitingResponse(); setStreamError((err as Error).message ?? '메시지 전송에 실패했습니다.'); } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`(route)/chat/[id]/ChatPage.tsx around lines 81 - 108, The UI state is racy because setIsAwaitingResponse updates on next render, so add a synchronous ref-based lock: create awaitingResponseRef = useRef(false) and implement lockAwaitingResponse() that returns false if already true, otherwise sets it true and returns true, plus unlockAwaitingResponse() that clears it and calls clearResponseUnlockTimer()/scheduleResponseUnlock() as appropriate; update the send()/submit flow to call lockAwaitingResponse() at start (and bail if it returns false), call setIsAwaitingResponse(true) for UI, and ensure unlockAwaitingResponse() is called on completion or error (and clear the ref in clearResponseUnlockTimer/scheduleResponseUnlock interactions) so rapid repeated events are blocked immediately.
🧹 Nitpick comments (1)
src/components/wrappers/QueryBox.tsx (1)
14-35:disabled가 버튼 클릭에만 적용되고 Enter 제출 경로는 그대로 열려 있습니다.지금 구현은
SendButton만 막고TextArea의onSubmit은 그대로 넘겨서, 호출처가 별도 가드를 빼먹으면 Enter로는 계속 제출됩니다. 공용 컴포넌트인 만큼QueryBox내부에서 제출 조건을 한 번만 계산해서 버튼/키보드 경로를 같이 막는 편이 안전합니다.제안 코드
export default function QueryBox({ value, onChange, onSubmit, disabled = false, inputDisabled = false, }: Props) { + const canSubmit = !disabled && !!value.trim(); + + const handleSubmit = () => { + if (!canSubmit) return; + onSubmit(); + }; + return ( <div className="relative w-full"> <TextArea heightLines={2} maxHeightLines={6} @@ showMax value={value} onChange={e => onChange(e.target.value)} - onSubmit={onSubmit} + onSubmit={handleSubmit} disabled={inputDisabled} className="shadow-[0_2px_4px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.04)]" /> - <SendButton disabled={disabled || !value.trim()} onClick={onSubmit} /> + <SendButton disabled={!canSubmit} onClick={handleSubmit} /> </div> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/wrappers/QueryBox.tsx` around lines 14 - 35, The component-level submission guard is missing: QueryBox currently only uses the disabled prop to disable SendButton while passing onSubmit directly into TextArea, allowing Enter submissions when disabled; change QueryBox to compute a single isDisabled = disabled || inputDisabled || !value?.trim() and pass that to both SendButton (disabled) and TextArea (prevent calling onSubmit when isDisabled is true), i.e., keep onChange as-is but wrap or gate calls to onSubmit inside QueryBox (use the computed isDisabled to early-return) so both button and keyboard paths honor the same condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/`(route)/chat/[id]/ChatPage.tsx:
- Around line 188-196: The current flow calls appendAiMessage(payload) before
checking control frames, which causes an empty AI bubble when payload.isEnd
arrives with no content; change the logic in the success branch to first inspect
payload.isEnd and, if true, run the unlock logic (call
clearResponseUnlockTimer(), setIsAwaitingResponse(false)) and
setStreamError(null) as needed without calling appendAiMessage, then return;
only when payload.isEnd is false should you call appendAiMessage(payload) and
scheduleResponseUnlock(); update the block that references appendAiMessage,
payload, isEnd, clearResponseUnlockTimer, scheduleResponseUnlock,
setIsAwaitingResponse, and setStreamError accordingly.
---
Outside diff comments:
In `@src/app/`(route)/chat/[id]/ChatPage.tsx:
- Around line 81-108: The UI state is racy because setIsAwaitingResponse updates
on next render, so add a synchronous ref-based lock: create awaitingResponseRef
= useRef(false) and implement lockAwaitingResponse() that returns false if
already true, otherwise sets it true and returns true, plus
unlockAwaitingResponse() that clears it and calls
clearResponseUnlockTimer()/scheduleResponseUnlock() as appropriate; update the
send()/submit flow to call lockAwaitingResponse() at start (and bail if it
returns false), call setIsAwaitingResponse(true) for UI, and ensure
unlockAwaitingResponse() is called on completion or error (and clear the ref in
clearResponseUnlockTimer/scheduleResponseUnlock interactions) so rapid repeated
events are blocked immediately.
---
Nitpick comments:
In `@src/components/wrappers/QueryBox.tsx`:
- Around line 14-35: The component-level submission guard is missing: QueryBox
currently only uses the disabled prop to disable SendButton while passing
onSubmit directly into TextArea, allowing Enter submissions when disabled;
change QueryBox to compute a single isDisabled = disabled || inputDisabled ||
!value?.trim() and pass that to both SendButton (disabled) and TextArea (prevent
calling onSubmit when isDisabled is true), i.e., keep onChange as-is but wrap or
gate calls to onSubmit inside QueryBox (use the computed isDisabled to
early-return) so both button and keyboard paths honor the same condition.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: aad1928d-bf18-4f43-ac65-511117786dd7
📒 Files selected for processing (5)
src/apis/chatSocket.tssrc/app/(route)/chat/[id]/ChatPage.tsxsrc/app/(route)/chat/_components/ChatQueryBox.tsxsrc/app/(route)/home/HomePage.tsxsrc/components/wrappers/QueryBox.tsx
68ef526 to
461c805
Compare
관련 이슈
PR 설명
채팅 페이지에서 답변 수신 중 연속 전송이 가능하던 문제를 수정했습니다.
src/app/(route)/chat/[id]/ChatPage.tsxisAwaitingResponse)를 추가했습니다.답변을 생성하고 있어요.로딩 UI가 보이도록 추가했습니다.router.replace대신history.replaceState를 사용하도록 바꿔 화면이 다시 로드되는 느낌을 줄였습니다.src/apis/chatSocket.tsisEnd값을 파싱하도록 수정했습니다.src/components/wrappers/QueryBox.tsxdisabled와inputDisabled역할을 나눴습니다.src/app/(route)/chat/_components/ChatQueryBox.tsxinputDisabled를 받을 수 있도록 수정했습니다.src/app/(route)/home/HomePage.tsx