Skip to content

[ZEPPELIN-6745] Add AI Assistant message and answer state UI - #5567

Merged
voidmatcha merged 6 commits into
apache:masterfrom
kimyenac:ZEPPELIN-6745
Oct 20, 2026
Merged

voidmatcha merged 6 commits into
apache:masterfrom
kimyenac:ZEPPELIN-6745

Conversation

@kimyenac

@kimyenac kimyenac commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What is this PR for?

Add the AI Assistant message and answer state UI: user and Assistant messages are shown distinctly, and an answer displays its loading, streaming, tool-use, completed and error states. Everything is driven by fixture props. There is no server, transport or panel state. The components live in projects/zeppelin-react/src/entities/assistant/ui.

This PR adds new files under entities/assistant/ui and removes the temporary Assistant/Setup story. It builds on the shared contract from ZEPPELIN-6741 (#5561) and the Markdown answer from ZEPPELIN-6744 (#5570), both on master.

Commits:

  1. Message item and answer view types
    • AssistantMessageItem renders a question or an answer:
      • A question is a right-aligned bubble, always shown as typed. It never goes through the content renderer.
      • An answer is a full-width body under an "AI / Assistant" label. While it streams, it is marked aria-busy and shows a caret where the text ends.
    • AssistantAnswerView is the display union (loading / streaming / done / error). It reuses AssistantAnswerMessage and AssistantToolCallRef from the ZEPPELIN-6741 contract.
  2. Answer status, tool log and error displays
    • AssistantAnswer renders one view at a time. Each state fully replaces the last.
    • The progress line (role="status") reads "Thinking…", "Writing answer…" or "Running <tool>…".
    • AssistantToolLog is open while a tool runs and collapses once the answer completes. A tool without a name is labelled "Tool".
    • AssistantAnswerError is an antd Alert (role="alert"):
      • failed (run.failed) and stream (AssistantStreamError) are errors with Retry.
      • connection (AssistantConnectionError) is a warning with Reload conversation, because the answer may have finished on the server.
      • With no onRetry, no button is shown.
      • A partial answer that arrived before the error is kept.
  3. Stories
    • Assistant/Message: User, CompletedReply, StreamingReply, LongContent, Conversation.
    • Assistant/Status: Loading, Streaming, ToolRunning, Completed, CompletedWithTools, Failed, StreamError, ConnectionLost, ErrorWithoutRetry.
    • Assistant/Status/StateSwitcher switches the fixture from the Controls panel, so you can check that the states stay consistent in both themes.
  4. Review: remove the temporary theme check story
    • Assistant/Setup › TemporaryThemeCheck (AssistantScope.stories.tsx) was a placeholder until the Assistant UI stories landed. Nothing else referenced it.
  5. Review: align the answer UI with segmented history (by @voidmatcha)
    • A completed answer is an AssistantAnswerMessage with ordered segments. Its text joins the rounds in order, and its tool log lists the tools from every round.
    • Tools still marked running when an error arrives are shown as "status unknown", not as running or done.
  6. Review: render answers as Markdown by default
    • renderContent now defaults to MarkdownAnswer from ZEPPELIN-6744. A parent can still pass its own, for example one that resolves paragraph references.
    • MarkdownAnswer wraps the text in blocks, so the streaming caret is now drawn inside the last block (or the last item of a closing list). It stays on the line where the text ends instead of dropping below it.

Styling uses @vanilla-extract/css through the Assistant theme contract only, so light and dark need no per-component rules. The HTML/CSS screens in voidmatcha/assistant-storybook-share were used as a design reference. The code is written from scratch.

Out of scope (per the issue / ZEPPELIN-6748): run event → view derivation, panel mounting in AssistantWorkspace, real tool calls, approval, persistence and WebSocket handling. Showing failed tool calls (raised in review) is tracked in ZEPPELIN-6754.

What type of PR is it?

Feature

Todos

  • Use the ZEPPELIN-6744 Markdown component as the default renderContent once it merges

What is the Jira issue?

How should this be tested?

In zeppelin-web-angular/projects/zeppelin-react:

  • npm ci, npm test (254 passed), npm run typecheck, npm run lint and npm run build:storybook all pass.
    • typecheck needs npm ci in zeppelin-web-angular first, because the SDK resolves rxjs from there.
    • The new files also pass the frontend's prettier --check.
  • npm run storybook: both themes checked manually for every story above, including StateSwitcher.
  • The specs were mutation-checked: making the connection error say "Retry", keeping the progress line on error, dropping the "Tool" fallback, rendering a question through renderContent, keeping the tool log open, or going back to a plain-text default each fails exactly the intended test.

npm run test:storybook:

  • With --workers=1, all 68 tests pass.
  • With the default config (3 workers), 1–5 tests time out between runs.
    • The traces show asset requests to the python3 -m http.server web server failing (status -1) and a blank page.
    • On the earlier temporary base (4599ad2d9), its own story alone reproduced this: with --repeat-each=8, 1–2 of 16 time out.
    • So it is a problem with the existing smoke-test server, not with these stories. I left it untouched here.

Screenshots (if appropriate)

See the Assistant/Message and Assistant/Status stories.

Questions:

  • Does the license files need to update? No. There are no new dependencies.
  • Is there breaking changes for older versions? No. Only new files are added, plus the removal of a temporary story.
  • Does this needs documentation? No.

@voidmatcha
voidmatcha changed the base branch from master to branch-voidmatcha-assistant-storybook-temp October 12, 2026 04:40

@voidmatcha voidmatcha left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message and answer states are easy to inspect in Storybook, and the components can be developed independently of the server. Thanks for putting these together.

Could you remove the TemporaryThemeCheck story now that the actual Assistant stories are available?

The PR description still says this was opened against master, but the current base is the temporary Storybook branch. Please update that explanation so the diff and dependencies are clear to reviewers.

Since different contributors are building the UI pieces, we may want one integration pass to align the design and state presentation afterward. I can open a follow-up issue if that becomes necessary.

@kimyenac

Copy link
Copy Markdown
Contributor Author

Thanks for the review!

  • Removed the TemporaryThemeCheck story in 1cc3ea2. AssistantScope.stories.tsx had no other stories or references. Tests, typecheck, lint, build:storybook and the Storybook smoke test (30/30) pass.
  • Updated the description: the base is now the temporary Storybook branch, and it lists the prerequisite PRs this depends on.
  • Agreed on the integration pass. Happy to help align these with the other pieces once they land.

voidmatcha
voidmatcha previously approved these changes Oct 12, 2026

@voidmatcha voidmatcha left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 Once the prerequisite PRs merge, I will retarget this PR to master and check the final diff and CI before merging.

@voidmatcha

voidmatcha commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

@kimyenac #5561 is on master, and this PR doesn't need #5565. On master, AssistantAnswerMessage uses segments instead of content, so the answer component, specs, and stories need to use the segmented shape after a rebase. Could you rebase onto master, move them to segments, force-push, and then switch the base to master?

git fetch https://github.com/apache/zeppelin.git master
git rebase --onto FETCH_HEAD 4599ad2d9
git push --force-with-lease

If it helps, c55b0cf on my branch (voidmatcha@c55b0cf7b) makes that change. It cherry-picks cleanly onto the rebased branch, and the typecheck and tests pass.

Also, #5570 has merged. Please use its Markdown answer component as the default renderContent when updating this PR, as the existing TODO notes. Jira ZEPPELIN-6745 no longer lists #5565 as a blocker. Please update the PR description to remove that prerequisite and its outdated statement that #5570 is not open.

}

/** The tools behind an answer. Open while one is still running, collapsed once they have all finished. */
export const AssistantToolLog = ({ tools }: AssistantToolLogProps) => {

@seung-00 seung-00 Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there’s no UI for failed tool calls. Because tool calls could fail sometimes, it would be helpful to make those failures easy to notice. How about adding a UI for that case?
cc @voidmatcha

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. I agree that failed tool calls should be distinguishable in the UI. The server already sends failure information, but we need to carry it through the frontend contract and event mapping to the UI. To keep this PR within its current scope, I suggest tracking this as a follow-up under ZEPPELIN-6754.

@kimyenac, would you be comfortable taking this on as a follow-up after this PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will take ZEPPELIN-6754 as a follow-up after this PR, including making failed tool calls easy to notice. Thanks for raising it, @seung-00.

@kimyenac
kimyenac changed the base branch from branch-voidmatcha-assistant-storybook-temp to master October 20, 2026 04:42
@kimyenac
kimyenac dismissed voidmatcha’s stale review October 20, 2026 04:42

The base branch was changed.

@kimyenac

Copy link
Copy Markdown
Contributor Author

Done, thanks:

  • Rebased onto master (no conflicts), force-pushed, and switched the base to master.
  • Segmented history (00c1178, your commit as is): it applied cleanly, and the files match c55b0cf.
  • Markdown by default (06dcb82): renderContent now defaults to MarkdownAnswer. A new spec checks it, and it fails with a plain-text default.
    • Your segmented-rounds spec checked for 'First finding\n\nSecond finding' in plain text. It now checks the two Markdown paragraphs in order.
    • MarkdownAnswer wraps the text in blocks, so the streaming caret dropped below the text. It is now drawn inside the last block (or the last list item), so it stays at the end of the text. Checked in Storybook in both themes.
  • The description no longer has the [ZEPPELIN-6743] Mount an opt-in AI Assistant shell in the notebook sidebar #5565 prerequisite or the outdated [ZEPPELIN-6744] Add AI Assistant Markdown answer component #5570 note, and the Markdown todo is checked.

npm test (254 passed), typecheck, lint, build:storybook and test:storybook --workers=1 (68 passed) pass.

voidmatcha pushed a commit that referenced this pull request Oct 20, 2026
### What is this PR for?

Add the AI Assistant question composer: a controlled component that takes a value, change and submit callbacks, and read-only and sending states. Nothing is sent anywhere. The parent receives the question and decides what to do with it. The component lives in `projects/zeppelin-react/src/entities/assistant/ui`.

Commits:
1. **Question composer**
   * `AssistantComposer` is a `<form>` with an antd `Input.TextArea` (auto-sizing, 2–8 rows) and a **Send** `Button`.
   * The value is controlled. `onSubmit` receives the question with surrounding whitespace trimmed. Clearing the input afterwards is left to the parent, so it can keep the text if sending fails.
   * Nothing is submitted, by the button, Enter or the form, when:
     * the input is empty or whitespace only (the server rejects a blank prompt with 400),
     * `readOnly` is set (for example a conversation owned by another user). The textarea is disabled and a note explains why (`readOnlyReason`, with a default).
     * `sending` is set (the server accepts one run per conversation). The textarea becomes read-only, so the question stays visible, and the button shows a spinner.
   * Enter sends and Shift+Enter adds a new line. An Enter that confirms an IME composition (Korean, Japanese, …) does not send. Otherwise the last syllable of a Korean question would send it. Commit 3 extends this to Safari.
2. **Stories**
   * `Assistant/Composer`: Empty, Draft, WhitespaceOnly, Sending, ReadOnly.
   * `Assistant/Composer/Playground` keeps its own value: type, send with the button or Enter, see `onSubmit` in the Actions panel and the input cleared.
3. **Review: do not send on the Enter that confirms an IME composition in Safari** (by <at>voidmatcha)
   * Safari fires `compositionend` before that Enter, so it arrives with `isComposing: false` and `keyCode: 229`. The check now covers both.
4. **Review: pin the controlled value of the composer in tests** (by <at>voidmatcha)
   * The field keeps showing the parent's value when `onChange` is ignored, and shows and sends a value the parent sets later. Both tests fail if `value` becomes `defaultValue`.
5. **Review: raise the composer placeholder contrast**
   * antd's placeholder colour was about 1.8:1 (light) and 2.3:1 (dark). It now uses the theme's `textTertiary`, which the theme lifts to 4.5:1. Measured in Storybook: 6.10:1 / 6.39:1 on the panel, 5.84:1 on the read-only field.

Styling uses `<at>vanilla-extract/css` through the Assistant theme contract only. The field draws a single focus ring for the textarea inside it. The HTML/CSS screens in `voidmatcha/assistant-storybook-share` were used as a design reference. The code is written from scratch.

**Out of scope** (per the issue): per-conversation draft persistence, actual sending, Stop and retry. These belong to panel assembly (ZEPPELIN-6748) or follow-up issues.

### What type of PR is it?
Feature

### Todos
* [ ] - Task

### What is the Jira issue?
* https://issues.apache.org/jira/browse/ZEPPELIN-6746

### How should this be tested?
In `zeppelin-web-angular/projects/zeppelin-react`:
* `npm ci`, `npm test` (247 passed, 8 new), `npm run typecheck`, `npm run lint` and `npm run build:storybook` all pass.
  * `typecheck` needs `npm ci` in `zeppelin-web-angular` first, because the SDK resolves `rxjs` from there.
  * The new files also pass the frontend's `prettier --check`.
* `npm run storybook`: every story above checked in both themes, including typing Korean and pressing Enter mid-composition in Playground (Chrome). Safari was not tried; commit 3 is covered by a `keyCode: 229` unit test.
* The spec was mutation-checked: dropping the IME check, the Safari `keyCode` check, the trim, the sending guard, the read-only guard or the read-only textarea while sending, or using `defaultValue`, each fails the intended test.
* `npm run test:storybook -- --workers=1`: all 52 tests pass. It runs with one worker because the default 3 workers made the existing `python3 -m http.server` web server drop requests on the earlier base, independent of these stories (see #5567).

### Screenshots (if appropriate)
See the `Assistant/Composer` stories.

### Questions:
* Does the license files need to update? No. There are no new dependencies.
* Is there breaking changes for older versions? No. Only new files are added.
* Does this needs documentation? No.



Closes #5569 from kimyenac/ZEPPELIN-6746.

Signed-off-by: YONGJAE LEE <dev.yongjaelee@gmail.com>

@voidmatcha voidmatcha left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@voidmatcha

voidmatcha commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

For reference, here is a local reproduction of the current caret placement, using this PR's rendered markup and styles.

To reproduce in Storybook:

  1. Open Assistant -> Message -> Streaming Reply (?path=/story/assistant-message--streaming-reply).
  2. Keep streaming set to true.
  3. In Controls, replace content with the following example, without a closing code fence or a trailing newline:
```sql
SELECT region, SUM(revenue)
FROM sales
GROUP BY region;

The caret appears on a separate line below the code content.

Current streaming caret placement

This is a non-blocking visual suggestion for a follow-up.

@voidmatcha
voidmatcha merged commit f801b2e into apache:master Oct 20, 2026
24 checks passed
@voidmatcha

Copy link
Copy Markdown
Member

Merged into master (f801b2e).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants