@sanity/assist 1.0.0

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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/index.d.ts +52 -0
  4. package/dist/index.esm.js +2341 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +2341 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +98 -0
  9. package/sanity.json +8 -0
  10. package/src/_lib/connector/ConnectFromRegion.tsx +24 -0
  11. package/src/_lib/connector/ConnectToRegion.tsx +22 -0
  12. package/src/_lib/connector/ConnectorRegion.tsx +23 -0
  13. package/src/_lib/connector/ConnectorsProvider.tsx +19 -0
  14. package/src/_lib/connector/ConnectorsStore.ts +122 -0
  15. package/src/_lib/connector/ConnectorsStoreContext.ts +4 -0
  16. package/src/_lib/connector/helpers.ts +5 -0
  17. package/src/_lib/connector/index.ts +9 -0
  18. package/src/_lib/connector/mapConnectorToLine.ts +83 -0
  19. package/src/_lib/connector/types.ts +56 -0
  20. package/src/_lib/connector/useConnectorsStore.ts +13 -0
  21. package/src/_lib/connector/useRegionRects.ts +141 -0
  22. package/src/_lib/fixedListenQuery.ts +101 -0
  23. package/src/_lib/form/DocumentForm.tsx +197 -0
  24. package/src/_lib/form/helpers.ts +31 -0
  25. package/src/_lib/form/index.ts +1 -0
  26. package/src/_lib/randomKey.ts +29 -0
  27. package/src/_lib/useListeningQuery.ts +61 -0
  28. package/src/_lib/usePrevious.ts +9 -0
  29. package/src/assistConnectors/AssistConnectorsOverlay.tsx +132 -0
  30. package/src/assistConnectors/ConnectorPath.tsx +62 -0
  31. package/src/assistConnectors/draw/arrowPath.ts +9 -0
  32. package/src/assistConnectors/draw/connectorPath.ts +142 -0
  33. package/src/assistConnectors/index.ts +1 -0
  34. package/src/assistDocument/AssistDocumentContext.tsx +31 -0
  35. package/src/assistDocument/AssistDocumentContextProvider.tsx +17 -0
  36. package/src/assistDocument/AssistDocumentInput.tsx +46 -0
  37. package/src/assistDocument/RequestRunInstructionProvider.tsx +50 -0
  38. package/src/assistDocument/components/AssistDocumentForm.tsx +188 -0
  39. package/src/assistDocument/components/FieldRefPreview.tsx +27 -0
  40. package/src/assistDocument/components/InstructionsArrayField.tsx +8 -0
  41. package/src/assistDocument/components/InstructionsArrayInput.tsx +26 -0
  42. package/src/assistDocument/components/SelectedFieldContext.tsx +10 -0
  43. package/src/assistDocument/components/generic/HiddenFieldTitle.tsx +5 -0
  44. package/src/assistDocument/components/helpers.ts +21 -0
  45. package/src/assistDocument/components/instruction/BackToInstructionsLink.tsx +31 -0
  46. package/src/assistDocument/components/instruction/FieldRefInput.tsx +33 -0
  47. package/src/assistDocument/components/instruction/InstructionInput.tsx +87 -0
  48. package/src/assistDocument/components/instruction/PromptInput.tsx +52 -0
  49. package/src/assistDocument/components/instruction/appearance/IconInput.tsx +46 -0
  50. package/src/assistDocument/components/instruction/appearance/InstructionVisibility.tsx +37 -0
  51. package/src/assistDocument/hooks/useAssistDocumentContextValue.tsx +68 -0
  52. package/src/assistDocument/hooks/useDocumentState.ts +6 -0
  53. package/src/assistDocument/hooks/useInstructionToaster.tsx +74 -0
  54. package/src/assistDocument/hooks/useStudioAssistDocument.ts +119 -0
  55. package/src/assistDocument/index.ts +1 -0
  56. package/src/assistFormComponents/AssistField.tsx +51 -0
  57. package/src/assistFormComponents/AssistFormBlock.tsx +31 -0
  58. package/src/assistFormComponents/AssistInlineFormBlock.tsx +14 -0
  59. package/src/assistFormComponents/AssistItem.tsx +20 -0
  60. package/src/assistFormComponents/validation/listItem.tsx +63 -0
  61. package/src/assistFormComponents/validation/validationList.tsx +89 -0
  62. package/src/assistInspector/AssistInspector.tsx +379 -0
  63. package/src/assistInspector/FieldAutocomplete.tsx +119 -0
  64. package/src/assistInspector/InstructionTaskHistoryButton.tsx +261 -0
  65. package/src/assistInspector/constants.ts +1 -0
  66. package/src/assistInspector/helpers.ts +125 -0
  67. package/src/assistInspector/index.ts +26 -0
  68. package/src/assistLayout/AiAssistanceConfigContext.tsx +81 -0
  69. package/src/assistLayout/AlphaMigration.tsx +311 -0
  70. package/src/assistLayout/AssistLayout.tsx +38 -0
  71. package/src/assistLayout/RunInstructionProvider.tsx +222 -0
  72. package/src/components/AssistFeatureBadge.tsx +9 -0
  73. package/src/components/Delay.tsx +25 -0
  74. package/src/components/HideReferenceChangedBannerInput.tsx +25 -0
  75. package/src/components/SafeValueInput.tsx +73 -0
  76. package/src/components/TimeAgo.tsx +18 -0
  77. package/src/constants.ts +20 -0
  78. package/src/fieldActions/PrivateIcon.tsx +20 -0
  79. package/src/fieldActions/assistFieldActions.tsx +230 -0
  80. package/src/globals.d.ts +4 -0
  81. package/src/helpers/assistSupported.ts +44 -0
  82. package/src/helpers/ids.ts +19 -0
  83. package/src/helpers/misc.ts +16 -0
  84. package/src/helpers/typeUtils.ts +15 -0
  85. package/src/helpers/useAssistSupported.ts +10 -0
  86. package/src/index.ts +6 -0
  87. package/src/legacy-types.ts +72 -0
  88. package/src/onboarding/FieldActionsOnboarding.tsx +90 -0
  89. package/src/onboarding/FirstAssistedPathProvider.tsx +29 -0
  90. package/src/onboarding/InspectorOnboarding.tsx +46 -0
  91. package/src/onboarding/onboardingStore.ts +33 -0
  92. package/src/plugin.tsx +80 -0
  93. package/src/presence/AiFieldPresence.tsx +28 -0
  94. package/src/presence/AssistAvatar.tsx +96 -0
  95. package/src/presence/AssistDocumentPresence.tsx +58 -0
  96. package/src/presence/useAssistPresence.ts +61 -0
  97. package/src/schemas/assistDocumentSchema.tsx +450 -0
  98. package/src/schemas/contextDocumentSchema.tsx +56 -0
  99. package/src/schemas/index.ts +25 -0
  100. package/src/schemas/serialize/SchemTypeTool.tsx +102 -0
  101. package/src/schemas/serialize/schemaUtils.ts +37 -0
  102. package/src/schemas/serialize/serializeSchema.test.ts +382 -0
  103. package/src/schemas/serialize/serializeSchema.ts +162 -0
  104. package/src/schemas/serializedSchemaTypeSchema.ts +59 -0
  105. package/src/schemas/typeDefExtensions.ts +30 -0
  106. package/src/types.ts +167 -0
  107. package/src/useApiClient.ts +140 -0
  108. package/src/vite.config.ts +9 -0
  109. package/v2-incompatible.js +11 -0
package/src/plugin.tsx ADDED
@@ -0,0 +1,80 @@
1
+ import {definePlugin, ObjectSchemaType} from 'sanity'
2
+ import {assistInspector} from './assistInspector'
3
+ import {AssistFieldWrapper} from './assistFormComponents/AssistField'
4
+ import {AssistLayout} from './assistLayout/AssistLayout'
5
+ import {AssistFormBlock} from './assistFormComponents/AssistFormBlock'
6
+ import {AssistItem} from './assistFormComponents/AssistItem'
7
+ import {SanityClient} from '@sanity/client'
8
+ import {SafeValueInput} from './components/SafeValueInput'
9
+ import {schemaTypes} from './schemas'
10
+ import {AssistInlineFormBlock} from './assistFormComponents/AssistInlineFormBlock'
11
+ import {assistFieldActions} from './fieldActions/assistFieldActions'
12
+ import {packageName} from './constants'
13
+ import {AssistDocumentInputWrapper} from './assistDocument/AssistDocumentInput'
14
+ import {createAssistDocumentPresence} from './presence/AssistDocumentPresence'
15
+ import {isSchemaAssistEnabled} from './helpers/assistSupported'
16
+
17
+ export interface AssistPluginConfig {
18
+ /**
19
+ * Set this to false to disable model migration from the alpha version of this plugin
20
+ */
21
+ alphaMigration?: boolean
22
+
23
+ /**
24
+ * @internal
25
+ */
26
+ __customApiClient?: (defaultClient: SanityClient) => SanityClient
27
+ }
28
+
29
+ export const assist = definePlugin<AssistPluginConfig | void>((config) => {
30
+ const configWithDefaults = config ?? {}
31
+ return {
32
+ name: packageName,
33
+
34
+ schema: {
35
+ types: schemaTypes,
36
+ },
37
+
38
+ document: {
39
+ inspectors: (prev, context) => {
40
+ const docSchema = context.schema.get(context.documentType)
41
+ if (docSchema && isSchemaAssistEnabled(docSchema)) {
42
+ return [...prev, assistInspector]
43
+ }
44
+ return prev
45
+ },
46
+ unstable_fieldActions: (prev) => {
47
+ return [...prev, assistFieldActions]
48
+ },
49
+ unstable_languageFilter: (prev, {documentId, schema, schemaType}) => {
50
+ const docSchema = schema.get(schemaType) as ObjectSchemaType
51
+ return [...prev, createAssistDocumentPresence(documentId, docSchema)]
52
+ },
53
+ },
54
+
55
+ studio: {
56
+ components: {
57
+ layout: function Layout(props) {
58
+ return <AssistLayout {...props} config={configWithDefaults} />
59
+ },
60
+ },
61
+ },
62
+
63
+ form: {
64
+ components: {
65
+ input: AssistDocumentInputWrapper,
66
+ field: AssistFieldWrapper,
67
+ item: AssistItem,
68
+ block: AssistFormBlock,
69
+ inlineBlock: AssistInlineFormBlock,
70
+ },
71
+ },
72
+
73
+ plugins: [
74
+ definePlugin({
75
+ name: `${packageName}/safe-value-input`,
76
+ form: {components: {input: SafeValueInput}},
77
+ })(),
78
+ ],
79
+ }
80
+ })
@@ -0,0 +1,28 @@
1
+ // eslint-disable-next-line react/no-unused-prop-types
2
+ import {FormNodePresence} from 'sanity'
3
+ import {Card, Flex, Text, Tooltip} from '@sanity/ui'
4
+ import {Delay} from '../components/Delay'
5
+ import {AssistAvatar} from './AssistAvatar'
6
+
7
+ export function AiFieldPresence(props: {presence: FormNodePresence}) {
8
+ return (
9
+ <Card style={{position: 'relative', background: 'transparent'}} contentEditable={false}>
10
+ <Tooltip
11
+ placement="left"
12
+ content={
13
+ <Card padding={3} border>
14
+ <Flex align="center">
15
+ <Text size={1}>Running instruction...</Text>
16
+ </Flex>
17
+ </Card>
18
+ }
19
+ >
20
+ <div>
21
+ <Delay durationMs={200} ms={250}>
22
+ <AssistAvatar state="active" />
23
+ </Delay>
24
+ </div>
25
+ </Tooltip>
26
+ </Card>
27
+ )
28
+ }
@@ -0,0 +1,96 @@
1
+ import {purple} from '@sanity/color'
2
+ import {SparklesIcon} from '@sanity/icons'
3
+ import {Text} from '@sanity/ui'
4
+ import {CSSProperties, useMemo} from 'react'
5
+ import {useColorSchemeValue} from 'sanity'
6
+ import styled, {keyframes} from 'styled-components'
7
+
8
+ const Root = styled.span`
9
+ display: block;
10
+ width: 25px;
11
+ height: 25px;
12
+ position: relative;
13
+ `
14
+
15
+ const dash = keyframes`
16
+ 0% {
17
+ transform: rotate(0);
18
+ }
19
+ 100% {
20
+ transform: rotate(43deg);
21
+ }
22
+ `
23
+
24
+ const Outline = styled.svg`
25
+ display: block;
26
+ position: absolute;
27
+ top: 0;
28
+ left: 0;
29
+
30
+ & > circle {
31
+ stroke: var(--ai-avatar-stroke-color);
32
+ stroke-width: 1.5px;
33
+ stroke-linecap: round;
34
+ transform-origin: center;
35
+ animation: ${dash} 500ms ease-in-out infinite;
36
+ transition: stroke-dasharray 200ms ease-in-out;
37
+
38
+ stroke-dasharray: 2.34px 0;
39
+
40
+ [data-state='active'] > & {
41
+ stroke-dasharray: 2px 2.34px;
42
+ }
43
+ }
44
+ `
45
+
46
+ const IconDisc = styled.span`
47
+ background: var(--ai-avatar-disc-color);
48
+ color: white;
49
+ width: 21px;
50
+ height: 21px;
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ border-radius: 10.5px;
55
+ position: absolute;
56
+ top: 2px;
57
+ left: 2px;
58
+ `
59
+
60
+ export function AssistAvatar(props: {state?: 'present' | 'active'}) {
61
+ const {state = 'present'} = props
62
+ const scheme = useColorSchemeValue()
63
+
64
+ const style = useMemo(() => {
65
+ if (scheme === 'dark') {
66
+ return {
67
+ [`--ai-avatar-stroke-color`]: purple[400].hex,
68
+ [`--ai-avatar-disc-color`]: purple[600].hex,
69
+ } as CSSProperties
70
+ }
71
+
72
+ return {
73
+ [`--ai-avatar-stroke-color`]: purple[500].hex,
74
+ [`--ai-avatar-disc-color`]: purple[600].hex,
75
+ } as CSSProperties
76
+ }, [scheme])
77
+
78
+ return (
79
+ <Root data-state={state} style={style}>
80
+ <Outline
81
+ width="25"
82
+ height="25"
83
+ viewBox="0 0 25 25"
84
+ fill="none"
85
+ xmlns="http://www.w3.org/2000/svg"
86
+ >
87
+ <circle cx="12.5" cy="12.5" r="11.75" />
88
+ </Outline>
89
+ <IconDisc>
90
+ <Text as="span" size={0} style={{color: 'inherit'}}>
91
+ <SparklesIcon />
92
+ </Text>
93
+ </IconDisc>
94
+ </Root>
95
+ )
96
+ }
@@ -0,0 +1,58 @@
1
+ import {ObjectSchemaType} from 'sanity'
2
+ import {useMemo} from 'react'
3
+ import {useAssistDocumentContextValue} from '../assistDocument/hooks/useAssistDocumentContextValue'
4
+ import {aiPresence} from './useAssistPresence'
5
+ import {documentRootKey, fieldPresenceTypeName} from '../types'
6
+ import {Card, Flex} from '@sanity/ui'
7
+ import {AiFieldPresence} from './AiFieldPresence'
8
+
9
+ export function createAssistDocumentPresence(
10
+ documentId: string | undefined,
11
+ schemaType: ObjectSchemaType
12
+ ) {
13
+ return function AssistDocumentPresenceWrapper() {
14
+ return documentId ? (
15
+ <AssistDocumentPresence documentId={documentId} schemaType={schemaType} />
16
+ ) : null
17
+ }
18
+ }
19
+
20
+ function AssistDocumentPresence(props: {documentId: string; schemaType: ObjectSchemaType}) {
21
+ const {assistDocument} = useAssistDocumentContextValue(
22
+ props.documentId,
23
+ props.schemaType as ObjectSchemaType
24
+ )
25
+ const anyPresence = useMemo(() => {
26
+ const anyPresence = assistDocument?.tasks
27
+ ?.filter((run) => !run.ended && !run.reason)
28
+ ?.flatMap((run) => run.presence ?? [])
29
+ .find((f) => f.started && new Date().getTime() - new Date(f.started).getTime() < 30000)
30
+ if (anyPresence) {
31
+ return aiPresence(anyPresence, [])
32
+ }
33
+ const anyRun = assistDocument?.tasks
34
+ ?.filter((run) => !run.ended && !run.reason)
35
+ ?.find((f) => f.started && new Date().getTime() - new Date(f.started).getTime() < 30000)
36
+ return anyRun
37
+ ? aiPresence(
38
+ {
39
+ started: anyRun.started,
40
+ path: documentRootKey,
41
+ _key: anyRun._key,
42
+ _type: fieldPresenceTypeName,
43
+ },
44
+ []
45
+ )
46
+ : undefined
47
+ }, [assistDocument?.tasks])
48
+
49
+ return (
50
+ <Card>
51
+ <Flex flex={1} justify="flex-end">
52
+ <Flex gap={2} align={'center'}>
53
+ {anyPresence && <AiFieldPresence presence={anyPresence} />}
54
+ </Flex>
55
+ </Flex>
56
+ </Card>
57
+ )
58
+ }
@@ -0,0 +1,61 @@
1
+ import {useMemo} from 'react'
2
+ import {FormNodePresence, isKeySegment, Path, stringToPath} from 'sanity'
3
+ import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
4
+ import {AiPresence} from '../types'
5
+ import {maxHistoryVisibilityMs, pluginTitle} from '../constants'
6
+
7
+ const NO_PRESENCE: FormNodePresence[] = []
8
+
9
+ export function useAssistPresence(path: Path, showFocusWithin?: boolean): FormNodePresence[] {
10
+ const context = useAssistDocumentContext()
11
+ const assistDocument = context && 'assistDocument' in context ? context.assistDocument : undefined
12
+ const tasks = assistDocument?.tasks
13
+
14
+ return useMemo(() => {
15
+ const activePresence = tasks
16
+ ?.filter((task) => !task.ended)
17
+ ?.flatMap((task) => task.presence ?? [])
18
+ ?.filter(
19
+ (p) =>
20
+ p.started && new Date().getTime() - new Date(p.started).getTime() < maxHistoryVisibilityMs
21
+ )
22
+ .filter((presence) => {
23
+ if (!presence.path || !path.length) {
24
+ return false
25
+ }
26
+ const statusPath = stringToPath(presence.path)
27
+
28
+ if (!showFocusWithin && statusPath.length !== path.length) {
29
+ return false
30
+ }
31
+
32
+ return path.every((pathSegment, i) => {
33
+ const statusSegment = statusPath[i]
34
+ if (typeof pathSegment === 'string') {
35
+ return pathSegment === statusSegment
36
+ }
37
+ if (isKeySegment(pathSegment) && isKeySegment(statusSegment)) {
38
+ return pathSegment._key === statusSegment._key
39
+ }
40
+ return false
41
+ })
42
+ })
43
+ if (!activePresence?.length) {
44
+ return NO_PRESENCE
45
+ }
46
+
47
+ return activePresence.map((status) => aiPresence(status, path))
48
+ }, [showFocusWithin, tasks, path])
49
+ }
50
+
51
+ export function aiPresence(presence: AiPresence, path: Path, title?: string): FormNodePresence {
52
+ return {
53
+ user: {
54
+ id: `sanity-assistant_${presence._key}`,
55
+ displayName: pluginTitle,
56
+ },
57
+ path: path,
58
+ sessionId: 'not-available',
59
+ lastActiveAt: presence?.started ?? new Date().toISOString(),
60
+ }
61
+ }