Skip to content

[NEB-241] Nebula: /chat endpoint schema update related changes #6982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 9, 2025
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
5 changes: 3 additions & 2 deletions apps/dashboard/src/app/nebula-app/(app)/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NEXT_PUBLIC_NEBULA_URL } from "@/constants/env";
// TODO - copy the source of this library to dashboard
import { stream } from "fetch-event-stream";
import type { NebulaTxData } from "../components/Chats";
import type { NebulaUserMessage } from "./types";

export type NebulaContext = {
chainIds: string[] | null;
Expand Down Expand Up @@ -42,15 +43,15 @@ export type NebulaSwapData = {
};

export async function promptNebula(params: {
message: string;
message: NebulaUserMessage;
sessionId: string;
authToken: string;
handleStream: (res: ChatStreamedResponse) => void;
abortController: AbortController;
context: undefined | NebulaContext;
}) {
const body: Record<string, string | boolean | object> = {
message: params.message,
messages: [params.message],
stream: true,
session_id: params.sessionId,
};
Expand Down
35 changes: 31 additions & 4 deletions apps/dashboard/src/app/nebula-app/(app)/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,39 @@ type SessionContextFilter = {
wallet_address: string | null;
};

export type NebulaSessionHistoryMessage = {
role: "user" | "assistant" | "action" | "image";
content: string;
timestamp: number;
type NebulaUserMessageContentItem =
| {
type: "image";
image_url: string;
}
| {
type: "text";
text: string;
}
| {
type: "transaction";
transaction_hash: string;
chain_id: number;
};

export type NebulaUserMessageContent = NebulaUserMessageContentItem[];

export type NebulaUserMessage = {
role: "user";
content: NebulaUserMessageContent;
};

export type NebulaSessionHistoryMessage =
| {
role: "assistant" | "action" | "image";
content: string;
timestamp: number;
}
| {
role: "user";
content: NebulaUserMessageContent | string;
};

export type SessionInfo = {
id: string;
account_id: string;
Expand Down
20 changes: 15 additions & 5 deletions apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ import {
import { shortenAddress } from "thirdweb/utils";
import type { Wallet } from "thirdweb/wallets";
import type { NebulaContext } from "../api/chat";
import type { NebulaUserMessage } from "../api/types";

export type WalletMeta = {
walletId: Wallet["id"];
address: string;
};

export function ChatBar(props: {
sendMessage: (message: string) => void;
sendMessage: (message: NebulaUserMessage) => void;
isChatStreaming: boolean;
abortChatStream: () => void;
prefillMessage: string | undefined;
Expand All @@ -53,11 +54,22 @@ export function ChatBar(props: {
connectedWallets: WalletMeta[];
setActiveWallet: (wallet: WalletMeta) => void;
isConnectingWallet: boolean;
// TODO - add this option later
// showImageUploader: boolean;
}) {
const [message, setMessage] = useState(props.prefillMessage || "");
const selectedChainIds = props.context?.chainIds?.map((x) => Number(x)) || [];
const firstChainId = selectedChainIds[0];

function handleSubmit(message: string) {
setMessage("");
props.sendMessage({
role: "user",
// TODO - add image here later
content: [{ type: "text", text: message }],
});
}

return (
<div
className={cn(
Expand All @@ -77,8 +89,7 @@ export function ChatBar(props: {
}
if (e.key === "Enter" && !props.isChatStreaming) {
e.preventDefault();
setMessage("");
props.sendMessage(message);
handleSubmit(message);
}
}}
className="min-h-[60px] resize-none border-none bg-transparent pt-2 leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0"
Expand Down Expand Up @@ -194,8 +205,7 @@ export function ChatBar(props: {
variant="pink"
onClick={() => {
if (message.trim() === "") return;
setMessage("");
props.sendMessage(message);
handleSubmit(message);
}}
>
<ArrowUpIcon className="size-4" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
} from "thirdweb/react";
import { type NebulaContext, promptNebula } from "../api/chat";
import { createSession, updateSession } from "../api/session";
import type { NebulaSessionHistoryMessage, SessionInfo } from "../api/types";
import type {
NebulaSessionHistoryMessage,
NebulaUserMessage,
SessionInfo,
} from "../api/types";
import { examplePrompts } from "../data/examplePrompts";
import { newSessionsStore } from "../stores";
import { ChatBar, type WalletMeta } from "./ChatBar";
Expand Down Expand Up @@ -135,20 +139,25 @@ export function ChatPageContent(props: {
}, [contextFilters, props.authToken]);

const handleSendMessage = useCallback(
async (message: string) => {
async (message: NebulaUserMessage) => {
setUserHasSubmittedMessage(true);
setMessages((prev) => [
...prev,
{ text: message, type: "user" },
// instant loading indicator feedback to user
{
type: "user",
content: message.content,
},
{
type: "presence",
texts: [],
},
]);

// handle hardcoded replies first
const lowerCaseMessage = message.toLowerCase();
const lowerCaseMessage = message.content
.find((x) => x.type === "text")
?.text.toLowerCase();

const interceptedReply = examplePrompts.find(
(prompt) => prompt.message.toLowerCase() === lowerCaseMessage,
)?.interceptedReply;
Expand All @@ -175,13 +184,18 @@ export function ChatPageContent(props: {
currentSessionId = session.id;
}

const firstTextMessage =
message.role === "user"
? message.content.find((x) => x.type === "text")?.text || ""
: "";

// add this session on sidebar
if (messages.length === 0) {
if (messages.length === 0 && firstTextMessage) {
const prevValue = newSessionsStore.getValue();
newSessionsStore.setValue([
{
id: currentSessionId,
title: message,
title: firstTextMessage,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
Expand All @@ -193,7 +207,7 @@ export function ChatPageContent(props: {

await handleNebulaPrompt({
abortController,
message,
message: message,
sessionId: currentSessionId,
authToken: props.authToken,
setMessages,
Expand Down Expand Up @@ -234,7 +248,15 @@ export function ChatPageContent(props: {
!hasDoneAutoPrompt.current
) {
hasDoneAutoPrompt.current = true;
handleSendMessage(props.initialParams.q);
handleSendMessage({
role: "user",
content: [
{
type: "text",
text: props.initialParams.q,
},
],
});
}
}, [props.initialParams?.q, messages.length, handleSendMessage]);

Expand Down Expand Up @@ -402,7 +424,7 @@ function getLastUsedChainIds(): string[] | null {

export async function handleNebulaPrompt(params: {
abortController: AbortController;
message: string;
message: NebulaUserMessage;
sessionId: string;
authToken: string;
setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
Expand Down Expand Up @@ -654,7 +676,15 @@ function parseHistoryToMessages(history: NebulaSessionHistoryMessage[]) {

case "user": {
messages.push({
text: message.content,
content:
typeof message.content === "string"
? [
{
type: "text",
text: message.content,
},
]
: message.content,
type: message.role,
});
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export const UserPresenceError: Story = {
args: {
messages: [
{
text: randomLorem(10),
content: [
{
type: "text",
text: randomLorem(10),
},
],
type: "user",
},
{
Expand Down Expand Up @@ -205,8 +210,13 @@ export const Markdown: Story = {
request_id: undefined,
},
{
text: responseWithCodeMarkdown,
type: "user",
content: [
{
type: "text",
text: responseWithCodeMarkdown,
},
],
},
],
},
Expand Down
Loading
Loading