1+ import { stream } from "fetch-event-stream" ;
2+ import type { NebulaTxData , NebulaUserMessage } from "./types" ;
3+
4+ // Mock URL for playground - you'll need to configure this
5+ const NEBULA_URL = process . env . NEXT_PUBLIC_NEBULA_URL || "https://nebula-api.thirdweb-dev.com" ;
6+
7+ export type NebulaContext = {
8+ chainIds : string [ ] | null ;
9+ walletAddress : string | null ;
10+ networks : "mainnet" | "testnet" | "all" | null ;
11+ } ;
12+
13+ export type NebulaSwapData = {
14+ action : string ;
15+ transaction : {
16+ chainId : number ;
17+ to : `0x${string } `;
18+ data : `0x${string } `;
19+ } ;
20+ to : {
21+ address : `0x${string } `;
22+ amount : string ;
23+ chain_id : number ;
24+ decimals : number ;
25+ symbol : string ;
26+ } ;
27+ from : {
28+ address : `0x${string } `;
29+ amount : string ;
30+ chain_id : number ;
31+ decimals : number ;
32+ symbol : string ;
33+ } ;
34+ intent : {
35+ amount : string ;
36+ destinationChainId : number ;
37+ destinationTokenAddress : `0x${string } `;
38+ originChainId : number ;
39+ originTokenAddress : `0x${string } `;
40+ receiver : `0x${string } `;
41+ sender : `0x${string } `;
42+ } ;
43+ } ;
44+
45+ export async function promptNebula ( params : {
46+ message : NebulaUserMessage ;
47+ sessionId : string ;
48+ authToken : string ;
49+ handleStream : ( res : ChatStreamedResponse ) => void ;
50+ abortController : AbortController ;
51+ context : undefined | NebulaContext ;
52+ } ) {
53+ const body : Record < string , string | boolean | object > = {
54+ messages : [ params . message ] ,
55+ session_id : params . sessionId ,
56+ stream : true ,
57+ } ;
58+
59+ if ( params . context ) {
60+ body . context = {
61+ chain_ids : params . context . chainIds || [ ] ,
62+ networks : params . context . networks ,
63+ wallet_address : params . context . walletAddress ,
64+ } ;
65+ }
66+
67+ const events = await stream ( `${ NEBULA_URL } /chat` , {
68+ body : JSON . stringify ( body ) ,
69+ headers : {
70+ Authorization : `Bearer ${ params . authToken } ` ,
71+ "Content-Type" : "application/json" ,
72+ } ,
73+ method : "POST" ,
74+ signal : params . abortController . signal ,
75+ } ) ;
76+
77+ for await ( const _event of events ) {
78+ if ( ! _event . data ) {
79+ continue ;
80+ }
81+
82+ const event = _event as ChatStreamedEvent ;
83+
84+ switch ( event . event ) {
85+ case "delta" : {
86+ params . handleStream ( {
87+ data : {
88+ v : JSON . parse ( event . data ) . v ,
89+ } ,
90+ event : "delta" ,
91+ } ) ;
92+ break ;
93+ }
94+
95+ case "presence" : {
96+ params . handleStream ( {
97+ data : JSON . parse ( event . data ) ,
98+ event : "presence" ,
99+ } ) ;
100+ break ;
101+ }
102+
103+ case "image" : {
104+ const data = JSON . parse ( event . data ) as {
105+ data : {
106+ width : number ;
107+ height : number ;
108+ url : string ;
109+ } ;
110+ request_id : string ;
111+ } ;
112+
113+ params . handleStream ( {
114+ data : data . data ,
115+ event : "image" ,
116+ request_id : data . request_id ,
117+ } ) ;
118+ break ;
119+ }
120+
121+ case "action" : {
122+ const data = JSON . parse ( event . data ) ;
123+
124+ if ( data . type === "sign_transaction" ) {
125+ try {
126+ const parsedTxData = JSON . parse ( data . data ) as NebulaTxData ;
127+ params . handleStream ( {
128+ data : parsedTxData ,
129+ event : "action" ,
130+ request_id : data . request_id ,
131+ type : "sign_transaction" ,
132+ } ) ;
133+ } catch ( e ) {
134+ console . error ( "failed to parse action data" , e , { event } ) ;
135+ }
136+ }
137+
138+ if ( data . type === "sign_swap" ) {
139+ try {
140+ const swapData = JSON . parse ( data . data ) as NebulaSwapData ;
141+ params . handleStream ( {
142+ data : swapData ,
143+ event : "action" ,
144+ request_id : data . request_id ,
145+ type : "sign_swap" ,
146+ } ) ;
147+ } catch ( e ) {
148+ console . error ( "failed to parse action data" , e , { event } ) ;
149+ }
150+ }
151+
152+ break ;
153+ }
154+
155+ case "error" : {
156+ const data = JSON . parse ( event . data ) as {
157+ code : number ;
158+ error : {
159+ message : string ;
160+ } ;
161+ } ;
162+
163+ params . handleStream ( {
164+ data : {
165+ code : data . code ,
166+ errorMessage : data . error . message ,
167+ } ,
168+ event : "error" ,
169+ } ) ;
170+ break ;
171+ }
172+
173+ case "init" : {
174+ const data = JSON . parse ( event . data ) ;
175+ params . handleStream ( {
176+ data : {
177+ request_id : data . request_id ,
178+ session_id : data . session_id ,
179+ } ,
180+ event : "init" ,
181+ } ) ;
182+ break ;
183+ }
184+
185+ case "context" : {
186+ const data = JSON . parse ( event . data ) as {
187+ data : string ;
188+ request_id : string ;
189+ session_id : string ;
190+ } ;
191+
192+ const contextData = JSON . parse ( data . data ) as {
193+ wallet_address : string ;
194+ chain_ids : number [ ] ;
195+ networks : NebulaContext [ "networks" ] ;
196+ } ;
197+
198+ params . handleStream ( {
199+ data : contextData ,
200+ event : "context" ,
201+ } ) ;
202+ break ;
203+ }
204+
205+ case "ping" : {
206+ break ;
207+ }
208+
209+ default : {
210+ console . warn ( "unhandled event" , event ) ;
211+ }
212+ }
213+ }
214+ }
215+
216+ type ChatStreamedResponse =
217+ | {
218+ event : "init" ;
219+ data : {
220+ session_id : string ;
221+ request_id : string ;
222+ } ;
223+ }
224+ | {
225+ event : "presence" ;
226+ data : {
227+ session_id : string ;
228+ request_id : string ;
229+ source : "user" | "reviewer" | ( string & { } ) ;
230+ data : string ;
231+ } ;
232+ }
233+ | {
234+ event : "delta" ;
235+ data : {
236+ v : string ;
237+ } ;
238+ }
239+ | {
240+ event : "action" ;
241+ type : "sign_transaction" ;
242+ data : NebulaTxData ;
243+ request_id : string ;
244+ }
245+ | {
246+ event : "action" ;
247+ type : "sign_swap" ;
248+ data : NebulaSwapData ;
249+ request_id : string ;
250+ }
251+ | {
252+ event : "image" ;
253+ data : {
254+ width : number ;
255+ height : number ;
256+ url : string ;
257+ } ;
258+ request_id : string ;
259+ }
260+ | {
261+ event : "context" ;
262+ data : {
263+ wallet_address : string ;
264+ chain_ids : number [ ] ;
265+ networks : NebulaContext [ "networks" ] ;
266+ } ;
267+ }
268+ | {
269+ event : "error" ;
270+ data : {
271+ code : number ;
272+ errorMessage : string ;
273+ } ;
274+ } ;
275+
276+ type ChatStreamedEvent =
277+ | {
278+ event : "init" ;
279+ data : string ;
280+ }
281+ | {
282+ event : "presence" ;
283+ data : string ;
284+ }
285+ | {
286+ event : "delta" ;
287+ data : string ;
288+ }
289+ | {
290+ event : "image" ;
291+ data : string ;
292+ }
293+ | {
294+ event : "action" ;
295+ type : "sign_transaction" | "sign_swap" ;
296+ data : string ;
297+ }
298+ | {
299+ event : "context" ;
300+ data : string ;
301+ }
302+ | {
303+ event : "error" ;
304+ data : string ;
305+ }
306+ | {
307+ event : "ping" ;
308+ data : string ;
309+ } ;
0 commit comments