Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions apps/dashboard/src/@/components/ui/text-shimmer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { cn } from "../../lib/utils";

export function TextShimmer(props: {
text: string;
className?: string;
}) {
return (
<div className="flex">
<p
className={cn(
"animate-text-shimmer bg-[length:200%_50%] bg-clip-text text-transparent will-change-auto",
props.className,
)}
style={{
backgroundImage:
"linear-gradient(70deg, hsl(var(--muted-foreground)/50%) 50%, hsl(var(--foreground)) 70%, hsl(var(--muted-foreground)/50%) 100%)",
}}
>
{props.text}
</p>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function ChatPageContent(props: {
// instant loading indicator feedback to user
{
type: "presence",
text: "Thinking...",
texts: [],
},
]);

Expand Down Expand Up @@ -521,19 +521,8 @@ export async function handleNebulaPrompt(params: {
hasReceivedResponse = true;
setMessages((prev) => {
const lastMessage = prev[prev.length - 1];
// if last message is presence, overwrite it
if (lastMessage?.type === "presence") {
return [
...prev.slice(0, -1),
{
text: res.data.v,
type: "assistant",
request_id: requestIdForMessage,
},
];
}

// if last message is from chat, append to it
// append to previous assistant message
if (lastMessage?.type === "assistant") {
return [
...prev.slice(0, -1),
Expand All @@ -545,7 +534,7 @@ export async function handleNebulaPrompt(params: {
];
}

// otherwise, add a new message
// start a new assistant message
return [
...prev,
{
Expand All @@ -560,28 +549,27 @@ export async function handleNebulaPrompt(params: {
if (res.event === "presence") {
setMessages((prev) => {
const lastMessage = prev[prev.length - 1];
// if last message is presence, overwrite it

// append to previous presence message
if (lastMessage?.type === "presence") {
return [
...prev.slice(0, -1),
{ text: res.data.data, type: "presence" },
{
type: "presence",
texts: [...lastMessage.texts, res.data.data],
},
];
}
// otherwise, add a new message
return [...prev, { text: res.data.data, type: "presence" }];

// start a new presence message
return [...prev, { texts: [res.data.data], type: "presence" }];
});
}

if (res.event === "action") {
if (res.type === "sign_transaction") {
hasReceivedResponse = true;
setMessages((prev) => {
let prevMessages = prev;
// if last message is presence, remove it
if (prevMessages[prevMessages.length - 1]?.type === "presence") {
prevMessages = prevMessages.slice(0, -1);
}

setMessages((prevMessages) => {
return [
...prevMessages,
{
Expand All @@ -608,10 +596,7 @@ export async function handleNebulaPrompt(params: {
// show an error message in that case
if (!hasReceivedResponse) {
setMessages((prev) => {
const newMessages = prev.slice(
0,
prev[prev.length - 1]?.type === "presence" ? -1 : undefined,
);
const newMessages = [...prev];

newMessages.push({
text: "No response received, please try again",
Expand Down
240 changes: 111 additions & 129 deletions apps/dashboard/src/app/nebula-app/(app)/components/Chats.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,105 @@
import type { Meta, StoryObj } from "@storybook/react";
import { randomLorem } from "stories/stubs";
import { BadgeContainer, storybookThirdwebClient } from "stories/utils";
import { storybookThirdwebClient } from "stories/utils";
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
import { type ChatMessage, Chats } from "./Chats";

const meta = {
title: "Nebula/Chats",
component: Story,
component: Variant,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof Story>;
decorators: [
(Story) => (
<ThirdwebProvider>
<div className="container flex max-w-[800px] flex-col gap-14 py-10">
<div>
<ConnectButton client={storybookThirdwebClient} />
</div>
<Story />
</div>
</ThirdwebProvider>
),
],
} satisfies Meta<typeof Variant>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Variants: Story = {
args: {},
export const UserPresenceError: Story = {
args: {
messages: [
{
text: randomLorem(10),
type: "user",
},
{
texts: [randomLorem(20)],
type: "presence",
},
{
text: randomLorem(20),
type: "error",
},
],
},
};

export const SendTransaction: Story = {
args: {
messages: [
{
text: randomLorem(40),
type: "assistant",
request_id: undefined,
},
{
type: "send_transaction",
data: {
chainId: 1,
to: "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
data: "0x",
value: "0x16345785d8a0000",
},
},
],
},
};

export const InvalidTxData: Story = {
args: {
messages: [
{
text: randomLorem(40),
type: "assistant",
request_id: undefined,
},
{
type: "send_transaction",
data: null,
},
],
},
};

export const WithAndWithoutRequestId: Story = {
args: {
messages: [
{
text: randomLorem(40),
type: "assistant",
request_id: "xxxxx",
},
{
text: randomLorem(50),
type: "assistant",
request_id: undefined,
},
],
},
};

const markdownExample = `\
Expand Down Expand Up @@ -129,134 +210,35 @@ ${randomLorem(20)}
${markdownExample}
`;

function Story() {
return (
<ThirdwebProvider>
<div className="container flex max-w-[800px] flex-col gap-14 py-10">
<div>
<ConnectButton client={storybookThirdwebClient} />
</div>

<Variant
label="user + presence + error"
messages={[
{
text: randomLorem(10),
type: "user",
},
{
text: randomLorem(20),
type: "presence",
},
{
text: randomLorem(20),
type: "error",
},
]}
/>

<Variant
label="send-transaction"
messages={[
{
text: randomLorem(40),
type: "assistant",
request_id: undefined,
},
{
type: "send_transaction",
data: {
chainId: 1,
to: "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37",
data: "0x",
value: "0x16345785d8a0000",
},
},
]}
/>

<Variant
label="invalid send-transaction"
messages={[
{
text: randomLorem(40),
type: "assistant",
request_id: undefined,
},
{
type: "send_transaction",
data: null,
},
]}
/>

<BadgeContainer label="Assistant response With request_id, Without request_id">
<Chats
sendMessage={() => {}}
enableAutoScroll={false}
setEnableAutoScroll={() => {}}
client={storybookThirdwebClient}
authToken="xxxxx"
isChatStreaming={false}
sessionId="xxxxx"
messages={[
{
text: randomLorem(40),
type: "assistant",
request_id: "xxxxx",
},
{
text: randomLorem(50),
type: "assistant",
request_id: undefined,
},
]}
/>
</BadgeContainer>

<BadgeContainer label="Assistant markdown">
<Chats
sendMessage={() => {}}
enableAutoScroll={false}
setEnableAutoScroll={() => {}}
client={storybookThirdwebClient}
authToken="xxxxx"
isChatStreaming={false}
sessionId="xxxxx"
messages={[
{
text: responseWithCodeMarkdown,
type: "assistant",
request_id: undefined,
},
{
text: responseWithCodeMarkdown,
type: "user",
},
]}
/>
</BadgeContainer>
</div>
</ThirdwebProvider>
);
}
export const Markdown: Story = {
args: {
messages: [
{
text: responseWithCodeMarkdown,
type: "assistant",
request_id: undefined,
},
{
text: responseWithCodeMarkdown,
type: "user",
},
],
},
};

function Variant(props: {
label: string;
messages: ChatMessage[];
}) {
return (
<BadgeContainer label={props.label}>
<Chats
enableAutoScroll={false}
setEnableAutoScroll={() => {}}
client={storybookThirdwebClient}
sendMessage={() => {}}
authToken="xxxxx"
isChatStreaming={false}
sessionId="xxxxx"
messages={props.messages}
/>
</BadgeContainer>
<Chats
enableAutoScroll={false}
setEnableAutoScroll={() => {}}
client={storybookThirdwebClient}
sendMessage={() => {}}
authToken="xxxxx"
isChatStreaming={false}
sessionId="xxxxx"
messages={props.messages}
/>
);
}
Loading
Loading