This is an automated email from the ASF dual-hosted git repository.

voidmatcha pushed a commit to branch 
branch-voidmatcha-assistant-integration-temp
in repository https://gitbox.apache.org/repos/asf/zeppelin.git

commit 69dbc89df31fc7d94e1075a3ada32e4e3e966ba7
Author: kimyenac <[email protected]>
AuthorDate: Mon Oct 12 12:53:50 2026 +0900

    [ZEPPELIN-6745] Add stories for AI Assistant messages and answer states
---
 .../assistant/ui/AssistantAnswer.stories.tsx       | 88 ++++++++++++++++++++++
 .../assistant/ui/AssistantMessageItem.stories.tsx  | 62 +++++++++++++++
 2 files changed, 150 insertions(+)

diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/src/entities/assistant/ui/AssistantAnswer.stories.tsx
 
b/zeppelin-web-angular/projects/zeppelin-react/src/entities/assistant/ui/AssistantAnswer.stories.tsx
new file mode 100644
index 0000000000..cedeff5d6e
--- /dev/null
+++ 
b/zeppelin-web-angular/projects/zeppelin-react/src/entities/assistant/ui/AssistantAnswer.stories.tsx
@@ -0,0 +1,88 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { AssistantConnectionError, AssistantStreamError } from 
'../model/assistantContract';
+import type { AssistantAnswerView } from './answerView';
+import { AssistantAnswer } from './AssistantAnswer';
+
+const partial = 'The notebook groups orders by region and';
+const reply = 'The notebook groups orders by region and ranks them by total 
revenue.';
+
+/** One fixture per state, in the order a run passes through them. */
+const fixtures = {
+  loading: { status: 'loading' },
+  streaming: { status: 'streaming', content: partial },
+  toolRunning: {
+    status: 'streaming',
+    content: '',
+    tools: [
+      { id: 't1', name: 'list_paragraphs', status: 'done' },
+      { id: 't2', name: 'list_paragraphs', status: 'running' }
+    ]
+  },
+  completed: { status: 'done', message: { id: 'a1', role: 'assistant', 
content: reply } },
+  completedWithTools: {
+    status: 'done',
+    message: {
+      id: 'a1',
+      role: 'assistant',
+      content: reply,
+      toolCalls: [
+        { id: 't1', name: 'list_paragraphs' },
+        { id: 't2', name: 'list_paragraphs' }
+      ]
+    }
+  },
+  failed: { status: 'error', kind: 'failed', message: 'The model request 
failed.' },
+  streamError: {
+    status: 'error',
+    kind: 'stream',
+    message: new AssistantStreamError().message,
+    content: partial,
+    tools: [{ id: 't1', name: 'list_paragraphs', status: 'done' }]
+  },
+  connectionLost: {
+    status: 'error',
+    kind: 'connection',
+    message: new AssistantConnectionError().message,
+    content: partial
+  }
+} satisfies Record<string, AssistantAnswerView>;
+
+type FixtureName = keyof typeof fixtures;
+
+const meta = {
+  title: 'Assistant/Status',
+  component: AssistantAnswer,
+  args: { view: fixtures.loading, onRetry: () => {} }
+} satisfies Meta<typeof AssistantAnswer>;
+
+export default meta;
+type Story = StoryObj<typeof meta>;
+
+export const Loading: Story = {};
+export const Streaming: Story = { args: { view: fixtures.streaming } };
+export const ToolRunning: Story = { args: { view: fixtures.toolRunning } };
+export const Completed: Story = { args: { view: fixtures.completed } };
+export const CompletedWithTools: Story = { args: { view: 
fixtures.completedWithTools } };
+export const Failed: Story = { args: { view: fixtures.failed } };
+export const StreamError: Story = { args: { view: fixtures.streamError } };
+export const ConnectionLost: Story = { args: { view: fixtures.connectionLost } 
};
+export const ErrorWithoutRetry: Story = { args: { view: fixtures.failed, 
onRetry: undefined } };
+
+/** Switch the fixture from the controls panel to check that each state fully 
replaces the last. */
+export const StateSwitcher: StoryObj<{ state: FixtureName }> = {
+  args: { state: 'loading' },
+  argTypes: { state: { control: 'select', options: Object.keys(fixtures) } },
+  render: ({ state }) => <AssistantAnswer view={fixtures[state]} onRetry={() 
=> {}} />
+};
diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/src/entities/assistant/ui/AssistantMessageItem.stories.tsx
 
b/zeppelin-web-angular/projects/zeppelin-react/src/entities/assistant/ui/AssistantMessageItem.stories.tsx
new file mode 100644
index 0000000000..75931eb2b7
--- /dev/null
+++ 
b/zeppelin-web-angular/projects/zeppelin-react/src/entities/assistant/ui/AssistantMessageItem.stories.tsx
@@ -0,0 +1,62 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { AssistantAnswer } from './AssistantAnswer';
+import { AssistantMessageItem } from './AssistantMessageItem';
+
+const question = 'Explain how this notebook calculates revenue.';
+const reply = 'The notebook groups orders by region and ranks them by total 
revenue.';
+
+const meta = {
+  title: 'Assistant/Message',
+  component: AssistantMessageItem,
+  args: { role: 'user', content: question }
+} satisfies Meta<typeof AssistantMessageItem>;
+
+export default meta;
+type Story = StoryObj<typeof meta>;
+
+export const User: Story = {};
+
+export const CompletedReply: Story = {
+  args: { role: 'assistant', content: reply }
+};
+
+export const StreamingReply: Story = {
+  args: { role: 'assistant', content: 'The notebook groups orders by region 
and', streaming: true }
+};
+
+export const LongContent: Story = {
+  args: {
+    role: 'assistant',
+    content: [
+      'Paragraph 2 loads `orders` and joins it with `regions` on region_id.',
+      'Paragraph 3 sums amount per region and sorts the totals in descending 
order.',
+      'A very long identifier such as 
orders_by_region_with_currency_normalisation_applied_v2 wraps instead of 
overflowing.'
+    ].join('\n\n')
+  }
+};
+
+export const Conversation: Story = {
+  render: () => (
+    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
+      <AssistantMessageItem role="user" content={question} />
+      <AssistantAnswer
+        view={{
+          status: 'done',
+          message: { id: 'a1', role: 'assistant', content: reply, toolCalls: 
[{ id: 't1', name: 'list_paragraphs' }] }
+        }}
+      />
+    </div>
+  )
+};

Reply via email to