6
6
Page ,
7
7
PencilSquareIcon ,
8
8
SparklesIcon ,
9
+ Spinner2 ,
9
10
} from "@dust-tt/sparkle" ;
10
11
import type {
11
12
APIError ,
@@ -14,14 +15,21 @@ import type {
14
15
WorkspaceType ,
15
16
} from "@dust-tt/types" ;
16
17
import { Err , Ok } from "@dust-tt/types" ;
17
- import React , { useCallback , useEffect , useRef , useState } from "react" ;
18
+ import React , {
19
+ useCallback ,
20
+ useContext ,
21
+ useEffect ,
22
+ useRef ,
23
+ useState ,
24
+ } from "react" ;
18
25
19
26
import { AvatarPicker } from "@app/components/assistant_builder/AssistantBuilderAvatarPicker" ;
20
27
import {
21
28
DROID_AVATAR_URLS ,
22
29
SPIRIT_AVATAR_URLS ,
23
30
} from "@app/components/assistant_builder/shared" ;
24
31
import type { AssistantBuilderState } from "@app/components/assistant_builder/types" ;
32
+ import { SendNotificationsContext } from "@app/components/sparkle/Notification" ;
25
33
import { isDevelopmentOrDustWorkspace } from "@app/lib/development" ;
26
34
import { debounce } from "@app/lib/utils/debounce" ;
27
35
@@ -134,6 +142,7 @@ export default function NamingScreen({
134
142
setEdited : ( edited : boolean ) => void ;
135
143
assistantHandleError : string | null ;
136
144
} ) {
145
+ const sendNotification = useContext ( SendNotificationsContext ) ;
137
146
const [ isAvatarModalOpen , setIsAvatarModalOpen ] = useState ( false ) ;
138
147
139
148
// Name suggestions handling
@@ -168,35 +177,74 @@ export default function NamingScreen({
168
177
] ) ;
169
178
170
179
// Description suggestions handling
171
- const [ descriptionSuggestions , setDescriptionSuggestions ] =
172
- useState < BuilderSuggestionsType > ( {
173
- status : "unavailable" ,
174
- reason : "irrelevant" ,
175
- } ) ;
176
180
177
- const [ descriptionSuggestionsIndex , setDescriptionSuggestionIndex ] =
178
- useState ( 0 ) ;
181
+ const [ generatingDescription , setGeneratingDescription ] = useState ( false ) ;
182
+ const [ descriptionIsGenerated , setDescriptionIsGenerated ] = useState ( false ) ;
179
183
180
- const updateDescriptionSuggestions = useCallback ( async ( ) => {
181
- const descriptionSuggestions = await getDescriptionSuggestions ( {
184
+ const suggestDescription = useCallback (
185
+ async ( fromUserClick ?: boolean ) => {
186
+ setGeneratingDescription ( true ) ;
187
+ const notifyError = fromUserClick ? sendNotification : console . log ;
188
+ const descriptionSuggestions = await getDescriptionSuggestions ( {
189
+ owner,
190
+ instructions : builderState . instructions || "" ,
191
+ name : builderState . handle || "" ,
192
+ } ) ;
193
+ if ( descriptionSuggestions . isOk ( ) ) {
194
+ const suggestion =
195
+ descriptionSuggestions . value . status === "ok" &&
196
+ descriptionSuggestions . value . suggestions . length > 0
197
+ ? descriptionSuggestions . value . suggestions [ 0 ]
198
+ : null ;
199
+ if ( suggestion ) {
200
+ setBuilderState ( ( state ) => ( {
201
+ ...state ,
202
+ description : suggestion ,
203
+ } ) ) ;
204
+ setDescriptionIsGenerated ( true ) ;
205
+ } else {
206
+ const errorMessage =
207
+ descriptionSuggestions . value . status === "unavailable"
208
+ ? descriptionSuggestions . value . reason ||
209
+ "No suggestions available"
210
+ : "No suggestions available" ;
211
+ notifyError ( {
212
+ type : "error" ,
213
+ title : "Error generating description suggestion." ,
214
+ description : errorMessage ,
215
+ } ) ;
216
+ }
217
+ } else {
218
+ notifyError ( {
219
+ type : "error" ,
220
+ title : "Error generating description suggestion." ,
221
+ description : descriptionSuggestions . error . message ,
222
+ } ) ;
223
+ }
224
+ setGeneratingDescription ( false ) ;
225
+ } ,
226
+ [
182
227
owner ,
183
- instructions : builderState . instructions || "" ,
184
- name : builderState . handle || "" ,
185
- } ) ;
186
- if ( descriptionSuggestions . isOk ( ) ) {
187
- setDescriptionSuggestions ( descriptionSuggestions . value ) ;
188
- }
189
- } , [ owner , builderState . instructions , builderState . handle ] ) ;
228
+ builderState . instructions ,
229
+ builderState . handle ,
230
+ setBuilderState ,
231
+ sendNotification ,
232
+ ]
233
+ ) ;
190
234
191
235
useEffect ( ( ) => {
192
236
if ( isDevelopmentOrDustWorkspace ( owner ) ) {
193
- void updateDescriptionSuggestions ( ) ;
237
+ if (
238
+ ! builderState . description ?. trim ( ) &&
239
+ builderState . instructions ?. trim ( ) &&
240
+ ! generatingDescription
241
+ ) {
242
+ void suggestDescription ( ) ;
243
+ }
194
244
}
195
- } , [ owner , updateDescriptionSuggestions ] ) ;
196
-
197
- const suggestionsAvailable =
198
- descriptionSuggestions . status === "ok" &&
199
- descriptionSuggestions . suggestions . length > 0 ;
245
+ // Here we only want to run this effect once
246
+ // eslint-disable-next-line react-hooks/exhaustive-deps
247
+ } , [ ] ) ;
200
248
201
249
return (
202
250
< >
@@ -299,62 +347,54 @@ export default function NamingScreen({
299
347
</ div >
300
348
< div className = "flex flex-col gap-4" >
301
349
< div >
302
- < Page . SectionHeader title = "Description" />
350
+ < div className = "flex gap-1" >
351
+ < Page . SectionHeader title = "Description" />
352
+ { generatingDescription && < Spinner2 size = "sm" /> }
353
+ </ div >
303
354
< div className = "text-sm font-normal text-element-700" >
304
- Describe for others the assistant’s purpose.
355
+ Describe for others the assistant’s purpose.{ " " }
305
356
</ div >
306
357
</ div >
307
358
308
359
< div className = "flex items-center gap-2" >
309
360
< div className = "flex-grow" >
310
361
< Input
311
362
placeholder = {
312
- suggestionsAvailable
313
- ? "Click on sparkles to generate a description"
314
- : "Answer questions about sales, translate from English to French… "
363
+ generatingDescription
364
+ ? "Generating description... "
365
+ : "Click on sparkles to generate a description "
315
366
}
316
- value = { builderState . description }
367
+ value = { generatingDescription ? "" : builderState . description }
317
368
onChange = { ( value ) => {
318
369
setEdited ( true ) ;
370
+ setDescriptionIsGenerated ( false ) ;
319
371
setBuilderState ( ( state ) => ( {
320
372
...state ,
321
373
description : value ,
322
374
} ) ) ;
323
375
} }
324
- error = { null } // TODO ?
325
376
name = "assistantDescription"
326
377
className = "text-sm"
378
+ disabled = { generatingDescription }
327
379
/>
328
380
</ div >
329
381
{ isDevelopmentOrDustWorkspace ( owner ) && (
330
382
< IconButton
331
383
icon = { SparklesIcon }
332
384
size = "md"
385
+ disabled = { generatingDescription }
333
386
onClick = { async ( ) => {
334
- if ( ! suggestionsAvailable ) return ;
335
- setEdited ( true ) ;
336
- setBuilderState ( ( state ) => ( {
337
- ...state ,
338
- description :
339
- descriptionSuggestions . suggestions [
340
- descriptionSuggestionsIndex
341
- ] ,
342
- } ) ) ;
343
- if ( descriptionSuggestionsIndex === 1 ) {
344
- await updateDescriptionSuggestions ( ) ;
345
- setDescriptionSuggestionIndex ( 0 ) ;
346
- } else {
347
- setDescriptionSuggestionIndex (
348
- descriptionSuggestionsIndex + 1
349
- ) ;
387
+ if (
388
+ ! builderState . description ?. trim ( ) ||
389
+ descriptionIsGenerated ||
390
+ confirm (
391
+ "Heads up! This will overwrite your current description. Are you sure you want to proceed?"
392
+ )
393
+ ) {
394
+ await suggestDescription ( ) ;
350
395
}
351
396
} }
352
- disabled = { ! suggestionsAvailable }
353
- tooltip = {
354
- suggestionsAvailable
355
- ? "Click to generate a description"
356
- : "Description generation not yet available"
357
- }
397
+ tooltip = "Click to generate a description"
358
398
/>
359
399
) }
360
400
</ div >
@@ -373,7 +413,7 @@ async function getNamingSuggestions({
373
413
instructions : string ;
374
414
description : string ;
375
415
} ) : Promise < Result < BuilderSuggestionsType , APIError > > {
376
- const res = await fetch ( `/api/w/${ owner . sId } /assistant/builder/suggestions` , {
416
+ return fetchWithErr ( `/api/w/${ owner . sId } /assistant/builder/suggestions` , {
377
417
method : "POST" ,
378
418
headers : {
379
419
"Content-Type" : "application/json" ,
@@ -383,13 +423,6 @@ async function getNamingSuggestions({
383
423
inputs : { instructions, description } ,
384
424
} ) ,
385
425
} ) ;
386
- if ( ! res . ok ) {
387
- return new Err ( {
388
- type : "internal_server_error" ,
389
- message : "Failed to get suggestions" ,
390
- } ) ;
391
- }
392
- return new Ok ( await res . json ( ) ) ;
393
426
}
394
427
395
428
async function getDescriptionSuggestions ( {
@@ -401,7 +434,7 @@ async function getDescriptionSuggestions({
401
434
instructions : string ;
402
435
name : string ;
403
436
} ) : Promise < Result < BuilderSuggestionsType , APIError > > {
404
- const res = await fetch ( `/api/w/${ owner . sId } /assistant/builder/suggestions` , {
437
+ return fetchWithErr ( `/api/w/${ owner . sId } /assistant/builder/suggestions` , {
405
438
method : "POST" ,
406
439
headers : {
407
440
"Content-Type" : "application/json" ,
@@ -411,11 +444,28 @@ async function getDescriptionSuggestions({
411
444
inputs : { instructions, name } ,
412
445
} ) ,
413
446
} ) ;
414
- if ( ! res . ok ) {
447
+ }
448
+
449
+ async function fetchWithErr < T > (
450
+ input : RequestInfo | URL ,
451
+ init ?: RequestInit
452
+ ) : Promise < Result < T , APIError > > {
453
+ try {
454
+ const res = await fetch ( input , init ) ;
455
+ if ( ! res . ok ) {
456
+ return new Err ( {
457
+ type : "internal_server_error" ,
458
+ message : `Failed to fetch: ${
459
+ res . statusText
460
+ } \nResponse: ${ await res . text ( ) } `,
461
+ } ) ;
462
+ }
463
+
464
+ return new Ok ( ( await res . json ( ) ) as T ) ;
465
+ } catch ( e ) {
415
466
return new Err ( {
416
467
type : "internal_server_error" ,
417
- message : " Failed to get suggestions" ,
468
+ message : ` Failed to fetch.\nError: ${ e } ` ,
418
469
} ) ;
419
470
}
420
- return new Ok ( await res . json ( ) ) ;
421
471
}
0 commit comments