slice-machine-ui 2.19.2-alpha.jp-figma-to-prismic.3 → 2.19.2-alpha.jp-figma-to-prismic.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/{8UM-Fh6SBL5e0B_msyLxt → J2YngfgTVf7j_xF1hSI57}/_buildManifest.js +1 -1
  3. package/out/_next/static/chunks/20-169231cb23a752ff.js +1 -0
  4. package/out/_next/static/chunks/{489-ce3053e1d81ade83.js → 489-b4b2ce029ea444c1.js} +1 -1
  5. package/out/_next/static/chunks/907-fc580cc28042182f.js +1 -0
  6. package/out/_next/static/chunks/pages/{_app-a9ca8c9f371bd423.js → _app-9abe37b33f3ca7c2.js} +1 -1
  7. package/out/changelog.html +1 -1
  8. package/out/changes.html +1 -1
  9. package/out/custom-types/[customTypeId].html +1 -1
  10. package/out/custom-types.html +1 -1
  11. package/out/index.html +1 -1
  12. package/out/labs.html +1 -1
  13. package/out/page-types/[pageTypeId].html +1 -1
  14. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  15. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  16. package/out/slices.html +1 -1
  17. package/package.json +3 -3
  18. package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/CreateSliceFromImageModal.tsx +335 -267
  19. package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/SliceCard.tsx +6 -2
  20. package/src/icons/FigmaIcon.tsx +16 -6
  21. package/src/legacy/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +1 -1
  22. package/test/src/modules/__fixtures__/serverState.ts +0 -2
  23. package/out/_next/static/chunks/20-4cb8941c8aafa019.js +0 -1
  24. package/out/_next/static/chunks/907-fbd4308471792876.js +0 -1
  25. /package/out/_next/static/{8UM-Fh6SBL5e0B_msyLxt → J2YngfgTVf7j_xF1hSI57}/_ssgManifest.js +0 -0
@@ -12,6 +12,7 @@ import {
12
12
  DialogHeader,
13
13
  FileDropZone,
14
14
  FileUploadButton,
15
+ ProgressCircle,
15
16
  ScrollArea,
16
17
  Text,
17
18
  } from "@prismicio/editor-ui";
@@ -25,6 +26,7 @@ import { getState, telemetry } from "@/apiClient";
25
26
  import { addAiFeedback } from "@/features/aiFeedback";
26
27
  import { useOnboarding } from "@/features/onboarding/useOnboarding";
27
28
  import { useAutoSync } from "@/features/sync/AutoSyncProvider";
29
+ import { useExperimentVariant } from "@/hooks/useExperimentVariant";
28
30
  import { FigmaIcon } from "@/icons/FigmaIcon";
29
31
  import { managerClient } from "@/managerClient";
30
32
  import useSliceMachineActions from "@/modules/useSliceMachineActions";
@@ -42,13 +44,7 @@ const IMAGE_UPLOAD_LIMIT = 10;
42
44
  interface CreateSliceFromImageModalProps {
43
45
  open: boolean;
44
46
  location: "custom_type" | "page_type" | "slices";
45
- onSuccess: (args: {
46
- slices: {
47
- model: SharedSlice;
48
- langSmithUrl?: string;
49
- }[];
50
- library: string;
51
- }) => void;
47
+ onSuccess: (args: { slices: SharedSlice[]; library: string }) => void;
52
48
  onClose: () => void;
53
49
  }
54
50
 
@@ -57,16 +53,18 @@ export function CreateSliceFromImageModal(
57
53
  ) {
58
54
  const { open, location, onClose, onSuccess } = props;
59
55
  const [slices, setSlices] = useState<Slice[]>([]);
56
+ const [isSubmitting, setIsSubmitting] = useState(false);
60
57
  const { syncChanges } = useAutoSync();
61
58
  const { createSliceSuccess } = useSliceMachineActions();
62
59
  const { completeStep } = useOnboarding();
63
60
  const existingSlices = useExistingSlices({ open });
64
-
61
+ const { libraryID, isLoading: isLoadingLibraryID } = useLibraryID();
65
62
  /**
66
63
  * Keeps track of the current instance id.
67
64
  * When the modal is closed, the id is reset.
68
65
  */
69
66
  const id = useRef(crypto.randomUUID());
67
+ const isFigmaEnabled = useIsFigmaEnabled();
70
68
 
71
69
  useHotkeys(
72
70
  ["meta+v", "ctrl+v"],
@@ -74,15 +72,9 @@ export function CreateSliceFromImageModal(
74
72
  event.preventDefault();
75
73
  void handlePaste();
76
74
  },
77
- { enabled: open },
75
+ { enabled: open && isFigmaEnabled },
78
76
  );
79
77
 
80
- useEffect(() => {
81
- if (slices.every((slice) => slice.status === "success")) {
82
- void onAllComplete();
83
- }
84
- }, [slices]); // eslint-disable-line react-hooks/exhaustive-deps
85
-
86
78
  const setSlice = (args: {
87
79
  index: number;
88
80
  slice: (prevSlice: Slice) => Slice;
@@ -99,6 +91,8 @@ export function CreateSliceFromImageModal(
99
91
  };
100
92
 
101
93
  const onImagesSelected = (images: File[]) => {
94
+ if (hasTriggeredGeneration) return;
95
+
102
96
  if (images.length > IMAGE_UPLOAD_LIMIT) {
103
97
  toast.error(
104
98
  `You can only upload ${IMAGE_UPLOAD_LIMIT} images at a time.`,
@@ -111,8 +105,8 @@ export function CreateSliceFromImageModal(
111
105
  ...prevSlices,
112
106
  ...images.map(
113
107
  (image): Slice => ({
114
- status: "uploading",
115
108
  source: "upload",
109
+ status: "uploading",
116
110
  image,
117
111
  }),
118
112
  ),
@@ -125,7 +119,14 @@ export function CreateSliceFromImageModal(
125
119
  };
126
120
 
127
121
  const handlePaste = async () => {
128
- if (!open) return;
122
+ if (
123
+ !open ||
124
+ !isFigmaEnabled ||
125
+ // For now we only support one Figma slice at a time
126
+ slices.some((slice) => slice.source === "figma")
127
+ ) {
128
+ return;
129
+ }
129
130
 
130
131
  // Don't allow pasting while uploads or generation are in progress
131
132
  const isLoading = slices.some(
@@ -282,20 +283,16 @@ export function CreateSliceFromImageModal(
282
283
  }
283
284
  };
284
285
 
285
- const generateAllPendingSlices = async () => {
286
- const smConfig = await managerClient.project.getSliceMachineConfig();
287
- const libraryID = smConfig?.libraries?.[0];
288
- if (libraryID === undefined) {
289
- throw new Error("No library found in the config.");
290
- }
286
+ const generateAllPendingSlices = () => {
287
+ if (libraryID === undefined) return;
291
288
 
292
289
  // Generate all pending slices simultaneously
293
290
  slices.forEach((slice, index) => {
294
291
  if (slice.status === "pending") {
295
292
  void inferSlice({
296
293
  index,
297
- imageUrl: slice.thumbnailUrl,
298
294
  libraryID,
295
+ imageUrl: slice.thumbnailUrl,
299
296
  source: slice.source,
300
297
  });
301
298
  }
@@ -309,7 +306,7 @@ export function CreateSliceFromImageModal(
309
306
  source: "figma" | "upload";
310
307
  }) => {
311
308
  const { index, imageUrl, libraryID, source } = args;
312
- const currentId = id.current;
309
+ let currentId = id.current;
313
310
 
314
311
  setSlice({
315
312
  index,
@@ -329,6 +326,13 @@ export function CreateSliceFromImageModal(
329
326
 
330
327
  if (currentId !== id.current) return;
331
328
 
329
+ const resolvedModel = sliceWithoutConflicts({
330
+ existingSlices: existingSlices.current,
331
+ newSlices: slices,
332
+ slice: inferResult.slice,
333
+ });
334
+
335
+ // Update slice state with success
332
336
  setSlices((prevSlices) => {
333
337
  return prevSlices.map((prevSlice, i) => {
334
338
  if (i !== index) return prevSlice;
@@ -336,16 +340,57 @@ export function CreateSliceFromImageModal(
336
340
  ...prevSlice,
337
341
  status: "success",
338
342
  thumbnailUrl: imageUrl,
339
- model: sliceWithoutConflicts({
340
- existingSlices: existingSlices.current,
341
- newSlices: slices,
342
- slice: inferResult.slice,
343
- }),
343
+ model: resolvedModel,
344
+ langSmithUrl: inferResult.langSmithUrl,
344
345
  };
345
346
  });
346
347
  });
348
+
349
+ if (source === "upload") {
350
+ currentId = id.current;
351
+ const currentSlice = slices[index];
352
+
353
+ const { errors } = await managerClient.slices.createSlice({
354
+ libraryID,
355
+ model: resolvedModel,
356
+ });
357
+ if (errors.length) {
358
+ throw new Error(`Failed to create slice ${resolvedModel.id}.`);
359
+ }
360
+
361
+ // Set the variation screenshot
362
+ await managerClient.slices.updateSliceScreenshot({
363
+ libraryID,
364
+ sliceID: resolvedModel.id,
365
+ variationID: resolvedModel.variations[0].id,
366
+ data: currentSlice.image,
367
+ });
368
+
369
+ if (currentId !== id.current) return;
370
+ }
371
+
372
+ void completeStep("createSlice");
373
+
374
+ void telemetry.track({
375
+ event: "slice:created",
376
+ id: resolvedModel.id,
377
+ name: resolvedModel.name,
378
+ library: libraryID,
379
+ location,
380
+ mode: "ai",
381
+ langSmithUrl: inferResult.langSmithUrl,
382
+ });
383
+
384
+ addAiFeedback({
385
+ type: "model",
386
+ library: libraryID,
387
+ sliceId: resolvedModel.id,
388
+ variationId: resolvedModel.variations[0].id,
389
+ langSmithUrl: inferResult.langSmithUrl,
390
+ });
347
391
  } catch {
348
392
  if (currentId !== id.current) return;
393
+
349
394
  setSlice({
350
395
  index,
351
396
  slice: (prevSlice) => ({
@@ -360,75 +405,38 @@ export function CreateSliceFromImageModal(
360
405
  }
361
406
  };
362
407
 
363
- const onAllComplete = async () => {
364
- const newSlices = slices.reduce<{ upload: NewSlice[]; figma: NewSlice[] }>(
365
- (acc, slice) => {
366
- if (slice.status === "success") {
367
- if (slice.source === "upload") {
368
- acc.upload.push(slice);
369
- } else {
370
- acc.figma.push(slice);
371
- }
372
- }
373
- return acc;
374
- },
375
- { upload: [], figma: [] },
376
- );
408
+ const resetState = () => {
409
+ id.current = crypto.randomUUID();
410
+ setSlices([]);
411
+ };
377
412
 
378
- if (!newSlices.upload.length && !newSlices.figma.length) return;
413
+ const handleClose = () => {
414
+ resetState();
415
+ onClose();
416
+ };
379
417
 
380
- const currentId = id.current;
418
+ const onSubmit = async () => {
381
419
  try {
382
- // Only the slices generated from uploaded images need this step
383
- const { slices, library } = await addSlices(newSlices.upload);
384
- if (currentId !== id.current) return;
420
+ setIsSubmitting(true);
421
+ if (libraryID === undefined) return;
385
422
 
386
423
  const serverState = await getState();
387
424
  createSliceSuccess(serverState.libraries);
388
425
  syncChanges();
389
426
 
390
- const total = newSlices.upload.length + newSlices.figma.length;
391
- toast.success(
392
- `${total} new slice${total > 1 ? "s" : ""} successfully generated.`,
393
- );
394
-
395
- onSuccess({ slices: [...newSlices.upload, ...newSlices.figma], library });
396
- id.current = crypto.randomUUID();
397
- setSlices([]);
398
-
399
- void completeStep("createSlice");
400
-
401
- for (const { model, langSmithUrl } of slices) {
402
- void telemetry.track({
403
- event: "slice:created",
404
- id: model.id,
405
- name: model.name,
406
- library,
407
- location,
408
- mode: "ai",
409
- langSmithUrl,
410
- });
427
+ onSuccess({
428
+ slices: slices.flatMap((slice) =>
429
+ slice.status === "success" ? slice.model : [],
430
+ ),
431
+ library: libraryID,
432
+ });
411
433
 
412
- addAiFeedback({
413
- type: "model",
414
- library,
415
- sliceId: model.id,
416
- variationId: model.variations[0].id,
417
- langSmithUrl,
418
- });
419
- }
420
- } catch {
421
- if (currentId !== id.current) return;
422
- toast.error("An unexpected error happened while adding slices.");
434
+ resetState();
435
+ } finally {
436
+ setIsSubmitting(false);
423
437
  }
424
438
  };
425
439
 
426
- const handleClose = () => {
427
- id.current = crypto.randomUUID();
428
- setSlices([]);
429
- onClose();
430
- };
431
-
432
440
  const loadingSliceCount = slices.filter((slice) => {
433
441
  return slice.status === "uploading" || slice.status === "generating";
434
442
  }).length;
@@ -441,8 +449,18 @@ export function CreateSliceFromImageModal(
441
449
  return slice.status === "generating" || slice.status === "success";
442
450
  });
443
451
 
452
+ const completedSliceCount = slices.filter((slice) => {
453
+ return slice.status === "success";
454
+ }).length;
455
+
444
456
  const generateSliceCount = loadingSliceCount + pendingSliceCount;
445
457
 
458
+ console.log({
459
+ slices,
460
+ generateSliceCount,
461
+ loadingSliceCount,
462
+ pendingSliceCount,
463
+ });
446
464
  return (
447
465
  <Dialog
448
466
  open={open}
@@ -453,133 +471,164 @@ export function CreateSliceFromImageModal(
453
471
  <DialogDescription hidden>
454
472
  Upload images to generate slices with AI
455
473
  </DialogDescription>
456
- {slices.length === 0 ? (
457
- <Box
458
- padding={16}
459
- height="100%"
460
- gap={16}
461
- display="flex"
462
- flexDirection="column"
463
- >
464
- <Box
465
- display="flex"
466
- gap={16}
467
- alignItems="center"
468
- backgroundColor="grey2"
469
- padding={16}
470
- borderRadius={12}
471
- >
472
- <Box display="flex" gap={8} alignItems="center" flexGrow={1}>
474
+ {!isLoadingLibraryID ? (
475
+ <>
476
+ {slices.length === 0 ? (
477
+ <Box
478
+ padding={16}
479
+ height="100%"
480
+ gap={16}
481
+ display="flex"
482
+ flexDirection="column"
483
+ >
484
+ {isFigmaEnabled && (
485
+ <Box
486
+ display="flex"
487
+ gap={16}
488
+ alignItems="center"
489
+ backgroundColor="grey2"
490
+ padding={16}
491
+ borderRadius={12}
492
+ >
493
+ <Box
494
+ display="flex"
495
+ gap={8}
496
+ alignItems="center"
497
+ flexGrow={1}
498
+ >
499
+ <Box
500
+ width={48}
501
+ height={48}
502
+ backgroundColor="grey12"
503
+ borderRadius="100%"
504
+ display="flex"
505
+ alignItems="center"
506
+ justifyContent="center"
507
+ >
508
+ <FigmaIcon variant="original" height={25} />
509
+ </Box>
510
+ <Box display="flex" flexDirection="column" flexGrow={1}>
511
+ <Text variant="bold">Want to work faster?</Text>
512
+ <Text variant="small" color="grey11">
513
+ Copy frames from Figma with the Slice Machine plugin
514
+ and paste them here.
515
+ </Text>
516
+ </Box>
517
+ </Box>
518
+ <Button
519
+ endIcon="arrowForward"
520
+ color="indigo"
521
+ onClick={() =>
522
+ window.open(
523
+ "https://www.figma.com/community/plugin/TODO",
524
+ "_blank",
525
+ )
526
+ }
527
+ sx={{ marginRight: 8 }}
528
+ invisible
529
+ >
530
+ Install plugin
531
+ </Button>
532
+ </Box>
533
+ )}
534
+ <FileDropZone
535
+ onFilesSelected={onImagesSelected}
536
+ assetType="image"
537
+ maxFiles={IMAGE_UPLOAD_LIMIT}
538
+ overlay={
539
+ <UploadBlankSlate
540
+ onFilesSelected={onImagesSelected}
541
+ onPaste={() => void handlePaste()}
542
+ droppingFiles
543
+ />
544
+ }
545
+ >
546
+ <UploadBlankSlate
547
+ onFilesSelected={onImagesSelected}
548
+ onPaste={() => void handlePaste()}
549
+ />
550
+ </FileDropZone>
551
+ </Box>
552
+ ) : (
553
+ <>
473
554
  <Box
474
- width={48}
475
- height={48}
476
- backgroundColor="grey12"
477
- borderRadius="100%"
478
555
  display="flex"
479
556
  alignItems="center"
480
- justifyContent="center"
557
+ justifyContent="space-between"
558
+ padding={16}
481
559
  >
482
- <FigmaIcon variant="original" height={25} />
483
- </Box>
484
- <Box display="flex" flexDirection="column" flexGrow={1}>
485
- <Text variant="bold">Want to work faster?</Text>
486
- <Text variant="small" color="grey11">
487
- Copy frames from Figma with the Slice Machine plugin and
488
- paste them here.
489
- </Text>
560
+ <Text variant="h3">Design</Text>
561
+ <FileUploadButton
562
+ size="medium"
563
+ color="grey"
564
+ onFilesSelected={onImagesSelected}
565
+ startIcon="attachFile"
566
+ disabled={hasTriggeredGeneration}
567
+ >
568
+ Add images
569
+ </FileUploadButton>
490
570
  </Box>
491
- </Box>
492
- <Button
493
- endIcon="arrowForward"
494
- color="indigo"
495
- onClick={() =>
496
- window.open(
497
- "https://www.figma.com/community/plugin/TODO",
498
- "_blank",
499
- )
500
- }
501
- sx={{ marginRight: 8 }}
502
- invisible
503
- >
504
- Install plugin
505
- </Button>
506
- </Box>
507
- <FileDropZone
508
- onFilesSelected={onImagesSelected}
509
- assetType="image"
510
- maxFiles={IMAGE_UPLOAD_LIMIT}
511
- overlay={
512
- <UploadBlankSlate
513
- onFilesSelected={onImagesSelected}
514
- onPaste={() => void handlePaste()}
515
- droppingFiles
516
- />
517
- }
518
- >
519
- <UploadBlankSlate
520
- onFilesSelected={onImagesSelected}
521
- onPaste={() => void handlePaste()}
522
- />
523
- </FileDropZone>
524
- </Box>
525
- ) : (
526
- <>
527
- <Box
528
- display="flex"
529
- alignItems="center"
530
- justifyContent="space-between"
531
- padding={16}
532
- >
533
- <Text variant="h3">Design</Text>
534
- <FileUploadButton
571
+ <ScrollArea stableScrollbar={false}>
572
+ <Box
573
+ display="grid"
574
+ gridTemplateColumns="1fr 1fr"
575
+ gap={16}
576
+ padding={16}
577
+ >
578
+ {slices.map((slice, index) => (
579
+ <SliceCard slice={slice} key={`slice-${index}`} />
580
+ ))}
581
+ </Box>
582
+ </ScrollArea>
583
+ </>
584
+ )}
585
+ <DialogActions>
586
+ <DialogCancelButton
535
587
  size="medium"
536
- color="grey"
537
- onFilesSelected={onImagesSelected}
538
- startIcon="attachFile"
539
- disabled={hasTriggeredGeneration}
588
+ onClick={handleClose}
589
+ disabled={loadingSliceCount > 0}
540
590
  >
541
- Add images
542
- </FileUploadButton>
543
- </Box>
544
- <ScrollArea stableScrollbar={false}>
545
- <Box
546
- display="grid"
547
- gridTemplateColumns="1fr 1fr"
548
- gap={16}
549
- padding={16}
550
- >
551
- {slices.map((slice, index) => (
552
- <SliceCard slice={slice} key={`slice-${index}`} />
553
- ))}
554
- </Box>
555
- </ScrollArea>
591
+ Close
592
+ </DialogCancelButton>
593
+ {completedSliceCount === 0 ? (
594
+ <DialogActionButton
595
+ color="purple"
596
+ startIcon="autoFixHigh"
597
+ onClick={() => void generateAllPendingSlices()}
598
+ disabled={
599
+ hasTriggeredGeneration ||
600
+ loadingSliceCount > 0 ||
601
+ pendingSliceCount === 0
602
+ }
603
+ loading={loadingSliceCount > 0}
604
+ size="medium"
605
+ >
606
+ Generate{" "}
607
+ {generateSliceCount > 0 ? `(${generateSliceCount}) ` : ""}
608
+ {generateSliceCount === 1 ? "Slice" : "Slices"}
609
+ </DialogActionButton>
610
+ ) : (
611
+ <DialogActionButton
612
+ color="purple"
613
+ onClick={() => void onSubmit()}
614
+ loading={isSubmitting}
615
+ size="medium"
616
+ >
617
+ {getSubmitButtonLabel(location, completedSliceCount)}
618
+ </DialogActionButton>
619
+ )}
620
+ </DialogActions>
556
621
  </>
557
- )}
558
-
559
- <DialogActions>
560
- <DialogCancelButton
561
- size="medium"
562
- onClick={handleClose}
563
- disabled={loadingSliceCount > 0}
564
- >
565
- Close
566
- </DialogCancelButton>
567
- <DialogActionButton
568
- color="purple"
569
- startIcon="autoFixHigh"
570
- onClick={() => void generateAllPendingSlices()}
571
- disabled={
572
- hasTriggeredGeneration ||
573
- loadingSliceCount > 0 ||
574
- pendingSliceCount === 0
575
- }
576
- loading={hasTriggeredGeneration}
577
- size="medium"
622
+ ) : (
623
+ <Box
624
+ display="flex"
625
+ justifyContent="center"
626
+ alignItems="center"
627
+ height="100%"
578
628
  >
579
- Generate {generateSliceCount > 0 ? `(${generateSliceCount}) ` : ""}
580
- {generateSliceCount === 1 ? "Slice" : "Slices"}
581
- </DialogActionButton>
582
- </DialogActions>
629
+ <ProgressCircle color="purple9" />
630
+ </Box>
631
+ )}
583
632
  </DialogContent>
584
633
  </Dialog>
585
634
  );
@@ -591,6 +640,7 @@ function UploadBlankSlate(props: {
591
640
  onPaste: () => void;
592
641
  }) {
593
642
  const { droppingFiles = false, onFilesSelected, onPaste } = props;
643
+ const isFigmaEnabled = useIsFigmaEnabled();
594
644
 
595
645
  return (
596
646
  <Box
@@ -618,30 +668,53 @@ function UploadBlankSlate(props: {
618
668
  gap={4}
619
669
  alignItems="center"
620
670
  >
621
- <Text>Generate slices from your designs</Text>
622
- <Text variant="small" color="grey11">
623
- Upload your design images or paste them directly from Figma.
624
- </Text>
671
+ {isFigmaEnabled ? (
672
+ <>
673
+ <Text>Generate slices from your designs</Text>
674
+ <Text variant="small" color="grey11">
675
+ Upload your design images or paste them directly from Figma.
676
+ </Text>
677
+ </>
678
+ ) : (
679
+ <>
680
+ <Text>Upload your design images.</Text>
681
+ <Text variant="small" color="grey11">
682
+ Once uploaded, you can generate slices automatically using AI.
683
+ </Text>
684
+ </>
685
+ )}
625
686
  </Box>
626
687
  <Box display="flex" alignItems="center" gap={16}>
627
- <Button
628
- size="small"
629
- renderStartIcon={() => (
630
- <FigmaIcon variant="original" height={16} />
631
- )}
632
- color="grey"
633
- onClick={onPaste}
634
- >
635
- Paste from Figma
636
- </Button>
637
- <FileUploadButton
638
- size="small"
639
- onFilesSelected={onFilesSelected}
640
- color="purple"
641
- invisible
642
- >
643
- Add images
644
- </FileUploadButton>
688
+ {isFigmaEnabled ? (
689
+ <>
690
+ <Button
691
+ size="small"
692
+ renderStartIcon={() => (
693
+ <FigmaIcon variant="original" height={16} />
694
+ )}
695
+ color="grey"
696
+ onClick={onPaste}
697
+ >
698
+ Paste from Figma
699
+ </Button>
700
+ <FileUploadButton
701
+ size="small"
702
+ onFilesSelected={onFilesSelected}
703
+ color="purple"
704
+ invisible
705
+ >
706
+ Add images
707
+ </FileUploadButton>
708
+ </>
709
+ ) : (
710
+ <FileUploadButton
711
+ startIcon="attachFile"
712
+ onFilesSelected={onFilesSelected}
713
+ color="grey"
714
+ >
715
+ Add images
716
+ </FileUploadButton>
717
+ )}
645
718
  </Box>
646
719
  </Box>
647
720
  </BlankSlate>
@@ -666,13 +739,6 @@ async function getImageUrl({ image }: { image: File }) {
666
739
  return url;
667
740
  }
668
741
 
669
- type NewSlice = {
670
- image: File;
671
- model: SharedSlice;
672
- langSmithUrl?: string;
673
- source: "figma" | "upload";
674
- };
675
-
676
742
  /**
677
743
  * Keeps track of the existing slices in the project.
678
744
  * Re-fetches them when the modal is opened.
@@ -744,40 +810,42 @@ function sliceWithoutConflicts({
744
810
  };
745
811
  }
746
812
 
747
- async function addSlices(newSlices: NewSlice[]) {
748
- // use the first library
749
- const { libraries = [] } =
750
- await managerClient.project.getSliceMachineConfig();
751
- const library = libraries[0];
752
- if (!library) {
753
- throw new Error("No library found in the config.");
754
- }
755
-
756
- for (const { model } of newSlices) {
757
- const { errors } = await managerClient.slices.createSlice({
758
- libraryID: library,
759
- model,
760
- });
761
- if (errors.length) {
762
- throw new Error(`Failed to create slice ${model.id}.`);
763
- }
764
- }
813
+ function useLibraryID() {
814
+ const [libraryID, setLibraryID] = useState<string | undefined>();
765
815
 
766
- // for each added slice, set the variation screenshot
767
- const slices = await Promise.all(
768
- newSlices.map(async ({ model, image, langSmithUrl }) => {
769
- await managerClient.slices.updateSliceScreenshot({
770
- libraryID: library,
771
- sliceID: model.id,
772
- variationID: model.variations[0].id,
773
- data: image,
816
+ useEffect(() => {
817
+ managerClient.project
818
+ .getSliceMachineConfig()
819
+ .then((smConfig) => {
820
+ const libraryID = smConfig?.libraries?.[0];
821
+ if (libraryID === undefined) {
822
+ throw new Error("No library found in the config.");
823
+ }
824
+ setLibraryID(libraryID);
825
+ })
826
+ .catch(() => {
827
+ throw new Error("Could not get library ID from the config.");
774
828
  });
775
- return {
776
- model,
777
- langSmithUrl,
778
- };
779
- }),
780
- );
829
+ }, []);
830
+
831
+ return { libraryID, isLoading: libraryID === undefined };
832
+ }
781
833
 
782
- return { library, slices };
834
+ function useIsFigmaEnabled() {
835
+ const experiment = useExperimentVariant("llm-proxy-access");
836
+ return experiment?.value === "on";
783
837
  }
838
+
839
+ const getSubmitButtonLabel = (
840
+ location: "custom_type" | "page_type" | "slices",
841
+ completedSliceCount: number,
842
+ ) => {
843
+ switch (location) {
844
+ case "custom_type":
845
+ return `Add to type (${completedSliceCount})`;
846
+ case "page_type":
847
+ return `Add to page (${completedSliceCount})`;
848
+ case "slices":
849
+ return "Done";
850
+ }
851
+ };