@sanity/embeddings-index-ui 3.0.0 → 4.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.
- package/LICENSE +1 -1
- package/README.md +31 -35
- package/dist/index.d.ts +49 -60
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +766 -2198
- package/dist/index.js.map +1 -1
- package/package.json +36 -69
- package/dist/index.d.mts +0 -74
- package/dist/index.mjs +0 -2353
- package/dist/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/api/embeddingsApi.ts +0 -68
- package/src/api/embeddingsApiHooks.ts +0 -17
- package/src/api/isEnabled.tsx +0 -65
- package/src/embeddingsIndexDashboard/EmbeddingsIndexTool.tsx +0 -168
- package/src/embeddingsIndexDashboard/IndexEditor.tsx +0 -185
- package/src/embeddingsIndexDashboard/IndexFormInput.tsx +0 -86
- package/src/embeddingsIndexDashboard/IndexInfo.tsx +0 -117
- package/src/embeddingsIndexDashboard/IndexList.tsx +0 -87
- package/src/embeddingsIndexDashboard/QueryIndex.tsx +0 -32
- package/src/embeddingsIndexDashboard/dashboardPlugin.ts +0 -16
- package/src/embeddingsIndexDashboard/hooks.ts +0 -37
- package/src/index.ts +0 -10
- package/src/preview/DocumentPreview.tsx +0 -139
- package/src/referenceInput/SemanticSearchAutocomplete.tsx +0 -213
- package/src/referenceInput/SemanticSearchReferenceInput.tsx +0 -169
- package/src/referenceInput/referencePlugin.tsx +0 -44
- package/src/referenceInput/types.ts +0 -0
- package/src/schemas/typeDefExtensions.ts +0 -27
- package/src/utils/id.ts +0 -3
- package/src/utils/types.ts +0 -11
- package/v2-incompatible.js +0 -11
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {useMemo} from 'react'
|
|
2
|
-
import {SanityClient, useClient} from 'sanity'
|
|
3
|
-
|
|
4
|
-
export function useApiClient(): SanityClient {
|
|
5
|
-
const client = useClient({apiVersion: 'vX'})
|
|
6
|
-
return useMemo(() => {
|
|
7
|
-
const customHost = localStorage.getItem('embeddings-index-host')
|
|
8
|
-
if (customHost) {
|
|
9
|
-
return client.withConfig({
|
|
10
|
-
apiHost: customHost,
|
|
11
|
-
useProjectHostname: false,
|
|
12
|
-
withCredentials: false,
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
return client
|
|
16
|
-
}, [client])
|
|
17
|
-
}
|
package/src/api/isEnabled.tsx
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import {Card, Text} from '@sanity/ui'
|
|
2
|
-
import {createContext, PropsWithChildren, useContext, useEffect, useState} from 'react'
|
|
3
|
-
import {useProjectId} from 'sanity'
|
|
4
|
-
|
|
5
|
-
import {useApiClient} from './embeddingsApiHooks'
|
|
6
|
-
|
|
7
|
-
export type FeatureStatus = 'enabled' | 'disabled' | 'loading' | 'error'
|
|
8
|
-
export const FeatureEnabledContext = createContext<FeatureStatus>('loading')
|
|
9
|
-
|
|
10
|
-
export function useIsFeatureEnabled() {
|
|
11
|
-
const client = useApiClient()
|
|
12
|
-
const [status, setStatus] = useState<FeatureStatus>('loading')
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
client
|
|
16
|
-
.request<{enabled: boolean}>({
|
|
17
|
-
method: 'GET',
|
|
18
|
-
url: `/embeddings-index/status`,
|
|
19
|
-
})
|
|
20
|
-
.then((response) => {
|
|
21
|
-
setStatus(response.enabled ? 'enabled' : 'disabled')
|
|
22
|
-
})
|
|
23
|
-
.catch((err) => {
|
|
24
|
-
console.error(err)
|
|
25
|
-
setStatus('error')
|
|
26
|
-
})
|
|
27
|
-
}, [client])
|
|
28
|
-
|
|
29
|
-
return status
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function FeatureEnabledProvider(props: PropsWithChildren) {
|
|
33
|
-
const status = useIsFeatureEnabled()
|
|
34
|
-
return (
|
|
35
|
-
<FeatureEnabledContext.Provider value={status}>{props.children}</FeatureEnabledContext.Provider>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function useIsFeatureEnabledContext(): FeatureStatus {
|
|
40
|
-
return useContext(FeatureEnabledContext)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function FeatureDisabledNotice(props: {urlSuffix?: string}) {
|
|
44
|
-
const projectId = useProjectId()
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<Card tone="primary" border padding={4}>
|
|
48
|
-
<Text size={1}>
|
|
49
|
-
💎 Unlock semantic search with the Embeddings Index API — available on Team, Business, and
|
|
50
|
-
Enterprise plans.{' '}
|
|
51
|
-
<a href={`https://www.sanity.io/manage/project/${projectId}/plan${props.urlSuffix ?? ''}`}>
|
|
52
|
-
Upgrade now →
|
|
53
|
-
</a>
|
|
54
|
-
</Text>
|
|
55
|
-
</Card>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function FeatureError() {
|
|
60
|
-
return (
|
|
61
|
-
<Card padding={4} border tone="critical">
|
|
62
|
-
An error occurred. See console for details.
|
|
63
|
-
</Card>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import {AddIcon, UndoIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Button, Card, Flex, Heading, Spinner, Stack} from '@sanity/ui'
|
|
3
|
-
import {useCallback, useEffect, useState} from 'react'
|
|
4
|
-
|
|
5
|
-
import {deleteIndex, getIndexes, IndexState, NamedIndex} from '../api/embeddingsApi'
|
|
6
|
-
import {useApiClient} from '../api/embeddingsApiHooks'
|
|
7
|
-
import {FeatureDisabledNotice, FeatureError, useIsFeatureEnabled} from '../api/isEnabled'
|
|
8
|
-
import {EditIndexDialog} from './IndexEditor'
|
|
9
|
-
import {IndexInfo} from './IndexInfo'
|
|
10
|
-
import {IndexList} from './IndexList'
|
|
11
|
-
|
|
12
|
-
export function EmbeddingsIndexTool() {
|
|
13
|
-
const featureState = useIsFeatureEnabled()
|
|
14
|
-
return (
|
|
15
|
-
<Card flex={1}>
|
|
16
|
-
<Flex justify="center" flex={1}>
|
|
17
|
-
{featureState === 'error' ? (
|
|
18
|
-
<Box padding={4}>
|
|
19
|
-
<FeatureError />
|
|
20
|
-
</Box>
|
|
21
|
-
) : null}
|
|
22
|
-
|
|
23
|
-
{featureState === 'disabled' ? (
|
|
24
|
-
<Box padding={4}>
|
|
25
|
-
<FeatureDisabledNotice urlSuffix="?ref=embeddings-tab" />
|
|
26
|
-
</Box>
|
|
27
|
-
) : null}
|
|
28
|
-
|
|
29
|
-
{featureState === 'loading' ? (
|
|
30
|
-
<Box padding={6}>
|
|
31
|
-
<Spinner size={4} />
|
|
32
|
-
</Box>
|
|
33
|
-
) : null}
|
|
34
|
-
|
|
35
|
-
{featureState === 'enabled' ? (
|
|
36
|
-
<Card flex={1} style={{maxWidth: 1200}} padding={5}>
|
|
37
|
-
<Indexes />
|
|
38
|
-
</Card>
|
|
39
|
-
) : null}
|
|
40
|
-
</Flex>
|
|
41
|
-
</Card>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const NO_INDEXES: IndexState[] = []
|
|
46
|
-
|
|
47
|
-
function Indexes() {
|
|
48
|
-
const client = useApiClient()
|
|
49
|
-
const [indexes, setIndexes] = useState<IndexState[]>(NO_INDEXES)
|
|
50
|
-
const [loading, setLoading] = useState(false)
|
|
51
|
-
const [error, setError] = useState(false)
|
|
52
|
-
const [createIndexOpen, setCreateIndexOpen] = useState(false)
|
|
53
|
-
const [selectedIndex, setSelectedIndex] = useState<IndexState | undefined>(undefined)
|
|
54
|
-
|
|
55
|
-
const onCreateIndexClose = useCallback(() => setCreateIndexOpen(false), [])
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
setSelectedIndex(indexes.find((i) => i.indexName === selectedIndex?.indexName))
|
|
59
|
-
}, [indexes, selectedIndex])
|
|
60
|
-
|
|
61
|
-
const updateIndexes = useCallback(() => {
|
|
62
|
-
setLoading(true)
|
|
63
|
-
setError(false)
|
|
64
|
-
getIndexes(client)
|
|
65
|
-
.then((response: IndexState[]) => {
|
|
66
|
-
setLoading(false)
|
|
67
|
-
setIndexes(response)
|
|
68
|
-
})
|
|
69
|
-
.catch((e) => {
|
|
70
|
-
// eslint-disable-next-line no-unused-expressions
|
|
71
|
-
console.error(e)
|
|
72
|
-
setError(true)
|
|
73
|
-
})
|
|
74
|
-
.finally(() => {
|
|
75
|
-
setLoading(false)
|
|
76
|
-
})
|
|
77
|
-
}, [client])
|
|
78
|
-
|
|
79
|
-
const deleteNamedIndex = useCallback(
|
|
80
|
-
(index: NamedIndex) => {
|
|
81
|
-
if (
|
|
82
|
-
// eslint-disable-next-line no-alert
|
|
83
|
-
!confirm(`Are you sure you want to delete ${index.indexName} for dataset ${index.dataset}?`)
|
|
84
|
-
) {
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
setLoading(true)
|
|
88
|
-
setError(false)
|
|
89
|
-
deleteIndex(index.indexName, client)
|
|
90
|
-
.then(() => {
|
|
91
|
-
setTimeout(() => updateIndexes())
|
|
92
|
-
})
|
|
93
|
-
.catch((e) => {
|
|
94
|
-
// eslint-disable-next-line no-unused-expressions
|
|
95
|
-
console.error(e)
|
|
96
|
-
setError(true)
|
|
97
|
-
})
|
|
98
|
-
.finally(() => {
|
|
99
|
-
setLoading(false)
|
|
100
|
-
})
|
|
101
|
-
},
|
|
102
|
-
[client, updateIndexes],
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
const onSelectIndex = useCallback(
|
|
106
|
-
(index: IndexState) => {
|
|
107
|
-
setSelectedIndex(index)
|
|
108
|
-
updateIndexes()
|
|
109
|
-
},
|
|
110
|
-
[setSelectedIndex, updateIndexes],
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
updateIndexes()
|
|
115
|
-
}, [updateIndexes])
|
|
116
|
-
|
|
117
|
-
const openCreate = useCallback(() => setCreateIndexOpen(true), [])
|
|
118
|
-
const onSubmit = useCallback(
|
|
119
|
-
(index: IndexState) => {
|
|
120
|
-
setIndexes((current) => [...current, index])
|
|
121
|
-
setSelectedIndex(index)
|
|
122
|
-
updateIndexes()
|
|
123
|
-
},
|
|
124
|
-
[updateIndexes],
|
|
125
|
-
)
|
|
126
|
-
return (
|
|
127
|
-
<Stack space={4}>
|
|
128
|
-
<Flex gap={2} align="center" style={{height: 30}}>
|
|
129
|
-
<Box flex={1}>
|
|
130
|
-
<Heading size={1}>Embeddings indexes</Heading>
|
|
131
|
-
</Box>
|
|
132
|
-
<Box style={{justifySelf: 'flex-end'}}>
|
|
133
|
-
<Button
|
|
134
|
-
icon={AddIcon}
|
|
135
|
-
text={'New index'}
|
|
136
|
-
tone="default"
|
|
137
|
-
mode="ghost"
|
|
138
|
-
onClick={openCreate}
|
|
139
|
-
/>
|
|
140
|
-
</Box>
|
|
141
|
-
<Button
|
|
142
|
-
size={1}
|
|
143
|
-
icon={loading ? <Spinner /> : UndoIcon}
|
|
144
|
-
title={'Refresh index list'}
|
|
145
|
-
tone="default"
|
|
146
|
-
mode="bleed"
|
|
147
|
-
onClick={updateIndexes}
|
|
148
|
-
disabled={loading}
|
|
149
|
-
/>
|
|
150
|
-
</Flex>
|
|
151
|
-
{error ? (
|
|
152
|
-
<Card tone="critical" padding={2} border>
|
|
153
|
-
An error occurred. See console for details.
|
|
154
|
-
</Card>
|
|
155
|
-
) : null}
|
|
156
|
-
<IndexList
|
|
157
|
-
loading={loading}
|
|
158
|
-
indexes={indexes}
|
|
159
|
-
selectedIndex={selectedIndex}
|
|
160
|
-
onIndexSelected={onSelectIndex}
|
|
161
|
-
/>
|
|
162
|
-
{selectedIndex && (
|
|
163
|
-
<IndexInfo selectedIndex={selectedIndex} onDeleteIndex={deleteNamedIndex} />
|
|
164
|
-
)}
|
|
165
|
-
<EditIndexDialog open={createIndexOpen} onClose={onCreateIndexClose} onSubmit={onSubmit} />
|
|
166
|
-
</Stack>
|
|
167
|
-
)
|
|
168
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import {AddIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Button, Card, Dialog, Spinner, Stack, Text} from '@sanity/ui'
|
|
3
|
-
import {FormEvent, useCallback, useEffect, useId, useRef, useState} from 'react'
|
|
4
|
-
import {useSchema} from 'sanity'
|
|
5
|
-
|
|
6
|
-
import {IndexState, NamedIndex} from '../api/embeddingsApi'
|
|
7
|
-
import {useApiClient} from '../api/embeddingsApiHooks'
|
|
8
|
-
import {useDefaultIndex} from './hooks'
|
|
9
|
-
import {IndexFormInput} from './IndexFormInput'
|
|
10
|
-
|
|
11
|
-
export function EditIndexDialog(props: {
|
|
12
|
-
open: boolean
|
|
13
|
-
onClose: () => void
|
|
14
|
-
onSubmit: (index: IndexState) => void
|
|
15
|
-
}) {
|
|
16
|
-
const {open, onClose, onSubmit} = props
|
|
17
|
-
const id = useId()
|
|
18
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (!open) {
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
setTimeout(() => ref.current?.querySelector('input')?.focus())
|
|
25
|
-
}, [ref, open])
|
|
26
|
-
|
|
27
|
-
const handleSubmit = useCallback(
|
|
28
|
-
(index: IndexState) => {
|
|
29
|
-
onSubmit(index)
|
|
30
|
-
onClose()
|
|
31
|
-
},
|
|
32
|
-
[onSubmit, onClose],
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
return open ? (
|
|
36
|
-
<Dialog id={id} width={1} ref={ref} onClose={onClose} header="Create embeddings index">
|
|
37
|
-
<Stack padding={4} space={5}>
|
|
38
|
-
<IndexEditor readOnly={false} onSubmit={handleSubmit} />
|
|
39
|
-
</Stack>
|
|
40
|
-
</Dialog>
|
|
41
|
-
) : null
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function IndexEditor(props: {
|
|
45
|
-
index?: Partial<NamedIndex>
|
|
46
|
-
readOnly: boolean
|
|
47
|
-
onSubmit?: (index: IndexState) => void
|
|
48
|
-
}) {
|
|
49
|
-
const {readOnly, index: selectedIndex, onSubmit} = props
|
|
50
|
-
const client = useApiClient()
|
|
51
|
-
const schema = useSchema()
|
|
52
|
-
const defaultIndex = useDefaultIndex(schema, client.config().dataset ?? '')
|
|
53
|
-
const [errors, setErrors] = useState<string[] | undefined>()
|
|
54
|
-
const [loading, setLoading] = useState<boolean>()
|
|
55
|
-
const [index, setIndex] = useState<Partial<NamedIndex>>(() => ({
|
|
56
|
-
...defaultIndex,
|
|
57
|
-
...selectedIndex,
|
|
58
|
-
}))
|
|
59
|
-
|
|
60
|
-
useEffect(() => setIndex(selectedIndex ?? {...defaultIndex}), [selectedIndex, defaultIndex])
|
|
61
|
-
|
|
62
|
-
const handleSubmit = useCallback(
|
|
63
|
-
(e: FormEvent) => {
|
|
64
|
-
e.preventDefault()
|
|
65
|
-
if (readOnly) {
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const validationErrors: string[] = []
|
|
70
|
-
|
|
71
|
-
if (!index.indexName) {
|
|
72
|
-
validationErrors.push('Index name is required')
|
|
73
|
-
} else if (!index.indexName.match(/^[a-zA-Z0-9-_]+$/g)) {
|
|
74
|
-
validationErrors.push('Index name can only contain the letters a-z, numbers - and _')
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!index.dataset) {
|
|
78
|
-
validationErrors.push('Dataset is required')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!index.filter) {
|
|
82
|
-
validationErrors.push('Filter is required')
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!index.projection) {
|
|
86
|
-
validationErrors.push('Projection is required')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (validationErrors.length) {
|
|
90
|
-
setErrors(validationErrors)
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const {projectId} = client.config()
|
|
95
|
-
setLoading(true)
|
|
96
|
-
client
|
|
97
|
-
.request({
|
|
98
|
-
method: 'POST',
|
|
99
|
-
url: `/embeddings-index/${index.dataset}?projectId=${projectId}`,
|
|
100
|
-
body: {
|
|
101
|
-
indexName: index.indexName,
|
|
102
|
-
projection: index.projection,
|
|
103
|
-
filter: index.filter,
|
|
104
|
-
},
|
|
105
|
-
})
|
|
106
|
-
.then((response: {index: IndexState}) => {
|
|
107
|
-
if (onSubmit) {
|
|
108
|
-
onSubmit(response.index)
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
112
|
-
.catch((err: any) => {
|
|
113
|
-
console.error(err)
|
|
114
|
-
setErrors([err.message])
|
|
115
|
-
})
|
|
116
|
-
.finally(() => {
|
|
117
|
-
setLoading(false)
|
|
118
|
-
})
|
|
119
|
-
},
|
|
120
|
-
[index, readOnly, onSubmit, client],
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<form onSubmit={handleSubmit}>
|
|
125
|
-
<Stack space={4}>
|
|
126
|
-
{errors?.length ? (
|
|
127
|
-
<Card tone="critical" border padding={2}>
|
|
128
|
-
<Text>
|
|
129
|
-
<ul style={{marginLeft: -10}}>
|
|
130
|
-
{/* eslint-disable-next-line react/no-array-index-key */}
|
|
131
|
-
{errors?.map((error, i) => <li key={`${error}-${i}`}>{error}</li>)}
|
|
132
|
-
</ul>
|
|
133
|
-
</Text>
|
|
134
|
-
</Card>
|
|
135
|
-
) : null}
|
|
136
|
-
<IndexFormInput
|
|
137
|
-
label="Index name"
|
|
138
|
-
placeholder={'Name of index without spaces...'}
|
|
139
|
-
index={index}
|
|
140
|
-
prop="indexName"
|
|
141
|
-
onChange={setIndex}
|
|
142
|
-
readOnly={readOnly}
|
|
143
|
-
/>
|
|
144
|
-
<IndexFormInput label="Dataset" index={index} prop="dataset" onChange={setIndex} readOnly />
|
|
145
|
-
<IndexFormInput
|
|
146
|
-
label="Filter"
|
|
147
|
-
description="Must be a valid GROQ filter"
|
|
148
|
-
placeholder={defaultIndex.filter}
|
|
149
|
-
index={index}
|
|
150
|
-
prop="filter"
|
|
151
|
-
onChange={setIndex}
|
|
152
|
-
readOnly={readOnly}
|
|
153
|
-
type="textarea"
|
|
154
|
-
/>
|
|
155
|
-
<IndexFormInput
|
|
156
|
-
label="Projection"
|
|
157
|
-
description="Must be a valid GROQ projection, starting { and ending with }"
|
|
158
|
-
placeholder={defaultIndex.projection}
|
|
159
|
-
index={index}
|
|
160
|
-
prop="projection"
|
|
161
|
-
onChange={setIndex}
|
|
162
|
-
readOnly={readOnly}
|
|
163
|
-
type="textarea"
|
|
164
|
-
/>
|
|
165
|
-
{onSubmit && (
|
|
166
|
-
<Button
|
|
167
|
-
type="submit"
|
|
168
|
-
text="Create index"
|
|
169
|
-
icon={
|
|
170
|
-
loading ? (
|
|
171
|
-
<Box style={{marginTop: 5}}>
|
|
172
|
-
<Spinner />
|
|
173
|
-
</Box>
|
|
174
|
-
) : (
|
|
175
|
-
AddIcon
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
disabled={readOnly || loading}
|
|
179
|
-
tone="primary"
|
|
180
|
-
/>
|
|
181
|
-
)}
|
|
182
|
-
</Stack>
|
|
183
|
-
</form>
|
|
184
|
-
)
|
|
185
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import {Box, Label, Stack, Text, TextArea, TextInput} from '@sanity/ui'
|
|
2
|
-
import {Dispatch, FormEvent, SetStateAction, useCallback, useId} from 'react'
|
|
3
|
-
|
|
4
|
-
import {NamedIndex} from '../api/embeddingsApi'
|
|
5
|
-
|
|
6
|
-
export interface IndexFormInputProps {
|
|
7
|
-
index: Partial<NamedIndex>
|
|
8
|
-
prop: keyof NamedIndex
|
|
9
|
-
label: string
|
|
10
|
-
description?: string
|
|
11
|
-
onChange: Dispatch<SetStateAction<Partial<NamedIndex>>>
|
|
12
|
-
readOnly: boolean
|
|
13
|
-
placeholder?: string
|
|
14
|
-
type?: 'text' | 'textarea'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function IndexFormInput(props: IndexFormInputProps) {
|
|
18
|
-
const {label, description, index, prop, onChange, readOnly, placeholder, type} = props
|
|
19
|
-
const handleChange = useCallback(
|
|
20
|
-
(propValue: string) => onChange((current) => ({...current, [prop]: propValue})),
|
|
21
|
-
[onChange, prop],
|
|
22
|
-
)
|
|
23
|
-
return (
|
|
24
|
-
<FormInput
|
|
25
|
-
label={label}
|
|
26
|
-
description={description}
|
|
27
|
-
onChange={handleChange}
|
|
28
|
-
value={index[prop] ?? ''}
|
|
29
|
-
readOnly={readOnly}
|
|
30
|
-
placeholder={placeholder}
|
|
31
|
-
type={type}
|
|
32
|
-
/>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface FormInputProps {
|
|
37
|
-
label: string
|
|
38
|
-
description?: string
|
|
39
|
-
onChange: (value: string) => void
|
|
40
|
-
value: string
|
|
41
|
-
readOnly: boolean
|
|
42
|
-
placeholder?: string
|
|
43
|
-
type?: 'text' | 'textarea'
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function FormInput(props: FormInputProps) {
|
|
47
|
-
const {label, description, onChange, value, readOnly, placeholder, type = 'text'} = props
|
|
48
|
-
const id = useId()
|
|
49
|
-
const handleChange = useCallback(
|
|
50
|
-
(e: FormEvent<HTMLInputElement | HTMLTextAreaElement>) => onChange(e.currentTarget.value),
|
|
51
|
-
[onChange],
|
|
52
|
-
)
|
|
53
|
-
return (
|
|
54
|
-
<Stack space={3}>
|
|
55
|
-
<Label muted htmlFor={id}>
|
|
56
|
-
<label htmlFor={id}>{label}</label>
|
|
57
|
-
</Label>
|
|
58
|
-
{description && (
|
|
59
|
-
<Box>
|
|
60
|
-
<Text size={1} muted>
|
|
61
|
-
{description}
|
|
62
|
-
</Text>
|
|
63
|
-
</Box>
|
|
64
|
-
)}
|
|
65
|
-
{type === 'text' ? (
|
|
66
|
-
<TextInput
|
|
67
|
-
id={id}
|
|
68
|
-
value={value}
|
|
69
|
-
onChange={handleChange}
|
|
70
|
-
readOnly={readOnly}
|
|
71
|
-
placeholder={placeholder}
|
|
72
|
-
/>
|
|
73
|
-
) : (
|
|
74
|
-
<TextArea
|
|
75
|
-
id={id}
|
|
76
|
-
value={value}
|
|
77
|
-
rows={3}
|
|
78
|
-
onChange={handleChange}
|
|
79
|
-
readOnly={readOnly}
|
|
80
|
-
placeholder={placeholder}
|
|
81
|
-
style={{resize: 'vertical'}}
|
|
82
|
-
/>
|
|
83
|
-
)}
|
|
84
|
-
</Stack>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {EllipsisVerticalIcon, TrashIcon} from '@sanity/icons'
|
|
2
|
-
import {
|
|
3
|
-
Box,
|
|
4
|
-
Button,
|
|
5
|
-
Flex,
|
|
6
|
-
Heading,
|
|
7
|
-
Label,
|
|
8
|
-
Menu,
|
|
9
|
-
MenuButton,
|
|
10
|
-
MenuItem,
|
|
11
|
-
Stack,
|
|
12
|
-
Text,
|
|
13
|
-
} from '@sanity/ui'
|
|
14
|
-
import {useCallback} from 'react'
|
|
15
|
-
|
|
16
|
-
import {IndexState} from '../api/embeddingsApi'
|
|
17
|
-
import {IndexEditor} from './IndexEditor'
|
|
18
|
-
import {QueryIndex} from './QueryIndex'
|
|
19
|
-
|
|
20
|
-
export interface IndexInfoProps {
|
|
21
|
-
selectedIndex: IndexState
|
|
22
|
-
onDeleteIndex: (index: IndexState) => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function IndexInfo({selectedIndex, onDeleteIndex}: IndexInfoProps) {
|
|
26
|
-
const handleDelete = useCallback(
|
|
27
|
-
() => onDeleteIndex(selectedIndex),
|
|
28
|
-
[selectedIndex, onDeleteIndex],
|
|
29
|
-
)
|
|
30
|
-
return (
|
|
31
|
-
<Stack space={4} flex={1}>
|
|
32
|
-
<Flex align="center" flex={1} gap={2}>
|
|
33
|
-
<Box flex={1}>
|
|
34
|
-
<Heading>Index: {selectedIndex?.indexName ?? 'Untitled'}</Heading>
|
|
35
|
-
</Box>
|
|
36
|
-
<Box>
|
|
37
|
-
<MenuButton
|
|
38
|
-
button={
|
|
39
|
-
<Button
|
|
40
|
-
title="Open index actions"
|
|
41
|
-
icon={EllipsisVerticalIcon}
|
|
42
|
-
padding={2}
|
|
43
|
-
mode="ghost"
|
|
44
|
-
/>
|
|
45
|
-
}
|
|
46
|
-
id={`button-${selectedIndex.indexName}`}
|
|
47
|
-
menu={
|
|
48
|
-
<Menu>
|
|
49
|
-
<MenuItem
|
|
50
|
-
text="Delete index"
|
|
51
|
-
icon={TrashIcon}
|
|
52
|
-
tone="critical"
|
|
53
|
-
onClick={handleDelete}
|
|
54
|
-
/>
|
|
55
|
-
</Menu>
|
|
56
|
-
}
|
|
57
|
-
popover={{placement: 'right'}}
|
|
58
|
-
/>
|
|
59
|
-
</Box>
|
|
60
|
-
</Flex>
|
|
61
|
-
|
|
62
|
-
<Flex gap={6}>
|
|
63
|
-
<Stack space={4} flex={1} style={{maxWidth: 600}}>
|
|
64
|
-
<Box>
|
|
65
|
-
<IndexEditor index={selectedIndex} readOnly />
|
|
66
|
-
</Box>
|
|
67
|
-
<IndexStatus selectedIndex={selectedIndex} />
|
|
68
|
-
</Stack>
|
|
69
|
-
|
|
70
|
-
<Stack space={3} flex={1}>
|
|
71
|
-
<Label muted>Query index</Label>
|
|
72
|
-
<QueryIndex indexName={selectedIndex.indexName} key={selectedIndex.indexName} />
|
|
73
|
-
</Stack>
|
|
74
|
-
</Flex>
|
|
75
|
-
</Stack>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function IndexStatus({selectedIndex}: {selectedIndex: IndexState}) {
|
|
80
|
-
return (
|
|
81
|
-
<Stack space={4} flex={1}>
|
|
82
|
-
<Flex gap={2} align="center">
|
|
83
|
-
<Box flex={1}>
|
|
84
|
-
<Label size={1} muted>
|
|
85
|
-
Status
|
|
86
|
-
</Label>
|
|
87
|
-
</Box>
|
|
88
|
-
<Stack space={2}>
|
|
89
|
-
<Text>{selectedIndex.status}</Text>
|
|
90
|
-
</Stack>
|
|
91
|
-
</Flex>
|
|
92
|
-
<Flex gap={5} align="center">
|
|
93
|
-
<Box flex={1}>
|
|
94
|
-
<Label size={1} muted>
|
|
95
|
-
Indexing progress
|
|
96
|
-
</Label>
|
|
97
|
-
</Box>
|
|
98
|
-
<Stack space={2}>
|
|
99
|
-
<Text>
|
|
100
|
-
{selectedIndex.startDocumentCount - selectedIndex.remainingDocumentCount} /{' '}
|
|
101
|
-
{selectedIndex.startDocumentCount}
|
|
102
|
-
</Text>
|
|
103
|
-
</Stack>
|
|
104
|
-
</Flex>
|
|
105
|
-
<Flex gap={5} align="center">
|
|
106
|
-
<Box flex={1}>
|
|
107
|
-
<Label size={1} muted>
|
|
108
|
-
Failed documents
|
|
109
|
-
</Label>
|
|
110
|
-
</Box>
|
|
111
|
-
<Stack space={2}>
|
|
112
|
-
<Text>{selectedIndex.failedDocumentCount}</Text>
|
|
113
|
-
</Stack>
|
|
114
|
-
</Flex>
|
|
115
|
-
</Stack>
|
|
116
|
-
)
|
|
117
|
-
}
|