@sanity/assist 1.0.12 → 1.1.1

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.
package/dist/index.js CHANGED
@@ -133,6 +133,20 @@ function isType(schemaType, typeName) {
133
133
  }
134
134
  return isType(schemaType.type, typeName);
135
135
  }
136
+ function isImage(schemaType) {
137
+ return isType(schemaType, "image");
138
+ }
139
+ function getCaptionFieldOption(schemaType) {
140
+ var _a;
141
+ if (!schemaType) {
142
+ return void 0;
143
+ }
144
+ const captionField = (_a = schemaType.options) == null ? void 0 : _a.captionField;
145
+ if (captionField) {
146
+ return captionField;
147
+ }
148
+ return getCaptionFieldOption(schemaType.type);
149
+ }
136
150
  function isSchemaAssistEnabled(type) {
137
151
  var _a, _b;
138
152
  return !((_b = (_a = type.options) == null ? void 0 : _a.aiWritingAssistance) == null ? void 0 : _b.exclude);
@@ -749,10 +763,11 @@ function InstructionTaskHistoryButton(props) {
749
763
  const titledTasks = react.useMemo(() => {
750
764
  var _a2;
751
765
  const t = (_a2 = tasks == null ? void 0 : tasks.filter(task => task.started && /* @__PURE__ */new Date().getTime() - new Date(task.started).getTime() < maxHistoryVisibilityMs).map(task => {
766
+ var _a3;
752
767
  const instruction = instructions == null ? void 0 : instructions.find(i => i._key === task.instructionKey);
753
768
  return {
754
769
  ...task,
755
- title: showTitles ? getInstructionTitle(instruction) : void 0,
770
+ title: showTitles ? (_a3 = task.title) != null ? _a3 : getInstructionTitle(instruction) : void 0,
756
771
  cancel: () => cancelRun(task._key)
757
772
  };
758
773
  })) != null ? _a2 : [];
@@ -906,6 +921,48 @@ function useApiClient(customApiClient) {
906
921
  });
907
922
  return react.useMemo(() => customApiClient ? customApiClient(client) : client, [client, customApiClient]);
908
923
  }
924
+ function useGenerateCaption(apiClient) {
925
+ const [loading, setLoading] = react.useState(false);
926
+ const user = sanity.useCurrentUser();
927
+ const schema = sanity.useSchema();
928
+ const types = react.useMemo(() => serializeSchema(schema, {
929
+ leanFormat: true
930
+ }), [schema]);
931
+ const toast = ui.useToast();
932
+ const generateCaption = react.useCallback(_ref4 => {
933
+ let {
934
+ path,
935
+ documentId
936
+ } = _ref4;
937
+ setLoading(true);
938
+ return apiClient.request({
939
+ method: "POST",
940
+ url: "/assist/tasks/generate-caption/".concat(apiClient.config().dataset, "?projectId=").concat(apiClient.config().projectId),
941
+ body: {
942
+ path,
943
+ documentId,
944
+ types,
945
+ userId: user == null ? void 0 : user.id
946
+ }
947
+ }).catch(e => {
948
+ toast.push({
949
+ status: "error",
950
+ title: "Generate caption failed",
951
+ description: e.message
952
+ });
953
+ setLoading(false);
954
+ throw e;
955
+ }).finally(() => {
956
+ setTimeout(() => {
957
+ setLoading(false);
958
+ }, 2e3);
959
+ });
960
+ }, [setLoading, apiClient, toast, user, types]);
961
+ return react.useMemo(() => ({
962
+ generateCaption,
963
+ loading
964
+ }), [generateCaption, loading]);
965
+ }
909
966
  function useGetInstructStatus(apiClient) {
910
967
  const [loading, setLoading] = react.useState(true);
911
968
  const projectClient = sanity.useClient({
@@ -1106,8 +1163,8 @@ function RunInstructionProvider(props) {
1106
1163
  runInstructionRequest({
1107
1164
  ...request,
1108
1165
  instructionKey: instruction._key,
1109
- userTexts: Object.entries(inputs).map(_ref4 => {
1110
- let [key, value] = _ref4;
1166
+ userTexts: Object.entries(inputs).map(_ref5 => {
1167
+ let [key, value] = _ref5;
1111
1168
  return {
1112
1169
  blockKey: key,
1113
1170
  userInput: value
@@ -1120,8 +1177,8 @@ function RunInstructionProvider(props) {
1120
1177
  const open = !!runRequest;
1121
1178
  const runDisabled = react.useMemo(() => {
1122
1179
  var _a2, _b;
1123
- return ((_b = (_a2 = runRequest == null ? void 0 : runRequest.userInputBlocks) == null ? void 0 : _a2.length) != null ? _b : 0) > Object.entries(inputs).filter(_ref5 => {
1124
- let [, value] = _ref5;
1180
+ return ((_b = (_a2 = runRequest == null ? void 0 : runRequest.userInputBlocks) == null ? void 0 : _a2.length) != null ? _b : 0) > Object.entries(inputs).filter(_ref6 => {
1181
+ let [, value] = _ref6;
1125
1182
  return !!value;
1126
1183
  }).length;
1127
1184
  }, [runRequest == null ? void 0 : runRequest.userInputBlocks, inputs]);
@@ -1701,10 +1758,10 @@ const assistInspector = {
1701
1758
  showAsAction: false
1702
1759
  }),
1703
1760
  component: AssistInspectorWrapper,
1704
- onClose(_ref6) {
1761
+ onClose(_ref7) {
1705
1762
  let {
1706
1763
  params
1707
- } = _ref6;
1764
+ } = _ref7;
1708
1765
  return {
1709
1766
  params: sanity.typed({
1710
1767
  ...params,
@@ -1777,18 +1834,19 @@ var __template$3 = (cooked, raw) => __freeze$3(__defProp$3(cooked, "raw", {
1777
1834
  var _a$3, _b$1;
1778
1835
  const fadeIn = styled.keyframes(_a$3 || (_a$3 = __template$3(["\n 0% {\n opacity: 0;\n transform: scale(0.75);\n }\n 40% {\n opacity: 0;\n transform: scale(0.75);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n"])));
1779
1836
  const FadeInDiv = styled__default.default.div(_b$1 || (_b$1 = __template$3(["\n animation-name: ", ";\n animation-timing-function: ease-in-out;\n"])), fadeIn);
1780
- function FadeInContent(_ref7) {
1837
+ const FadeInContent = react.forwardRef(function FadeInContent2(_ref8, ref) {
1781
1838
  let {
1782
1839
  children,
1783
1840
  durationMs = 250
1784
- } = _ref7;
1841
+ } = _ref8;
1785
1842
  return /* @__PURE__ */jsxRuntime.jsx(FadeInDiv, {
1843
+ ref,
1786
1844
  style: {
1787
1845
  animationDuration: "".concat(durationMs, "ms")
1788
1846
  },
1789
1847
  children
1790
1848
  });
1791
- }
1849
+ });
1792
1850
  const purple = {
1793
1851
  "50": {
1794
1852
  title: "Purple 50",
@@ -3075,13 +3133,13 @@ function useSelectedSchema(fieldPath, documentSchema) {
3075
3133
  return currentSchema;
3076
3134
  }, [documentSchema, fieldPath]);
3077
3135
  }
3078
- function FieldsInitializer(_ref8) {
3136
+ function FieldsInitializer(_ref9) {
3079
3137
  let {
3080
3138
  pathKey,
3081
3139
  activePath,
3082
3140
  fieldExists,
3083
3141
  onChange
3084
- } = _ref8;
3142
+ } = _ref9;
3085
3143
  const initialized = react.useRef(false);
3086
3144
  react.useEffect(() => {
3087
3145
  if (initialized.current || fieldExists || activePath || !pathKey) {
@@ -3253,8 +3311,8 @@ function IconInput(props) {
3253
3311
  onChange
3254
3312
  } = props;
3255
3313
  const id = react.useId();
3256
- const items = react.useMemo(() => Object.entries(icons.icons).map(_ref9 => {
3257
- let [key, icon] = _ref9;
3314
+ const items = react.useMemo(() => Object.entries(icons.icons).map(_ref10 => {
3315
+ let [key, icon] = _ref10;
3258
3316
  return /* @__PURE__ */jsxRuntime.jsx(IconItem, {
3259
3317
  iconKey: key,
3260
3318
  icon,
@@ -3282,12 +3340,12 @@ function IconInput(props) {
3282
3340
  }
3283
3341
  });
3284
3342
  }
3285
- function IconItem(_ref10) {
3343
+ function IconItem(_ref11) {
3286
3344
  let {
3287
3345
  icon,
3288
3346
  iconKey: key,
3289
3347
  onChange
3290
- } = _ref10;
3348
+ } = _ref11;
3291
3349
  const onClick = react.useCallback(() => onChange(sanity.set(key)), [onChange, key]);
3292
3350
  return /* @__PURE__ */jsxRuntime.jsx(ui.MenuItem, {
3293
3351
  icon,
@@ -3298,8 +3356,8 @@ function IconItem(_ref10) {
3298
3356
  }
3299
3357
  function getIcon(iconName) {
3300
3358
  var _a, _b;
3301
- return (_b = (_a = Object.entries(icons.icons).find(_ref11 => {
3302
- let [key] = _ref11;
3359
+ return (_b = (_a = Object.entries(icons.icons).find(_ref12 => {
3360
+ let [key] = _ref12;
3303
3361
  return key === iconName;
3304
3362
  })) == null ? void 0 : _a[1]) != null ? _b : icons.icons.sparkles;
3305
3363
  }
@@ -3553,11 +3611,11 @@ const contextDocumentSchema = sanity.defineType({
3553
3611
  title: "title",
3554
3612
  context: "context"
3555
3613
  },
3556
- prepare(_ref12) {
3614
+ prepare(_ref13) {
3557
3615
  let {
3558
3616
  title,
3559
3617
  context
3560
- } = _ref12;
3618
+ } = _ref13;
3561
3619
  var _a;
3562
3620
  const text = context == null ? void 0 : context.flatMap(block => block == null ? void 0 : block.children).flatMap(child => {
3563
3621
  var _a2;
@@ -3686,10 +3744,10 @@ const fieldReference = sanity.defineType({
3686
3744
  select: {
3687
3745
  path: "path"
3688
3746
  },
3689
- prepare(_ref13) {
3747
+ prepare(_ref14) {
3690
3748
  let {
3691
3749
  path
3692
- } = _ref13;
3750
+ } = _ref14;
3693
3751
  return {
3694
3752
  title: path,
3695
3753
  path,
@@ -3835,12 +3893,12 @@ const instruction = sanity.defineType({
3835
3893
  title: "title",
3836
3894
  userId: "userId"
3837
3895
  },
3838
- prepare: _ref14 => {
3896
+ prepare: _ref15 => {
3839
3897
  let {
3840
3898
  icon,
3841
3899
  title,
3842
3900
  userId
3843
- } = _ref14;
3901
+ } = _ref15;
3844
3902
  return {
3845
3903
  title,
3846
3904
  icon: icon ? icons.icons[icon] : icons.SparklesIcon,
@@ -4128,6 +4186,100 @@ function PrivateIcon() {
4128
4186
  children: /* @__PURE__ */jsxRuntime.jsx(icons.LockIcon, {})
4129
4187
  });
4130
4188
  }
4189
+ const ImageContext = react.createContext(void 0);
4190
+ function ImageContextProvider(props) {
4191
+ var _a;
4192
+ const {
4193
+ schemaType,
4194
+ path,
4195
+ value
4196
+ } = props;
4197
+ const assetRef = (_a = value == null ? void 0 : value.asset) == null ? void 0 : _a._ref;
4198
+ const [assetRefState, setAssetRefState] = react.useState(assetRef);
4199
+ const {
4200
+ documentId
4201
+ } = useAssistDocumentContext();
4202
+ const {
4203
+ config
4204
+ } = useAiAssistanceConfig();
4205
+ const apiClient = useApiClient(config == null ? void 0 : config.__customApiClient);
4206
+ const {
4207
+ generateCaption
4208
+ } = useGenerateCaption(apiClient);
4209
+ react.useEffect(() => {
4210
+ const captionField = getCaptionFieldOption(schemaType);
4211
+ if (assetRef && documentId && captionField && assetRef !== assetRefState) {
4212
+ setAssetRefState(assetRef);
4213
+ generateCaption({
4214
+ path: sanity.pathToString([...path, captionField]),
4215
+ documentId
4216
+ });
4217
+ }
4218
+ }, [schemaType, path, assetRef, assetRefState, documentId, generateCaption]);
4219
+ const context = react.useMemo(() => {
4220
+ const captionField = getCaptionFieldOption(schemaType);
4221
+ return captionField ? {
4222
+ captionPath: sanity.pathToString([...path, captionField]),
4223
+ assetRef
4224
+ } : void 0;
4225
+ }, [schemaType, path, assetRef]);
4226
+ return /* @__PURE__ */jsxRuntime.jsx(ImageContext.Provider, {
4227
+ value: context,
4228
+ children: props.renderDefault(props)
4229
+ });
4230
+ }
4231
+ function node$1(node2) {
4232
+ return node2;
4233
+ }
4234
+ const generateCaptionsActions = {
4235
+ name: "sanity-assist-generate-captions",
4236
+ useAction(props) {
4237
+ const pathKey = usePathKey(props.path);
4238
+ const {
4239
+ config
4240
+ } = useAiAssistanceConfig();
4241
+ const apiClient = useApiClient(config == null ? void 0 : config.__customApiClient);
4242
+ const {
4243
+ generateCaption,
4244
+ loading
4245
+ } = useGenerateCaption(apiClient);
4246
+ const imageContext = react.useContext(ImageContext);
4247
+ if (imageContext && pathKey === (imageContext == null ? void 0 : imageContext.captionPath)) {
4248
+ const {
4249
+ documentId
4250
+ } = useAssistDocumentContext();
4251
+ return react.useMemo(() => {
4252
+ return node$1({
4253
+ type: "action",
4254
+ icon: loading ? () => /* @__PURE__ */jsxRuntime.jsx(ui.Box, {
4255
+ style: {
4256
+ height: 17
4257
+ },
4258
+ children: /* @__PURE__ */jsxRuntime.jsx(ui.Spinner, {
4259
+ style: {
4260
+ transform: "translateY(6px)"
4261
+ }
4262
+ })
4263
+ }) : icons.ImageIcon,
4264
+ title: "Generate caption",
4265
+ onAction: () => {
4266
+ if (loading) {
4267
+ return;
4268
+ }
4269
+ generateCaption({
4270
+ path: pathKey,
4271
+ documentId: documentId != null ? documentId : ""
4272
+ });
4273
+ },
4274
+ renderAsButton: true,
4275
+ disabled: loading,
4276
+ hidden: !imageContext.assetRef
4277
+ });
4278
+ }, [generateCaption, pathKey, documentId, loading, imageContext]);
4279
+ }
4280
+ return void 0;
4281
+ }
4282
+ };
4131
4283
  function node(node2) {
4132
4284
  return node2;
4133
4285
  }
@@ -4177,6 +4329,7 @@ const assistFieldActions = {
4177
4329
  const isInspectorOpen = (inspector == null ? void 0 : inspector.name) === aiInspectorId;
4178
4330
  const isPathSelected = pathKey === selectedPath;
4179
4331
  const isSelected = isInspectorOpen && isPathSelected;
4332
+ const imageCaptionAction = generateCaptionsActions.useAction(props);
4180
4333
  const manageInstructions = react.useCallback(() => isSelected ? closeInspector(aiInspectorId) : openInspector(aiInspectorId, {
4181
4334
  [fieldPathParam]: pathKey,
4182
4335
  [instructionParam]: void 0
@@ -4202,21 +4355,21 @@ const assistFieldActions = {
4202
4355
  }, [fieldAssist == null ? void 0 : fieldAssist.instructions]);
4203
4356
  const instructions = react.useMemo(() => [...privateInstructions, ...sharedInstructions], [privateInstructions, sharedInstructions]);
4204
4357
  const runInstructionsGroup = react.useMemo(() => {
4205
- return (instructions == null ? void 0 : instructions.length) ? node({
4358
+ return (instructions == null ? void 0 : instructions.length) || imageCaptionAction ? node({
4206
4359
  type: "group",
4207
4360
  icon: () => null,
4208
4361
  title: "Run instructions",
4209
- children: instructions.map(instruction => instructionItem({
4362
+ children: [...(instructions == null ? void 0 : instructions.map(instruction => instructionItem({
4210
4363
  instruction,
4211
4364
  isPrivate: Boolean(instruction.userId && instruction.userId === (currentUser == null ? void 0 : currentUser.id)),
4212
4365
  onInstructionAction,
4213
4366
  hidden: isHidden,
4214
4367
  documentIsNew: !!documentIsNew,
4215
4368
  assistSupported
4216
- })),
4369
+ }))), imageCaptionAction].filter(Boolean),
4217
4370
  expanded: true
4218
4371
  }) : void 0;
4219
- }, [instructions, currentUser == null ? void 0 : currentUser.id, onInstructionAction, isHidden, documentIsNew, assistSupported]);
4372
+ }, [instructions, currentUser == null ? void 0 : currentUser.id, onInstructionAction, isHidden, documentIsNew, assistSupported, imageCaptionAction]);
4220
4373
  const instructionsLength = (instructions == null ? void 0 : instructions.length) || 0;
4221
4374
  const manageInstructionsItem = react.useMemo(() => node({
4222
4375
  type: "action",
@@ -4229,23 +4382,13 @@ const assistFieldActions = {
4229
4382
  type: "group",
4230
4383
  icon: icons.SparklesIcon,
4231
4384
  title: pluginTitleShort,
4232
- children: [runInstructionsGroup,
4233
- /* documentIsNew && {
4234
- type: 'action',
4235
- disabled: true,
4236
- title: `Document is new. Make an edit to enable `,
4237
- icon: WarningOutlineIcon,
4238
- tone: 'caution',
4239
- status: 'warning',
4240
- onAction: () => {},
4241
- },*/
4242
- manageInstructionsItem].filter(c => !!c),
4385
+ children: [runInstructionsGroup, assistSupported && manageInstructionsItem].filter(c => !!c),
4243
4386
  expanded: false,
4244
4387
  renderAsButton: true,
4245
- hidden: !assistSupported
4388
+ hidden: !assistSupported && !imageCaptionAction
4246
4389
  }), [
4247
4390
  //documentIsNew,
4248
- runInstructionsGroup, manageInstructionsItem, assistSupported]);
4391
+ runInstructionsGroup, manageInstructionsItem, assistSupported, imageCaptionAction]);
4249
4392
  const emptyAction = react.useMemo(() => node({
4250
4393
  type: "action",
4251
4394
  hidden: !assistSupported,
@@ -4255,7 +4398,7 @@ const assistFieldActions = {
4255
4398
  title: pluginTitleShort,
4256
4399
  selected: isSelected
4257
4400
  }), [assistSupported, manageInstructions, isSelected]);
4258
- if (instructionsLength === 0) {
4401
+ if (instructionsLength === 0 && !imageCaptionAction) {
4259
4402
  return emptyAction;
4260
4403
  }
4261
4404
  return group;
@@ -4315,13 +4458,13 @@ function useInstructionToaster(documentId, documentSchemaType) {
4315
4458
  return !prevTask && task.ended || !(prevTask == null ? void 0 : prevTask.ended) && task.ended;
4316
4459
  }).filter(task => task.ended && dateFns.isAfter(dateFns.addSeconds(new Date(task.ended), 30), /* @__PURE__ */new Date()));
4317
4460
  endedTasks == null ? void 0 : endedTasks.forEach(task => {
4318
- var _a2;
4319
- const title = getInstructionTitle(task.instruction);
4461
+ var _a2, _b;
4462
+ const title = (_a2 = task.title) != null ? _a2 : getInstructionTitle(task.instruction);
4320
4463
  if (task.reason === "error") {
4321
4464
  toast.push({
4322
4465
  title: "Failed: ".concat(title),
4323
4466
  status: "error",
4324
- description: "Instruction failed. ".concat((_a2 = task.message) != null ? _a2 : ""),
4467
+ description: "Instruction failed. ".concat((_b = task.message) != null ? _b : ""),
4325
4468
  closable: true,
4326
4469
  duration: 1e4
4327
4470
  });
@@ -4368,11 +4511,11 @@ function AssistDocumentInputWrapper(props) {
4368
4511
  documentId
4369
4512
  });
4370
4513
  }
4371
- function AssistDocumentInput(_ref15) {
4514
+ function AssistDocumentInput(_ref16) {
4372
4515
  let {
4373
4516
  documentId,
4374
4517
  ...props
4375
- } = _ref15;
4518
+ } = _ref16;
4376
4519
  useInstructionToaster(documentId, props.schemaType);
4377
4520
  return /* @__PURE__ */jsxRuntime.jsx(FirstAssistedPathProvider, {
4378
4521
  members: props.members,
@@ -4461,12 +4604,12 @@ const assist = sanity.definePlugin(config => {
4461
4604
  unstable_fieldActions: prev => {
4462
4605
  return [...prev, assistFieldActions];
4463
4606
  },
4464
- unstable_languageFilter: (prev, _ref16) => {
4607
+ unstable_languageFilter: (prev, _ref17) => {
4465
4608
  let {
4466
4609
  documentId,
4467
4610
  schema,
4468
4611
  schemaType
4469
- } = _ref16;
4612
+ } = _ref17;
4470
4613
  const docSchema = schema.get(schemaType);
4471
4614
  return [...prev, createAssistDocumentPresence(documentId, docSchema)];
4472
4615
  }
@@ -4497,6 +4640,23 @@ const assist = sanity.definePlugin(config => {
4497
4640
  input: SafeValueInput
4498
4641
  }
4499
4642
  }
4643
+ })(), sanity.definePlugin({
4644
+ name: "".concat(packageName, "/generate-caption"),
4645
+ form: {
4646
+ components: {
4647
+ input: props => {
4648
+ const {
4649
+ schemaType
4650
+ } = props;
4651
+ if (isImage(schemaType)) {
4652
+ return /* @__PURE__ */jsxRuntime.jsx(ImageContextProvider, {
4653
+ ...props
4654
+ });
4655
+ }
4656
+ return props.renderDefault(props);
4657
+ }
4658
+ }
4659
+ }
4500
4660
  })()]
4501
4661
  };
4502
4662
  });