Skip to content

Commit e6af26b

Browse files
committed
[TOOL-4294] Nebula: Improved Thinking message UI
1 parent fa8a39e commit e6af26b

File tree

9 files changed

+248
-167
lines changed

9 files changed

+248
-167
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { cn } from "../../lib/utils";
2+
3+
export function TextShimmer(props: {
4+
text: string;
5+
className?: string;
6+
}) {
7+
return (
8+
<div className="flex">
9+
<p
10+
className={cn(
11+
"animate-text-shimmer bg-[length:200%_50%] bg-clip-text text-transparent will-change-auto",
12+
props.className,
13+
)}
14+
style={{
15+
backgroundImage:
16+
"linear-gradient(70deg, hsl(var(--muted-foreground)/50%) 50%, hsl(var(--foreground)) 70%, hsl(var(--muted-foreground)/50%) 100%)",
17+
}}
18+
>
19+
{props.text}
20+
</p>
21+
</div>
22+
);
23+
}

apps/dashboard/src/app/nebula-app/(app)/components/ChatPageContent.tsx

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export function ChatPageContent(props: {
213213
// instant loading indicator feedback to user
214214
{
215215
type: "presence",
216-
text: "Thinking...",
216+
texts: [],
217217
},
218218
]);
219219

@@ -520,19 +520,8 @@ export async function handleNebulaPrompt(params: {
520520
hasReceivedResponse = true;
521521
setMessages((prev) => {
522522
const lastMessage = prev[prev.length - 1];
523-
// if last message is presence, overwrite it
524-
if (lastMessage?.type === "presence") {
525-
return [
526-
...prev.slice(0, -1),
527-
{
528-
text: res.data.v,
529-
type: "assistant",
530-
request_id: requestIdForMessage,
531-
},
532-
];
533-
}
534523

535-
// if last message is from chat, append to it
524+
// append to previous assistant message
536525
if (lastMessage?.type === "assistant") {
537526
return [
538527
...prev.slice(0, -1),
@@ -544,7 +533,7 @@ export async function handleNebulaPrompt(params: {
544533
];
545534
}
546535

547-
// otherwise, add a new message
536+
// start a new assistant message
548537
return [
549538
...prev,
550539
{
@@ -559,28 +548,27 @@ export async function handleNebulaPrompt(params: {
559548
if (res.event === "presence") {
560549
setMessages((prev) => {
561550
const lastMessage = prev[prev.length - 1];
562-
// if last message is presence, overwrite it
551+
552+
// append to previous presence message
563553
if (lastMessage?.type === "presence") {
564554
return [
565555
...prev.slice(0, -1),
566-
{ text: res.data.data, type: "presence" },
556+
{
557+
type: "presence",
558+
texts: [...lastMessage.texts, res.data.data],
559+
},
567560
];
568561
}
569-
// otherwise, add a new message
570-
return [...prev, { text: res.data.data, type: "presence" }];
562+
563+
// start a new presence message
564+
return [...prev, { texts: [res.data.data], type: "presence" }];
571565
});
572566
}
573567

574568
if (res.event === "action") {
575569
if (res.type === "sign_transaction") {
576570
hasReceivedResponse = true;
577-
setMessages((prev) => {
578-
let prevMessages = prev;
579-
// if last message is presence, remove it
580-
if (prevMessages[prevMessages.length - 1]?.type === "presence") {
581-
prevMessages = prevMessages.slice(0, -1);
582-
}
583-
571+
setMessages((prevMessages) => {
584572
return [
585573
...prevMessages,
586574
{
@@ -607,10 +595,7 @@ export async function handleNebulaPrompt(params: {
607595
// show an error message in that case
608596
if (!hasReceivedResponse) {
609597
setMessages((prev) => {
610-
const newMessages = prev.slice(
611-
0,
612-
prev[prev.length - 1]?.type === "presence" ? -1 : undefined,
613-
);
598+
const newMessages = [...prev];
614599

615600
newMessages.push({
616601
text: "No response received, please try again",
Lines changed: 111 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,105 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { randomLorem } from "stories/stubs";
3-
import { BadgeContainer, storybookThirdwebClient } from "stories/utils";
3+
import { storybookThirdwebClient } from "stories/utils";
44
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
55
import { type ChatMessage, Chats } from "./Chats";
66

77
const meta = {
88
title: "Nebula/Chats",
9-
component: Story,
9+
component: Variant,
1010
parameters: {
1111
nextjs: {
1212
appDirectory: true,
1313
},
1414
},
15-
} satisfies Meta<typeof Story>;
15+
decorators: [
16+
(Story) => (
17+
<ThirdwebProvider>
18+
<div className="container flex max-w-[800px] flex-col gap-14 py-10">
19+
<div>
20+
<ConnectButton client={storybookThirdwebClient} />
21+
</div>
22+
<Story />
23+
</div>
24+
</ThirdwebProvider>
25+
),
26+
],
27+
} satisfies Meta<typeof Variant>;
1628

1729
export default meta;
1830
type Story = StoryObj<typeof meta>;
1931

20-
export const Variants: Story = {
21-
args: {},
32+
export const UserPresenceError: Story = {
33+
args: {
34+
messages: [
35+
{
36+
text: randomLorem(10),
37+
type: "user",
38+
},
39+
{
40+
texts: [randomLorem(20)],
41+
type: "presence",
42+
},
43+
{
44+
text: randomLorem(20),
45+
type: "error",
46+
},
47+
],
48+
},
49+
};
50+
51+
export const SendTransaction: Story = {
52+
args: {
53+
messages: [
54+
{
55+
text: randomLorem(40),
56+
type: "assistant",
57+
request_id: undefined,
58+
},
59+
{
60+
type: "send_transaction",
61+
data: {
62+
chainId: 1,
63+
to: "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
64+
data: "0x",
65+
value: "0x16345785d8a0000",
66+
},
67+
},
68+
],
69+
},
70+
};
71+
72+
export const InvalidTxData: Story = {
73+
args: {
74+
messages: [
75+
{
76+
text: randomLorem(40),
77+
type: "assistant",
78+
request_id: undefined,
79+
},
80+
{
81+
type: "send_transaction",
82+
data: null,
83+
},
84+
],
85+
},
86+
};
87+
88+
export const WithAndWithoutRequestId: Story = {
89+
args: {
90+
messages: [
91+
{
92+
text: randomLorem(40),
93+
type: "assistant",
94+
request_id: "xxxxx",
95+
},
96+
{
97+
text: randomLorem(50),
98+
type: "assistant",
99+
request_id: undefined,
100+
},
101+
],
102+
},
22103
};
23104

24105
const markdownExample = `\
@@ -129,134 +210,35 @@ ${randomLorem(20)}
129210
${markdownExample}
130211
`;
131212

132-
function Story() {
133-
return (
134-
<ThirdwebProvider>
135-
<div className="container flex max-w-[800px] flex-col gap-14 py-10">
136-
<div>
137-
<ConnectButton client={storybookThirdwebClient} />
138-
</div>
139-
140-
<Variant
141-
label="user + presence + error"
142-
messages={[
143-
{
144-
text: randomLorem(10),
145-
type: "user",
146-
},
147-
{
148-
text: randomLorem(20),
149-
type: "presence",
150-
},
151-
{
152-
text: randomLorem(20),
153-
type: "error",
154-
},
155-
]}
156-
/>
157-
158-
<Variant
159-
label="send-transaction"
160-
messages={[
161-
{
162-
text: randomLorem(40),
163-
type: "assistant",
164-
request_id: undefined,
165-
},
166-
{
167-
type: "send_transaction",
168-
data: {
169-
chainId: 1,
170-
to: "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
171-
data: "0x",
172-
value: "0x16345785d8a0000",
173-
},
174-
},
175-
]}
176-
/>
177-
178-
<Variant
179-
label="invalid send-transaction"
180-
messages={[
181-
{
182-
text: randomLorem(40),
183-
type: "assistant",
184-
request_id: undefined,
185-
},
186-
{
187-
type: "send_transaction",
188-
data: null,
189-
},
190-
]}
191-
/>
192-
193-
<BadgeContainer label="Assistant response With request_id, Without request_id">
194-
<Chats
195-
sendMessage={() => {}}
196-
enableAutoScroll={false}
197-
setEnableAutoScroll={() => {}}
198-
client={storybookThirdwebClient}
199-
authToken="xxxxx"
200-
isChatStreaming={false}
201-
sessionId="xxxxx"
202-
messages={[
203-
{
204-
text: randomLorem(40),
205-
type: "assistant",
206-
request_id: "xxxxx",
207-
},
208-
{
209-
text: randomLorem(50),
210-
type: "assistant",
211-
request_id: undefined,
212-
},
213-
]}
214-
/>
215-
</BadgeContainer>
216-
217-
<BadgeContainer label="Assistant markdown">
218-
<Chats
219-
sendMessage={() => {}}
220-
enableAutoScroll={false}
221-
setEnableAutoScroll={() => {}}
222-
client={storybookThirdwebClient}
223-
authToken="xxxxx"
224-
isChatStreaming={false}
225-
sessionId="xxxxx"
226-
messages={[
227-
{
228-
text: responseWithCodeMarkdown,
229-
type: "assistant",
230-
request_id: undefined,
231-
},
232-
{
233-
text: responseWithCodeMarkdown,
234-
type: "user",
235-
},
236-
]}
237-
/>
238-
</BadgeContainer>
239-
</div>
240-
</ThirdwebProvider>
241-
);
242-
}
213+
export const Markdown: Story = {
214+
args: {
215+
messages: [
216+
{
217+
text: responseWithCodeMarkdown,
218+
type: "assistant",
219+
request_id: undefined,
220+
},
221+
{
222+
text: responseWithCodeMarkdown,
223+
type: "user",
224+
},
225+
],
226+
},
227+
};
243228

244229
function Variant(props: {
245-
label: string;
246230
messages: ChatMessage[];
247231
}) {
248232
return (
249-
<BadgeContainer label={props.label}>
250-
<Chats
251-
enableAutoScroll={false}
252-
setEnableAutoScroll={() => {}}
253-
client={storybookThirdwebClient}
254-
sendMessage={() => {}}
255-
authToken="xxxxx"
256-
isChatStreaming={false}
257-
sessionId="xxxxx"
258-
messages={props.messages}
259-
/>
260-
</BadgeContainer>
233+
<Chats
234+
enableAutoScroll={false}
235+
setEnableAutoScroll={() => {}}
236+
client={storybookThirdwebClient}
237+
sendMessage={() => {}}
238+
authToken="xxxxx"
239+
isChatStreaming={false}
240+
sessionId="xxxxx"
241+
messages={props.messages}
242+
/>
261243
);
262244
}

0 commit comments

Comments
 (0)