unbridled-41 opened a new pull request, #4740:
URL: https://github.com/apache/rocketmq-dashboard/pull/4740

   Fixes #4739.
   
   ### Problem
   
   A send the server refused cleared the composer and never gave the draft 
back. `Composer.handleSend` 
(`web/src/pages/ai/components/Composer.tsx:256-261`) calls `onSend(text)` and 
then `onChange('')`, and its own contract (`:108-113`) says the caller must 
restore the text when it has to reject the send — "no conversation yet, **409 
from the server**". The AI page does restore it 
(`web/src/pages/ai/index.tsx:115,119-124`, "a rejected send gives the cleared 
draft back"), but only when `startRun` resolves `null`, and `startRun` resolved 
`null` only for a failed conversation *creation*: `useAiSend.startRun` 
(`web/src/pages/ai/hooks/useAiSend.ts:108-111`) used `void 
optionsRef.current.send(current, request); return current;` for an existing 
conversation, so the id came back whether or not the send was admitted. The 
home-page handoff had the same shape 
(`web/src/pages/ai/hooks/useDraftHandoff.ts:122-123`), clearing the applied 
prompt regardless of the result.
   
   `useAgentRun.startStream` (`web/src/pages/ai/hooks/useAgentRun.ts:285-295`, 
`:318-336`) already had the information — two guards refuse before opening 
anything, and a rejection from `open()` with no frame delivered means the run 
never started — it just never reported it.
   
   Trigger (deterministic): on `/ai/c/{id}`, send while another tab holds a run 
in flight. The server answers 409, the page shows the error alert and the 
button returns to 发送 — and the composer is empty, even though the prompt never 
left the browser. The same happens when the provider rejects the prompt or the 
stream fails before its first frame.
   
   ### Root cause
   
   The send's admission result was not part of the send API: 
`send`/`startStream` returned `Promise<void>`, so "the server never started a 
run" and "the run started" were indistinguishable to callers, and `startRun` 
could only report creation failures.
   
   ### Fix
   
   `useAgentRun.startStream` now resolves to whether the run was admitted — 
`false` for its two refusal guards and for a failure that produced no frame, 
`true` once the first event arrives (a mid-stream failure is an admitted run 
and must not resurrect the draft). `send` passes that through (`attach` keeps 
returning `void`), `useAiSend.startRun` returns `null` for a refused send on an 
existing conversation, and the handoff applies the emptied draft only when a 
target was resolved. `attach` and every existing caller keep their contracts; 
`ai/index.tsx` needed no change — its restore-on-null path now covers both 
reasons.
   
   ### Priority
   
   **68** = impact 22 + reach 10 + reproducibility 20 + maintenance value 16.
   
   - Impact 22/40: the operator retypes a prompt that never reached the server; 
the two send paths of the same page disagreed about the same failure, and the 
composer accepts long multi-line instructions.
   - Reach 10/20: every refused send — a 409 from a concurrent run, a provider 
rejection, a connection that dies before the first frame.
   - Reproducibility 20/20: deterministic — reject `openRunStream` with the 
existing 409 fixture.
   - Maintenance value 16/20: removes an asymmetry the code itself documents 
("a rejected send gives the cleared draft back"), and gives `send` the 
admission verdict its callers were guessing at.
   
   `FIX_CONFIDENCE`: **88** — the composer's documented contract, the existing 
comment on the restore path and the DLQ-free unit fixtures fix the expected 
behavior; the change is a return value plus its two consumers.
   
   ### Tests
   
   Red (source at base commit `7ce9a682`, two new tests):
   
   ```
   npx vitest run src/pages/ai/__tests__/AiPage.test.tsx -t 
"givesTheDraftBack|keepsTheHandoffDraft"
       × givesTheDraftBackWhenTheServerRefusesTheSendTest
       × keepsTheHandoffDraftWhenTheSendIsRefusedTest
   Expected the element to have value: 第二次发送   (received '')
   Expected the element to have value: 检查集群状态  (received '')
   ```
   
   Each has teeth independently: the handoff test was re-run with only that 
part of the fix reverted and failed again, then passed once restored.
   
   Green (this branch, `fix/restore-draft-on-refused-send` @ ed7f223f):
   
   ```
   npx vitest run src/pages/ai/__tests__/AiPage.test.tsx     → 17 passed (17)
   npx vitest run src/pages/ai src/api/ai                    → 18 files, 253 
passed (253)
   npx vitest run src/pages/ai                               → 14 files, 179 
passed (179)
   npx vitest run --maxWorkers=4                             → 134 files, 1237 
passed (1237)
   npx tsc -b                                                → clean
   npx eslint <the 5 changed files>                          → clean
   npm run build                                             → ✓ built in 11.10s
   ```
   
   (The full-suite run measured alone: a first attempt overlapped another 
vitest process and hit the repository's known load-sensitive `ConsumerPage > 
keeps the latest client stack…` case, which passes in isolation and in the 
clean run — recorded here because it is the same "flaky under load" behavior 
the earlier reports on this repository describe.)
   
   `npx tsc -b` also caught five test fixtures declaring `send` as 
`Promise<void>` — exactly the type change this PR makes; they are updated in 
`useAgentRun.test.ts`.
   
   ### Risk
   
   Low. Two behavior changes, both intended: a refused send no longer clears 
the composer, and a stream that dies before its first frame now reports as "not 
admitted" so the draft returns (a stream that delivered any frame keeps the 
composer cleared, even if it then fails). `attach` and the run lifecycle are 
untouched; the existing tests that pin the 409 toast and the double-submit 
guard still pass.
   
   ### Base branch
   
   Targets `rocketmq-studio`, the development trunk. Note the repository's 
default branch is still `master`; GitHub interprets the closing keyword only 
for PRs that target the default branch, so the `Fixes` link above does not 
auto-close #4739 when this merges — the issue needs to be closed by hand.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to