Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
c67f345d
Unverified
Commit
c67f345d
authored
Aug 30, 2023
by
KVOJJJin
Committed by
GitHub
Aug 30, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix: disable operations of dataset when embedding unavailable (#1055)
Co-authored-by:
jyong
<
jyong@dify.ai
>
parent
8b8e510b
Changes
16
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
401 additions
and
287 deletions
+401
-287
datasets.py
api/controllers/console/datasets/datasets.py
+17
-3
dataset_service.py
api/services/dataset_service.py
+13
-9
index.tsx
...ents/app/configuration/dataset-config/card-item/index.tsx
+1
-1
InfiniteVirtualList.tsx
...tasets/documents/detail/completed/InfiniteVirtualList.tsx
+4
-2
SegmentCard.tsx
...nents/datasets/documents/detail/completed/SegmentCard.tsx
+22
-18
index.tsx
.../components/datasets/documents/detail/completed/index.tsx
+20
-10
index.tsx
web/app/components/datasets/documents/detail/index.tsx
+6
-1
index.tsx
web/app/components/datasets/documents/index.tsx
+12
-12
list.tsx
web/app/components/datasets/documents/list.tsx
+87
-77
index.tsx
web/app/components/datasets/hit-testing/index.tsx
+91
-81
index.tsx
web/app/components/datasets/settings/form/index.tsx
+59
-50
index.module.css
...nts/datasets/settings/index-method-radio/index.module.css
+17
-1
index.tsx
...components/datasets/settings/index-method-radio/index.tsx
+16
-10
index.module.css
...ents/datasets/settings/permissions-radio/index.module.css
+16
-0
index.tsx
.../components/datasets/settings/permissions-radio/index.tsx
+17
-11
i18next-config.ts
web/i18n/i18next-config.ts
+3
-1
No files found.
api/controllers/console/datasets/datasets.py
View file @
c67f345d
...
...
@@ -148,14 +148,28 @@ class DatasetApi(Resource):
dataset
=
DatasetService
.
get_dataset
(
dataset_id_str
)
if
dataset
is
None
:
raise
NotFound
(
"Dataset not found."
)
try
:
DatasetService
.
check_dataset_permission
(
dataset
,
current_user
)
except
services
.
errors
.
account
.
NoPermissionError
as
e
:
raise
Forbidden
(
str
(
e
))
return
marshal
(
dataset
,
dataset_detail_fields
),
200
data
=
marshal
(
dataset
,
dataset_detail_fields
)
# check embedding setting
provider_service
=
ProviderService
()
# get valid model list
valid_model_list
=
provider_service
.
get_valid_model_list
(
current_user
.
current_tenant_id
,
ModelType
.
EMBEDDINGS
.
value
)
model_names
=
[]
for
valid_model
in
valid_model_list
:
model_names
.
append
(
f
"{valid_model['model_name']}:{valid_model['model_provider']['provider_name']}"
)
if
data
[
'indexing_technique'
]
==
'high_quality'
:
item_model
=
f
"{data['embedding_model']}:{data['embedding_model_provider']}"
if
item_model
in
model_names
:
data
[
'embedding_available'
]
=
True
else
:
data
[
'embedding_available'
]
=
False
else
:
data
[
'embedding_available'
]
=
True
return
data
,
200
@
setup_required
@
login_required
...
...
api/services/dataset_service.py
View file @
c67f345d
...
...
@@ -137,28 +137,31 @@ class DatasetService:
@
staticmethod
def
update_dataset
(
dataset_id
,
data
,
user
):
filtered_data
=
{
k
:
v
for
k
,
v
in
data
.
items
()
if
v
is
not
None
or
k
==
'description'
}
dataset
=
DatasetService
.
get_dataset
(
dataset_id
)
DatasetService
.
check_dataset_permission
(
dataset
,
user
)
action
=
None
if
dataset
.
indexing_technique
!=
data
[
'indexing_technique'
]:
# if update indexing_technique
if
data
[
'indexing_technique'
]
==
'economy'
:
deal_dataset_vector_index_task
.
delay
(
dataset_id
,
'remove'
)
action
=
'remove'
filtered_data
[
'embedding_model'
]
=
None
filtered_data
[
'embedding_model_provider'
]
=
None
elif
data
[
'indexing_technique'
]
==
'high_quality'
:
# check embedding model setting
action
=
'add'
# get embedding model setting
try
:
ModelFactory
.
get_embedding_model
(
tenant_id
=
current_user
.
current_tenant_id
,
model_provider_name
=
dataset
.
embedding_model_provider
,
model_name
=
dataset
.
embedding_model
embedding_model
=
ModelFactory
.
get_embedding_model
(
tenant_id
=
current_user
.
current_tenant_id
)
filtered_data
[
'embedding_model'
]
=
embedding_model
.
name
filtered_data
[
'embedding_model_provider'
]
=
embedding_model
.
model_provider
.
provider_name
except
LLMBadRequestError
:
raise
ValueError
(
f
"No Embedding Model available. Please configure a valid provider "
f
"in the Settings -> Model Provider."
)
except
ProviderTokenNotInitError
as
ex
:
raise
ValueError
(
ex
.
description
)
deal_dataset_vector_index_task
.
delay
(
dataset_id
,
'add'
)
filtered_data
=
{
k
:
v
for
k
,
v
in
data
.
items
()
if
v
is
not
None
or
k
==
'description'
}
filtered_data
[
'updated_by'
]
=
user
.
id
filtered_data
[
'updated_at'
]
=
datetime
.
datetime
.
now
()
...
...
@@ -166,7 +169,8 @@ class DatasetService:
dataset
.
query
.
filter_by
(
id
=
dataset_id
)
.
update
(
filtered_data
)
db
.
session
.
commit
()
if
action
:
deal_dataset_vector_index_task
.
delay
(
dataset_id
,
action
)
return
dataset
@
staticmethod
...
...
web/app/components/app/configuration/dataset-config/card-item/index.tsx
View file @
c67f345d
...
...
@@ -43,7 +43,7 @@ const CardItem: FC<ICardItemProps> = ({
selector=
{
`unavailable-tag-${config.id}`
}
htmlContent=
{
t
(
'dataset.unavailableTip'
)
}
>
<
span
className=
'shrink-0 px-1 border boder-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'
>
{
t
(
'dataset.unavailable'
)
}
</
span
>
<
span
className=
'shrink-0
inline-flex whitespace-nowrap
px-1 border boder-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'
>
{
t
(
'dataset.unavailable'
)
}
</
span
>
</
Tooltip
>
)
}
</
div
>
...
...
web/app/components/datasets/documents/detail/completed/InfiniteVirtualList.tsx
View file @
c67f345d
...
...
@@ -15,7 +15,7 @@ type IInfiniteVirtualListProps = {
onChangeSwitch
:
(
segId
:
string
,
enabled
:
boolean
)
=>
Promise
<
void
>
onDelete
:
(
segId
:
string
)
=>
Promise
<
void
>
archived
?:
boolean
embeddingAvailable
:
boolean
}
const
InfiniteVirtualList
:
FC
<
IInfiniteVirtualListProps
>
=
({
...
...
@@ -27,6 +27,7 @@ const InfiniteVirtualList: FC<IInfiniteVirtualListProps> = ({
onChangeSwitch
,
onDelete
,
archived
,
embeddingAvailable
,
})
=>
{
// If there are more items to be loaded then add an extra row to hold a loading indicator.
const
itemCount
=
hasNextPage
?
items
.
length
+
1
:
items
.
length
...
...
@@ -45,7 +46,7 @@ const InfiniteVirtualList: FC<IInfiniteVirtualListProps> = ({
content
=
(
<>
{
[
1
,
2
,
3
].
map
(
v
=>
(
<
SegmentCard
loading=
{
true
}
detail=
{
{
position
:
v
}
as
any
}
/>
<
SegmentCard
key=
{
v
}
loading=
{
true
}
detail=
{
{
position
:
v
}
as
any
}
/>
))
}
</>
)
...
...
@@ -60,6 +61,7 @@ const InfiniteVirtualList: FC<IInfiniteVirtualListProps> = ({
onDelete=
{
onDelete
}
loading=
{
false
}
archived=
{
archived
}
embeddingAvailable=
{
embeddingAvailable
}
/>
))
}
...
...
web/app/components/datasets/documents/detail/completed/SegmentCard.tsx
View file @
c67f345d
...
...
@@ -43,6 +43,7 @@ type ISegmentCardProps = {
scene
?:
UsageScene
className
?:
string
archived
?:
boolean
embeddingAvailable
:
boolean
}
const
SegmentCard
:
FC
<
ISegmentCardProps
>
=
({
...
...
@@ -55,6 +56,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
scene
=
'doc'
,
className
=
''
,
archived
,
embeddingAvailable
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
...
...
@@ -115,24 +117,26 @@ const SegmentCard: FC<ISegmentCardProps> = ({
:
(
<>
<
StatusItem
status=
{
enabled
?
'enabled'
:
'disabled'
}
reverse
textCls=
"text-gray-500 text-xs"
/>
<
div
className=
"hidden group-hover:inline-flex items-center"
>
<
Divider
type=
"vertical"
className=
"!h-2"
/>
<
div
onClick=
{
(
e
:
React
.
MouseEvent
<
HTMLDivElement
,
MouseEvent
>
)
=>
e
.
stopPropagation
()
}
className=
"inline-flex items-center"
>
<
Switch
size=
'md'
disabled=
{
archived
}
defaultValue=
{
enabled
}
onChange=
{
async
(
val
)
=>
{
await
onChangeSwitch
?.(
id
,
val
)
}
}
/>
{
embeddingAvailable
&&
(
<
div
className=
"hidden group-hover:inline-flex items-center"
>
<
Divider
type=
"vertical"
className=
"!h-2"
/>
<
div
onClick=
{
(
e
:
React
.
MouseEvent
<
HTMLDivElement
,
MouseEvent
>
)
=>
e
.
stopPropagation
()
}
className=
"inline-flex items-center"
>
<
Switch
size=
'md'
disabled=
{
archived
}
defaultValue=
{
enabled
}
onChange=
{
async
(
val
)
=>
{
await
onChangeSwitch
?.(
id
,
val
)
}
}
/>
</
div
>
</
div
>
</
div
>
)
}
</>
)
}
</
div
>
...
...
@@ -173,7 +177,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
<
div
className=
{
cn
(
s
.
commonIcon
,
s
.
bezierCurveIcon
)
}
/>
<
div
className=
{
s
.
segDataText
}
>
{
index_node_hash
}
</
div
>
</
div
>
{
!
archived
&&
(
{
!
archived
&&
embeddingAvailable
&&
(
<
div
className=
'shrink-0 w-6 h-6 flex items-center justify-center rounded-md hover:bg-red-100 hover:text-red-600 cursor-pointer group/delete'
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
()
setShowModal
(
true
)
...
...
web/app/components/datasets/documents/detail/completed/index.tsx
View file @
c67f345d
...
...
@@ -46,6 +46,7 @@ export const SegmentIndexTag: FC<{ positionId: string | number; className?: stri
}
type
ISegmentDetailProps
=
{
embeddingAvailable
:
boolean
segInfo
?:
Partial
<
SegmentDetailModel
>
&
{
id
:
string
}
onChangeSwitch
?:
(
segId
:
string
,
enabled
:
boolean
)
=>
Promise
<
void
>
onUpdate
:
(
segmentId
:
string
,
q
:
string
,
a
:
string
,
k
:
string
[])
=>
void
...
...
@@ -56,6 +57,7 @@ type ISegmentDetailProps = {
* Show all the contents of the segment
*/
const
SegmentDetailComponent
:
FC
<
ISegmentDetailProps
>
=
({
embeddingAvailable
,
segInfo
,
archived
,
onChangeSwitch
,
...
...
@@ -146,7 +148,7 @@ const SegmentDetailComponent: FC<ISegmentDetailProps> = ({
</
Button
>
</>
)
}
{
!
isEditing
&&
!
archived
&&
(
{
!
isEditing
&&
!
archived
&&
embeddingAvailable
&&
(
<>
<
div
className=
'group relative flex justify-center items-center w-6 h-6 hover:bg-gray-100 rounded-md cursor-pointer'
>
<
div
className=
{
cn
(
s
.
editTip
,
'hidden items-center absolute -top-10 px-3 h-[34px] bg-white rounded-lg whitespace-nowrap text-xs font-semibold text-gray-700 group-hover:flex'
)
}
>
{
t
(
'common.operation.edit'
)
}
</
div
>
...
...
@@ -183,15 +185,19 @@ const SegmentDetailComponent: FC<ISegmentDetailProps> = ({
</
div
>
<
div
className=
'flex items-center'
>
<
StatusItem
status=
{
segInfo
?.
enabled
?
'enabled'
:
'disabled'
}
reverse
textCls=
'text-gray-500 text-xs'
/>
<
Divider
type=
'vertical'
className=
'!h-2'
/>
<
Switch
size=
'md'
defaultValue=
{
segInfo
?.
enabled
}
onChange=
{
async
(
val
)
=>
{
await
onChangeSwitch
?.(
segInfo
?.
id
||
''
,
val
)
}
}
disabled=
{
archived
}
/>
{
embeddingAvailable
&&
(
<>
<
Divider
type=
'vertical'
className=
'!h-2'
/>
<
Switch
size=
'md'
defaultValue=
{
segInfo
?.
enabled
}
onChange=
{
async
(
val
)
=>
{
await
onChangeSwitch
?.(
segInfo
?.
id
||
''
,
val
)
}
}
disabled=
{
archived
}
/>
</>
)
}
</
div
>
</
div
>
</
div
>
...
...
@@ -209,6 +215,7 @@ export const splitArray = (arr: any[], size = 3) => {
}
type
ICompletedProps
=
{
embeddingAvailable
:
boolean
showNewSegmentModal
:
boolean
onNewSegmentModalChange
:
(
state
:
boolean
)
=>
void
importStatus
:
ProcessStatus
|
string
|
undefined
...
...
@@ -220,6 +227,7 @@ type ICompletedProps = {
* Support search and filter
*/
const
Completed
:
FC
<
ICompletedProps
>
=
({
embeddingAvailable
,
showNewSegmentModal
,
onNewSegmentModalChange
,
importStatus
,
...
...
@@ -384,6 +392,7 @@ const Completed: FC<ICompletedProps> = ({
<
Input
showPrefix
wrapperClassName=
'!w-52'
className=
'!h-8'
onChange=
{
debounce
(
setSearchValue
,
500
)
}
/>
</
div
>
<
InfiniteVirtualList
embeddingAvailable=
{
embeddingAvailable
}
hasNextPage=
{
lastSegmentsRes
?.
has_more
??
true
}
isNextPageLoading=
{
loading
}
items=
{
allSegments
}
...
...
@@ -395,6 +404,7 @@ const Completed: FC<ICompletedProps> = ({
/>
<
Modal
isShow=
{
currSegment
.
showModal
}
onClose=
{
()
=>
{}
}
className=
'!max-w-[640px] !overflow-visible'
>
<
SegmentDetail
embeddingAvailable=
{
embeddingAvailable
}
segInfo=
{
currSegment
.
segInfo
??
{
id
:
''
}
}
onChangeSwitch=
{
onChangeSwitch
}
onUpdate=
{
handleUpdateSegment
}
...
...
web/app/components/datasets/documents/detail/index.tsx
View file @
c67f345d
...
...
@@ -22,6 +22,7 @@ import type { MetadataType } from '@/service/datasets'
import
{
checkSegmentBatchImportProgress
,
fetchDocumentDetail
,
segmentBatchImport
}
from
'@/service/datasets'
import
{
ToastContext
}
from
'@/app/components/base/toast'
import
type
{
DocForm
}
from
'@/models/datasets'
import
{
useDatasetDetailContext
}
from
'@/context/dataset-detail'
export
const
DocumentContext
=
createContext
<
{
datasetId
?:
string
;
documentId
?:
string
;
docForm
:
string
}
>
({
docForm
:
''
})
...
...
@@ -50,6 +51,8 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
const
router
=
useRouter
()
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
{
dataset
}
=
useDatasetDetailContext
()
const
embeddingAvailable
=
!!
dataset
?.
embedding_available
const
[
showMetadata
,
setShowMetadata
]
=
useState
(
true
)
const
[
newSegmentModalVisible
,
setNewSegmentModalVisible
]
=
useState
(
false
)
const
[
batchModalVisible
,
setBatchModalVisible
]
=
useState
(
false
)
...
...
@@ -128,7 +131,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
<
Divider
className=
'!h-4'
type=
'vertical'
/>
<
DocumentTitle
extension=
{
documentDetail
?.
data_source_info
?.
upload_file
?.
extension
}
name=
{
documentDetail
?.
name
}
/>
<
StatusItem
status=
{
documentDetail
?.
display_status
||
'available'
}
scene=
'detail'
errorMessage=
{
documentDetail
?.
error
||
''
}
/>
{
documentDetail
&&
!
documentDetail
.
archived
&&
(
{
embeddingAvailable
&&
documentDetail
&&
!
documentDetail
.
archived
&&
(
<
SegmentAdd
importStatus=
{
importStatus
}
clearProcessStatus=
{
resetProcessStatus
}
...
...
@@ -138,6 +141,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
)
}
<
OperationAction
scene=
'detail'
embeddingAvailable=
{
embeddingAvailable
}
detail=
{
{
enabled
:
documentDetail
?.
enabled
||
false
,
archived
:
documentDetail
?.
archived
||
false
,
...
...
@@ -161,6 +165,7 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
{
embedding
?
<
Embedding
detail=
{
documentDetail
}
detailUpdate=
{
detailMutate
}
/>
:
<
Completed
embeddingAvailable=
{
embeddingAvailable
}
showNewSegmentModal=
{
newSegmentModalVisible
}
onNewSegmentModalChange=
{
setNewSegmentModalVisible
}
importStatus=
{
importStatus
}
...
...
web/app/components/datasets/documents/index.tsx
View file @
c67f345d
...
...
@@ -51,7 +51,7 @@ const NotionIcon = ({ className }: React.SVGProps<SVGElement>) => {
</
svg
>
}
const
EmptyElement
:
FC
<
{
onClick
:
()
=>
void
;
type
?:
'upload'
|
'sync'
}
>
=
({
onClick
,
type
=
'upload'
})
=>
{
const
EmptyElement
:
FC
<
{
canAdd
:
boolean
;
onClick
:
()
=>
void
;
type
?:
'upload'
|
'sync'
}
>
=
({
canAdd
=
true
,
onClick
,
type
=
'upload'
})
=>
{
const
{
t
}
=
useTranslation
()
return
<
div
className=
{
s
.
emptyWrapper
}
>
<
div
className=
{
s
.
emptyElement
}
>
...
...
@@ -62,7 +62,7 @@ const EmptyElement: FC<{ onClick: () => void; type?: 'upload' | 'sync' }> = ({ o
<
div
className=
{
s
.
emptyTip
}
>
{
t
(
`datasetDocuments.list.empty.${type}.tip`
)
}
</
div
>
{
type
===
'upload'
&&
<
Button
onClick=
{
onClick
}
className=
{
s
.
addFileBtn
}
>
{
type
===
'upload'
&&
canAdd
&&
<
Button
onClick=
{
onClick
}
className=
{
s
.
addFileBtn
}
>
<
PlusIcon
className=
{
s
.
plusIcon
}
/>
{
t
(
'datasetDocuments.list.addFile'
)
}
</
Button
>
}
</
div
>
...
...
@@ -84,6 +84,7 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
const
[
notionPageSelectorModalVisible
,
setNotionPageSelectorModalVisible
]
=
useState
(
false
)
const
[
timerCanRun
,
setTimerCanRun
]
=
useState
(
true
)
const
isDataSourceNotion
=
dataset
?.
data_source_type
===
DataSourceType
.
NOTION
const
embeddingAvailable
=
!!
dataset
?.
embedding_available
const
query
=
useMemo
(()
=>
{
return
{
page
:
currPage
+
1
,
limit
,
keyword
:
searchValue
,
fetch
:
isDataSourceNotion
?
true
:
''
}
...
...
@@ -205,20 +206,19 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
onChange=
{
debounce
(
setSearchValue
,
500
)
}
value=
{
searchValue
}
/>
<
Button
type=
'primary'
onClick=
{
routeToDocCreate
}
className=
'!h-8 !text-[13px]'
>
<
PlusIcon
className=
'h-4 w-4 mr-2 stroke-current'
/>
{
isDataSourceNotion
?
t
(
'datasetDocuments.list.addPages'
)
:
t
(
'datasetDocuments.list.addFile'
)
}
</
Button
>
{
embeddingAvailable
&&
(
<
Button
type=
'primary'
onClick=
{
routeToDocCreate
}
className=
'!h-8 !text-[13px]'
>
<
PlusIcon
className=
'h-4 w-4 mr-2 stroke-current'
/>
{
isDataSourceNotion
&&
t
(
'datasetDocuments.list.addPages'
)
}
{
!
isDataSourceNotion
&&
t
(
'datasetDocuments.list.addFile'
)
}
</
Button
>
)
}
</
div
>
{
isLoading
?
<
Loading
type=
'app'
/>
:
total
>
0
?
<
List
documents=
{
documentsList
||
[]
}
datasetId=
{
datasetId
}
onUpdate=
{
mutate
}
/>
:
<
EmptyElement
onClick=
{
routeToDocCreate
}
type=
{
isDataSourceNotion
?
'sync'
:
'upload'
}
/>
?
<
List
embeddingAvailable=
{
embeddingAvailable
}
documents=
{
documentsList
||
[]
}
datasetId=
{
datasetId
}
onUpdate=
{
mutate
}
/>
:
<
EmptyElement
canAdd=
{
embeddingAvailable
}
onClick=
{
routeToDocCreate
}
type=
{
isDataSourceNotion
?
'sync'
:
'upload'
}
/>
}
{
/* Show Pagination only if the total is more than the limit */
}
{
(
total
&&
total
>
limit
)
...
...
web/app/components/datasets/documents/list.tsx
View file @
c67f345d
...
...
@@ -103,6 +103,7 @@ type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_
// operation action for list and detail
export
const
OperationAction
:
FC
<
{
embeddingAvailable
:
boolean
detail
:
{
enabled
:
boolean
archived
:
boolean
...
...
@@ -114,7 +115,7 @@ export const OperationAction: FC<{
onUpdate
:
(
operationName
?:
string
)
=>
void
scene
?:
'list'
|
'detail'
className
?:
string
}
>
=
({
datasetId
,
detail
,
onUpdate
,
scene
=
'list'
,
className
=
''
})
=>
{
}
>
=
({
embeddingAvailable
,
datasetId
,
detail
,
onUpdate
,
scene
=
'list'
,
className
=
''
})
=>
{
const
{
id
,
enabled
=
false
,
archived
=
false
,
data_source_type
}
=
detail
||
{}
const
[
showModal
,
setShowModal
]
=
useState
(
false
)
const
{
notify
}
=
useContext
(
ToastContext
)
...
...
@@ -154,87 +155,94 @@ export const OperationAction: FC<{
}
return
<
div
className=
'flex items-center'
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
{
isListScene
&&
<>
{
archived
?
<
Tooltip
selector=
{
`list-switch-${id}`
}
content=
{
t
(
'datasetDocuments.list.action.enableWarning'
)
as
string
}
className=
'!font-semibold'
>
<
div
>
<
Switch
defaultValue=
{
false
}
onChange=
{
()
=>
{
}
}
disabled=
{
true
}
size=
'md'
/>
</
div
>
</
Tooltip
>
:
<
Switch
defaultValue=
{
enabled
}
onChange=
{
v
=>
onOperate
(
v
?
'enable'
:
'disable'
)
}
size=
'md'
/>
}
<
Divider
className=
'!ml-4 !mr-2 !h-3'
type=
'vertical'
/>
</>
}
<
Popover
htmlContent=
{
<
div
className=
'w-full py-1'
>
{
!
isListScene
&&
<>
<
div
className=
'flex justify-between items-center mx-4 pt-2'
>
<
span
className=
{
cn
(
s
.
actionName
,
'font-medium'
)
}
>
{
!
archived
&&
enabled
?
t
(
'datasetDocuments.list.index.enable'
)
:
t
(
'datasetDocuments.list.index.disable'
)
}
</
span
>
<
Tooltip
selector=
{
`detail-switch-${id}`
}
content=
{
t
(
'datasetDocuments.list.action.enableWarning'
)
as
string
}
className=
'!font-semibold'
disabled=
{
!
archived
}
>
<
div
>
<
Switch
defaultValue=
{
archived
?
false
:
enabled
}
onChange=
{
v
=>
!
archived
&&
onOperate
(
v
?
'enable'
:
'disable'
)
}
disabled=
{
archived
}
size=
'md'
/>
</
div
>
</
Tooltip
>
{
isListScene
&&
!
embeddingAvailable
&&
(
<
Switch
defaultValue=
{
false
}
onChange=
{
()
=>
{
}
}
disabled=
{
true
}
size=
'md'
/>
)
}
{
isListScene
&&
embeddingAvailable
&&
(
<>
{
archived
?
<
Tooltip
selector=
{
`list-switch-${id}`
}
content=
{
t
(
'datasetDocuments.list.action.enableWarning'
)
as
string
}
className=
'!font-semibold'
>
<
div
>
<
Switch
defaultValue=
{
false
}
onChange=
{
()
=>
{
}
}
disabled=
{
true
}
size=
'md'
/>
</
div
>
<
div
className=
'mx-4 pb-1 pt-0.5 text-xs text-gray-500'
>
{
!
archived
&&
enabled
?
t
(
'datasetDocuments.list.index.enableTip'
)
:
t
(
'datasetDocuments.list.index.disableTip'
)
}
</
div
>
<
Divider
/>
</>
}
{
!
archived
&&
(
<>
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
router
.
push
(
`/datasets/${datasetId}/documents/${detail.id}/settings`
)
}
>
<
SettingsIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.settings'
)
}
</
span
>
</
Tooltip
>
:
<
Switch
defaultValue=
{
enabled
}
onChange=
{
v
=>
onOperate
(
v
?
'enable'
:
'disable'
)
}
size=
'md'
/>
}
<
Divider
className=
'!ml-4 !mr-2 !h-3'
type=
'vertical'
/>
</>
)
}
{
embeddingAvailable
&&
(
<
Popover
htmlContent=
{
<
div
className=
'w-full py-1'
>
{
!
isListScene
&&
<>
<
div
className=
'flex justify-between items-center mx-4 pt-2'
>
<
span
className=
{
cn
(
s
.
actionName
,
'font-medium'
)
}
>
{
!
archived
&&
enabled
?
t
(
'datasetDocuments.list.index.enable'
)
:
t
(
'datasetDocuments.list.index.disable'
)
}
</
span
>
<
Tooltip
selector=
{
`detail-switch-${id}`
}
content=
{
t
(
'datasetDocuments.list.action.enableWarning'
)
as
string
}
className=
'!font-semibold'
disabled=
{
!
archived
}
>
<
div
>
<
Switch
defaultValue=
{
archived
?
false
:
enabled
}
onChange=
{
v
=>
!
archived
&&
onOperate
(
v
?
'enable'
:
'disable'
)
}
disabled=
{
archived
}
size=
'md'
/>
</
div
>
</
Tooltip
>
</
div
>
<
div
className=
'mx-4 pb-1 pt-0.5 text-xs text-gray-500'
>
{
!
archived
&&
enabled
?
t
(
'datasetDocuments.list.index.enableTip'
)
:
t
(
'datasetDocuments.list.index.disableTip'
)
}
</
div
>
{
data_source_type
===
'notion_import'
&&
(
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'sync'
)
}
>
<
SyncIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.sync'
)
}
</
span
>
<
Divider
/>
</>
}
{
!
archived
&&
(
<>
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
router
.
push
(
`/datasets/${datasetId}/documents/${detail.id}/settings`
)
}
>
<
SettingsIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.settings'
)
}
</
span
>
</
div
>
)
}
<
Divider
className=
'my-1'
/
>
<
/>
)
}
{
!
archived
&&
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'archive'
)
}
>
<
ArchiveIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.archive'
)
}
</
span
>
</
div
>
}
{
archived
&&
(
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'un_
archive'
)
}
>
{
data_source_type
===
'notion_import'
&&
(
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'sync'
)
}
>
<
SyncIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.sync'
)
}
</
span
>
</
div
>
)
}
<
Divider
className=
'my-1'
/
>
</>
)
}
{
!
archived
&&
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'
archive'
)
}
>
<
ArchiveIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.unarchive'
)
}
</
span
>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.archive'
)
}
</
span
>
</
div
>
}
{
archived
&&
(
<
div
className=
{
s
.
actionItem
}
onClick=
{
()
=>
onOperate
(
'un_archive'
)
}
>
<
ArchiveIcon
/>
<
span
className=
{
s
.
actionName
}
>
{
t
(
'datasetDocuments.list.action.unarchive'
)
}
</
span
>
</
div
>
)
}
<
div
className=
{
cn
(
s
.
actionItem
,
s
.
deleteActionItem
,
'group'
)
}
onClick=
{
()
=>
setShowModal
(
true
)
}
>
<
TrashIcon
className=
{
'w-4 h-4 stroke-current text-gray-500 stroke-2 group-hover:text-red-500'
}
/>
<
span
className=
{
cn
(
s
.
actionName
,
'group-hover:text-red-500'
)
}
>
{
t
(
'datasetDocuments.list.action.delete'
)
}
</
span
>
</
div
>
)
}
<
div
className=
{
cn
(
s
.
actionItem
,
s
.
deleteActionItem
,
'group'
)
}
onClick=
{
()
=>
setShowModal
(
true
)
}
>
<
TrashIcon
className=
{
'w-4 h-4 stroke-current text-gray-500 stroke-2 group-hover:text-red-500'
}
/>
<
span
className=
{
cn
(
s
.
actionName
,
'group-hover:text-red-500'
)
}
>
{
t
(
'datasetDocuments.list.action.delete'
)
}
</
span
>
</
div
>
</
div
>
}
trigger=
'click
'
position=
'br'
btnElement=
{
<
div
className=
{
cn
(
s
.
commonIcon
)
}
>
<
DotsHorizontal
className=
'w-4 h-4 text-gray-700'
/
>
</
div
>
}
btnClassName=
{
open
=>
cn
(
isListScene
?
s
.
actionIconWrapperList
:
s
.
actionIconWrapperDetail
,
open
?
'!bg-gray-100 !shadow-none'
:
'!bg-transparent'
)
}
className=
{
`!w-[200px] h-fit !z-20 ${className}`
}
/>
}
trigger=
'click'
position=
'br
'
btnElement=
{
<
div
className=
{
cn
(
s
.
commonIcon
)
}
>
<
DotsHorizontal
className=
'w-4 h-4 text-gray-700'
/
>
<
/
div
>
}
btnClassName=
{
open
=>
cn
(
isListScene
?
s
.
actionIconWrapperList
:
s
.
actionIconWrapperDetail
,
open
?
'!bg-gray-100 !shadow-none'
:
'!bg-transparent'
)
}
className=
{
`!w-[200px] h-fit !z-20 ${className}`
}
/>
)
}
{
showModal
&&
<
Modal
isShow=
{
showModal
}
onClose=
{
()
=>
setShowModal
(
false
)
}
className=
{
s
.
delModal
}
closable
>
<
div
>
<
div
className=
{
s
.
warningWrapper
}
>
...
...
@@ -277,6 +285,7 @@ const renderCount = (count: number | undefined) => {
type
LocalDoc
=
SimpleDocumentDetail
&
{
percent
?:
number
}
type
IDocumentListProps
=
{
embeddingAvailable
:
boolean
documents
:
LocalDoc
[]
datasetId
:
string
onUpdate
:
()
=>
void
...
...
@@ -285,7 +294,7 @@ type IDocumentListProps = {
/**
* Document list component including basic information
*/
const
DocumentList
:
FC
<
IDocumentListProps
>
=
({
documents
=
[],
datasetId
,
onUpdate
})
=>
{
const
DocumentList
:
FC
<
IDocumentListProps
>
=
({
embeddingAvailable
,
documents
=
[],
datasetId
,
onUpdate
})
=>
{
const
{
t
}
=
useTranslation
()
const
router
=
useRouter
()
const
[
localDocs
,
setLocalDocs
]
=
useState
<
LocalDoc
[]
>
(
documents
)
...
...
@@ -361,6 +370,7 @@ const DocumentList: FC<IDocumentListProps> = ({ documents = [], datasetId, onUpd
</
td
>
<
td
>
<
OperationAction
embeddingAvailable=
{
embeddingAvailable
}
datasetId=
{
datasetId
}
detail=
{
pick
(
doc
,
[
'enabled'
,
'archived'
,
'id'
,
'data_source_type'
,
'doc_form'
])
}
onUpdate=
{
onUpdate
}
...
...
web/app/components/datasets/hit-testing/index.tsx
View file @
c67f345d
This diff is collapsed.
Click to expand it.
web/app/components/datasets/settings/form/index.tsx
View file @
c67f345d
...
...
@@ -5,6 +5,7 @@ import useSWR from 'swr'
import
{
useContext
}
from
'use-context-selector'
import
{
BookOpenIcon
}
from
'@heroicons/react/24/outline'
import
{
useTranslation
}
from
'react-i18next'
import
cn
from
'classnames'
import
PermissionsRadio
from
'../permissions-radio'
import
IndexMethodRadio
from
'../index-method-radio'
import
{
ToastContext
}
from
'@/app/components/base/toast'
...
...
@@ -88,7 +89,8 @@ const Form = ({
<
div
>
{
t
(
'datasetSettings.form.name'
)
}
</
div
>
</
div
>
<
input
className=
{
inputClass
}
disabled=
{
!
currentDataset
?.
embedding_available
}
className=
{
cn
(
inputClass
,
!
currentDataset
?.
embedding_available
&&
'opacity-60'
)
}
value=
{
name
}
onChange=
{
e
=>
setName
(
e
.
target
.
value
)
}
/>
...
...
@@ -99,7 +101,8 @@ const Form = ({
</
div
>
<
div
>
<
textarea
className=
{
`${inputClass} block mb-2 h-[120px] py-2 resize-none`
}
disabled=
{
!
currentDataset
?.
embedding_available
}
className=
{
cn
(
`${inputClass} block mb-2 h-[120px] py-2 resize-none`
,
!
currentDataset
?.
embedding_available
&&
'opacity-60'
)
}
placeholder=
{
t
(
'datasetSettings.form.descPlaceholder'
)
||
''
}
value=
{
description
}
onChange=
{
e
=>
setDescription
(
e
.
target
.
value
)
}
...
...
@@ -116,61 +119,67 @@ const Form = ({
</
div
>
<
div
className=
'w-[480px]'
>
<
PermissionsRadio
disable=
{
!
currentDataset
?.
embedding_available
}
value=
{
permission
}
onChange=
{
v
=>
setPermission
(
v
)
}
/>
</
div
>
</
div
>
<
div
className=
'w-full h-0 border-b-[0.5px] border-b-gray-200 my-2'
/>
<
div
className=
{
rowClass
}
>
<
div
className=
{
labelClass
}
>
<
div
>
{
t
(
'datasetSettings.form.indexMethod'
)
}
</
div
>
</
div
>
<
div
className=
'w-[480px]'
>
<
IndexMethodRadio
value=
{
indexMethod
}
onChange=
{
v
=>
setIndexMethod
(
v
)
}
/>
</
div
>
</
div
>
<
div
className=
{
rowClass
}
>
<
div
className=
{
labelClass
}
>
<
div
>
{
t
(
'datasetSettings.form.embeddingModel'
)
}
</
div
>
</
div
>
<
div
className=
'w-[480px]'
>
{
currentDataset
&&
(
<>
<
div
className=
'w-full h-9 rounded-lg bg-gray-100 opacity-60'
>
<
ModelSelector
readonly
value=
{
{
providerName
:
currentDataset
.
embedding_model_provider
as
ProviderEnum
,
modelName
:
currentDataset
.
embedding_model
,
}
}
modelType=
{
ModelType
.
embeddings
}
onChange=
{
()
=>
{}
}
/>
</
div
>
<
div
className=
'mt-2 w-full text-xs leading-6 text-gray-500'
>
{
t
(
'datasetSettings.form.embeddingModelTip'
)
}
<
span
className=
'text-[#155eef] cursor-pointer'
onClick=
{
()
=>
setShowSetAPIKeyModal
(
true
)
}
>
{
t
(
'datasetSettings.form.embeddingModelTipLink'
)
}
</
span
>
</
div
>
</>
)
}
{
currentDataset
&&
currentDataset
.
indexing_technique
&&
(
<>
<
div
className=
'w-full h-0 border-b-[0.5px] border-b-gray-200 my-2'
/>
<
div
className=
{
rowClass
}
>
<
div
className=
{
labelClass
}
>
<
div
>
{
t
(
'datasetSettings.form.indexMethod'
)
}
</
div
>
</
div
>
<
div
className=
'w-[480px]'
>
<
IndexMethodRadio
disable=
{
!
currentDataset
?.
embedding_available
}
value=
{
indexMethod
}
onChange=
{
v
=>
setIndexMethod
(
v
)
}
/>
</
div
>
</
div
>
</>
)
}
{
currentDataset
&&
currentDataset
.
indexing_technique
===
'high_quality'
&&
(
<
div
className=
{
rowClass
}
>
<
div
className=
{
labelClass
}
>
<
div
>
{
t
(
'datasetSettings.form.embeddingModel'
)
}
</
div
>
</
div
>
<
div
className=
'w-[480px]'
>
<
div
className=
'w-full h-9 rounded-lg bg-gray-100 opacity-60'
>
<
ModelSelector
readonly
value=
{
{
providerName
:
currentDataset
.
embedding_model_provider
as
ProviderEnum
,
modelName
:
currentDataset
.
embedding_model
,
}
}
modelType=
{
ModelType
.
embeddings
}
onChange=
{
()
=>
{}
}
/>
</
div
>
<
div
className=
'mt-2 w-full text-xs leading-6 text-gray-500'
>
{
t
(
'datasetSettings.form.embeddingModelTip'
)
}
<
span
className=
'text-[#155eef] cursor-pointer'
onClick=
{
()
=>
setShowSetAPIKeyModal
(
true
)
}
>
{
t
(
'datasetSettings.form.embeddingModelTipLink'
)
}
</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
{
rowClass
}
>
<
div
className=
{
labelClass
}
/>
<
div
className=
'w-[480px]'
>
<
Button
className=
'min-w-24 text-sm'
type=
'primary'
onClick=
{
handleSave
}
>
{
t
(
'datasetSettings.form.save'
)
}
</
Button
>
)
}
{
currentDataset
?.
embedding_available
&&
(
<
div
className=
{
rowClass
}
>
<
div
className=
{
labelClass
}
/>
<
div
className=
'w-[480px]'
>
<
Button
className=
'min-w-24 text-sm'
type=
'primary'
onClick=
{
handleSave
}
>
{
t
(
'datasetSettings.form.save'
)
}
</
Button
>
</
div
>
</
div
>
</
div
>
)
}
{
showSetAPIKeyModal
&&
(
<
AccountSetting
activeTab=
"provider"
onCancel=
{
async
()
=>
{
setShowSetAPIKeyModal
(
false
)
...
...
web/app/components/datasets/settings/index-method-radio/index.module.css
View file @
c67f345d
...
...
@@ -35,4 +35,20 @@
border-width
:
1.5px
;
border-color
:
#528BFF
;
box-shadow
:
0px
1px
3px
rgba
(
16
,
24
,
40
,
0.1
),
0px
1px
2px
rgba
(
16
,
24
,
40
,
0.06
);
}
\ No newline at end of file
}
.wrapper
.item.disable
{
@apply
opacity-60;
}
.wrapper
.item-active.disable
{
@apply
opacity-60;
}
.wrapper
.item.disable
:hover
{
@apply
bg-gray-25
border
border-gray-100
shadow-none
cursor-default
opacity-60;
}
.wrapper
.item-active.disable
:hover
{
@apply
cursor-default
opacity-60;
border-width
:
1.5px
;
border-color
:
#528BFF
;
box-shadow
:
0px
1px
3px
rgba
(
16
,
24
,
40
,
0.1
),
0px
1px
2px
rgba
(
16
,
24
,
40
,
0.06
);
}
web/app/components/datasets/settings/index-method-radio/index.tsx
View file @
c67f345d
...
...
@@ -2,7 +2,7 @@
import
{
useTranslation
}
from
'react-i18next'
import
classNames
from
'classnames'
import
s
from
'./index.module.css'
import
{
DataSet
}
from
'@/models/datasets'
import
type
{
DataSet
}
from
'@/models/datasets'
const
itemClass
=
`
w-[234px] p-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer
...
...
@@ -13,11 +13,13 @@ const radioClass = `
type
IIndexMethodRadioProps
=
{
value
?:
DataSet
[
'indexing_technique'
]
onChange
:
(
v
?:
DataSet
[
'indexing_technique'
])
=>
void
disable
?:
boolean
}
const
IndexMethodRadio
=
({
value
,
onChange
onChange
,
disable
,
}:
IIndexMethodRadioProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
options
=
[
...
...
@@ -25,28 +27,32 @@ const IndexMethodRadio = ({
key
:
'high_quality'
,
text
:
t
(
'datasetSettings.form.indexMethodHighQuality'
),
desc
:
t
(
'datasetSettings.form.indexMethodHighQualityTip'
),
icon
:
'high-quality'
icon
:
'high-quality'
,
},
{
key
:
'economy'
,
text
:
t
(
'datasetSettings.form.indexMethodEconomy'
),
desc
:
t
(
'datasetSettings.form.indexMethodEconomyTip'
),
icon
:
'economy'
}
icon
:
'economy'
,
}
,
]
return
(
<
div
className=
{
classNames
(
s
.
wrapper
,
'flex justify-between w-full'
)
}
>
{
options
.
map
(
option
=>
(
<
div
key=
{
option
.
key
}
<
div
key=
{
option
.
key
}
className=
{
classNames
(
option
.
key
===
value
&&
s
[
'item-active'
],
itemClass
,
s
.
item
,
itemClass
option
.
key
===
value
&&
s
[
'item-active'
],
disable
&&
s
.
disable
,
)
}
onClick=
{
()
=>
onChange
(
option
.
key
as
DataSet
[
'indexing_technique'
])
}
onClick=
{
()
=>
{
if
(
!
disable
)
onChange
(
option
.
key
as
DataSet
[
'indexing_technique'
])
}
}
>
<
div
className=
'flex items-center mb-1'
>
<
div
className=
{
classNames
(
s
.
icon
,
s
[
`${option.icon}-icon`
])
}
/>
...
...
web/app/components/datasets/settings/permissions-radio/index.module.css
View file @
c67f345d
...
...
@@ -27,4 +27,20 @@
border-width
:
1.5px
;
border-color
:
#528BFF
;
box-shadow
:
0px
1px
3px
rgba
(
16
,
24
,
40
,
0.1
),
0px
1px
2px
rgba
(
16
,
24
,
40
,
0.06
);
}
.wrapper
.item.disable
{
@apply
opacity-60;
}
.wrapper
.item-active.disable
{
@apply
opacity-60;
}
.wrapper
.item.disable
:hover
{
@apply
bg-gray-25
border
border-gray-100
shadow-none
cursor-default
opacity-60;
}
.wrapper
.item-active.disable
:hover
{
@apply
cursor-default
opacity-60;
border-width
:
1.5px
;
border-color
:
#528BFF
;
box-shadow
:
0px
1px
3px
rgba
(
16
,
24
,
40
,
0.1
),
0px
1px
2px
rgba
(
16
,
24
,
40
,
0.06
);
}
\ No newline at end of file
web/app/components/datasets/settings/permissions-radio/index.tsx
View file @
c67f345d
...
...
@@ -2,7 +2,7 @@
import
{
useTranslation
}
from
'react-i18next'
import
classNames
from
'classnames'
import
s
from
'./index.module.css'
import
{
DataSet
}
from
'@/models/datasets'
import
type
{
DataSet
}
from
'@/models/datasets'
const
itemClass
=
`
flex items-center w-[234px] h-12 px-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer
...
...
@@ -13,36 +13,42 @@ const radioClass = `
type
IPermissionsRadioProps
=
{
value
?:
DataSet
[
'permission'
]
onChange
:
(
v
?:
DataSet
[
'permission'
])
=>
void
disable
?:
boolean
}
const
PermissionsRadio
=
({
value
,
onChange
onChange
,
disable
,
}:
IPermissionsRadioProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
options
=
[
{
key
:
'only_me'
,
text
:
t
(
'datasetSettings.form.permissionsOnlyMe'
)
text
:
t
(
'datasetSettings.form.permissionsOnlyMe'
)
,
},
{
key
:
'all_team_members'
,
text
:
t
(
'datasetSettings.form.permissionsAllMember'
)
}
text
:
t
(
'datasetSettings.form.permissionsAllMember'
)
,
}
,
]
return
(
<
div
className=
{
classNames
(
s
.
wrapper
,
'flex justify-between w-full'
)
}
>
{
options
.
map
(
option
=>
(
<
div
key=
{
option
.
key
}
<
div
key=
{
option
.
key
}
className=
{
classNames
(
option
.
key
===
value
&&
s
[
'item-active'
],
itemClass
,
s
.
item
itemClass
,
s
.
item
,
option
.
key
===
value
&&
s
[
'item-active'
],
disable
&&
s
.
disable
,
)
}
onClick=
{
()
=>
onChange
(
option
.
key
as
DataSet
[
'permission'
])
}
onClick=
{
()
=>
{
if
(
!
disable
)
onChange
(
option
.
key
as
DataSet
[
'permission'
])
}
}
>
<
div
className=
{
classNames
(
s
[
'user-icon'
],
'mr-3'
)
}
/>
<
div
className=
'grow text-sm text-gray-900'
>
{
option
.
text
}
</
div
>
...
...
web/i18n/i18next-config.ts
View file @
c67f345d
...
...
@@ -35,6 +35,8 @@ import exploreEn from './lang/explore.en'
import
exploreZh
from
'./lang/explore.zh'
import
{
getLocaleOnClient
}
from
'@/i18n/client'
const
localLng
=
getLocaleOnClient
()
const
resources
=
{
'en'
:
{
translation
:
{
...
...
@@ -86,7 +88,7 @@ i18n.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.
init
({
lng
:
getLocaleOnClient
()
,
lng
:
localLng
,
fallbackLng
:
'en'
,
// debug: true,
resources
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment