Repository navigation
[ZEPPELIN-6742] Connect the AI Assistant frontend to conversation REST and WebSocket APIs - #5563
Conversation
2aaa236 to
b75de05
Compare
b75de05 to
c470802
Compare
|
This PR connects the frontend to the conversation REST API and WebSocket run events. The current diff is +1,527/-1 across four files. Excluding 869 lines in Server PR #5547 has merged. This PR still depends on #5558 and #5561, which are included in its temporary base for review. After those prerequisites merge, I will rebase and retarget the PR to |
b772a8d to
9f119fc
Compare
0c3b303 to
c41de73
Compare
9f119fc to
b0b71a0
Compare
c41de73 to
3af8991
Compare
b0b71a0 to
fc57755
Compare
3af8991 to
3bcb8e2
Compare
fc57755 to
404c586
Compare
3bcb8e2 to
e3e2119
Compare
404c586 to
e345f98
Compare
e3e2119 to
c221c82
Compare
c221c82 to
e4880f9
Compare
There was a problem hiding this comment.
Transport-level review. I reproduced the reported sequences by loading the module and controlling HTTP responses and socket events. I have not verified them against a live server or the notebook UI. The authentication comment is a contract question, not a confirmed integration defect.
| // ACKs belong to the connection, even if the note or conversation that sent the request has unmounted. | ||
| const trackPendingRun = (socket: AssistantSocket, conversationId: string, tracking: RunTracking): PendingRun => { | ||
| const pending: PendingRun = { abandoned: false }; | ||
| tracking.disconnected.delete(conversationId); |
There was a problem hiding this comment.
This removes the previous disconnected marker before the new request has been accepted.
I reproduced this sequence: run A starts, its connection closes, and a retry is sent without reloading history. When that retry receives run.failed with status 409, the transport reports idle, even though A's history has not been recovered. The failure message itself says the conversation is still answering.
This conflicts with the documented behavior of retaining the lost-connection state until history is reloaded. Please preserve that uncertainty when the retry is rejected, and cover the disconnect → retry → 409 sequence in a regression test.
There was a problem hiding this comment.
Thanks for catching this. The disconnected state now remains until the retry is accepted by the server. I also added a regression test for disconnect -> retry -> 409.
Fixed in 7ad64ff.
| {}, | ||
| onAuthError | ||
| ); | ||
| if (!before && runs.disconnected.delete(conversationId)) notifyRunState(runs, conversationId); |
There was a problem hiding this comment.
A successful latest-page response clears the current disconnected marker without checking whether the request predates that disconnect.
I reproduced this with an accepted run: start getMessages(), delay its response, close the socket, then release the history response. The state changes from disconnected to idle, although no history request was started after the connection loss.
Please ensure that a request spanning a newer disconnect cannot clear that disconnect's recovery state, and add a regression test for this ordering.
There was a problem hiding this comment.
Thanks for catching the race. A history response requested before a disconnect can no longer clear that disconnect state. I added a regression test for this response ordering as well.
Fixed in 6de3a98.
Keep the transport and sidebar shell independently buildable while both use the same SDK slot contract. Confidence: high Scope-risk: none Not-tested: full notebook runtime integration
7b16eb1 to
3e95adf
Compare
e4880f9 to
6de3a98
Compare
698542f to
a3acd8e
Compare
| ...later, | ||
| content: [earlier.content, later.content].filter(Boolean).join('\n\n'), | ||
| ...(toolCalls.length ? { toolCalls } : {}) | ||
| segments |
There was a problem hiding this comment.
These parts seem to conflict with the changes in #5561
| typeof value === 'object' && value !== null && 'body' in value ? value.body : value; | ||
|
|
||
| /** Hands 401/405 to the host, which owns login redirects and logout. */ | ||
| export type AssistantAuthErrorHandler = (status: number, location: string | null) => void; |
There was a problem hiding this comment.
The server doesn’t provide a redirect location. I’d prefer to leave unimplemented features out of the contract. What do you think?
There was a problem hiding this comment.
You're right. Nothing on the server sets Location on a 401: form auth redirects to /api/login with a 302 that fetch follows to a 405, and Kerberos returns a 401 with only WWW-Authenticate. I narrowed the handler to (status: number) => void and dropped location from the error in c83e5b0.
| disconnected: Map<string, object>; | ||
| listeners: Map<string, Set<(state: AssistantRunState) => void>>; | ||
| stopTracking?: () => void; | ||
| } |
There was a problem hiding this comment.
I wonder if we could simplify the per-conversation state tracking a bit. One concern is that a client-side timeout leaves pending and active intact. If no terminal event arrives and the WebSocket stays connected, the conversation could remain blocked.
Some of this complexity seems to come from sharing the notebook WebSocket, where events can keep arriving after the user leaves the view. A separate assistant WebSocket tied to the component lifecycle might simplify this eventually.
One approach we could consider for this PR, without changing the server, is:
-
Keep a single slot for the current request:
for example: :{ conversationId, phase: 'pending' | 'running', runId? }
The server already rejects concurrent requests in the same conversation with 409. We could ignore events whose conversationId does not match the slot, and clear both the slot and its subscription when the component unmounts. -
Track the remaining edge case separately:
A user sendshiin conversationC1, leaves the page, then returns toC1and sendsbye.An event fromhicould arrive while the client is waiting forbyeand be mistaken for its response.
The single-slot approach would not fully handle this timing-dependent case, but it seems unlikely to happen often. Could we open a separate issue for it and address it later?
There was a problem hiding this comment.
Thanks, done in d853f7d. A run now keeps only its own { conversationId, phase, runId? }, drops events for other conversations, and releases its subscriptions when it ends, times out, the socket closes, or the consumer unmounts. The duplicate check is left to the server's 409, and the abandoned/disconnected tracking and the run-state subscription are gone. I'll drop 'disconnected' and subscribeRunState from the contract in #5561.
For 2, an issue sounds good. Two related cases share the cause and could go in it: retrying after a client timeout can hit the same mix-up, and after a reconnect the run's events stay bound to the old connection, so the answer only shows after a reload. Echoing a client request id in run.started/run.failed would let the client tell them apart even before a dedicated socket.
What is this PR for?
Connect the Assistant frontend to the conversation REST and WebSocket APIs. The transport provides conversation creation and retrieval, message sending, and response delivery for the UI.
Server PR #5547 has merged. PR #5558 has merged. This PR still depends on UI contracts #5561; its temporary base contains those changes for review.
What type of PR is it?
Feature
Todos
What is the Jira issue?
ZEPPELIN-6742
How should this be tested?
Run from
zeppelin-web-angular/:npm run build-project:sdk npm run typecheck:react npm --prefix projects/zeppelin-react test -- src/entities/assistant/model/assistantTransport.spec.tsAlso verify the REST and WebSocket integration against the final API contract from server PR #5547.
Screenshots (if appropriate)
N/A
Questions: