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
d05f7910
Commit
d05f7910
authored
Jun 28, 2023
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refact common layout
parent
334f46d0
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
150 additions
and
153 deletions
+150
-153
_layout-client.tsx
web/app/(commonLayout)/_layout-client.tsx
+0
-111
layout.tsx
web/app/(commonLayout)/layout.tsx
+13
-6
index.tsx
web/app/components/header/account-dropdown/index.tsx
+13
-3
index.tsx
web/app/components/header/index.tsx
+8
-16
sentry-initor.tsx
web/app/components/sentry-initor.tsx
+30
-0
swr-initor.tsx
web/app/components/swr-initor.tsx
+21
-0
layout.tsx
web/app/layout.tsx
+6
-2
app-context.tsx
web/context/app-context.tsx
+59
-15
No files found.
web/app/(commonLayout)/_layout-client.tsx
deleted
100644 → 0
View file @
334f46d0
'use client'
import
type
{
FC
}
from
'react'
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
import
{
usePathname
,
useRouter
,
useSelectedLayoutSegments
}
from
'next/navigation'
import
useSWR
,
{
SWRConfig
}
from
'swr'
import
*
as
Sentry
from
'@sentry/react'
import
Header
from
'../components/header'
import
{
fetchAppList
}
from
'@/service/apps'
import
{
fetchDatasets
}
from
'@/service/datasets'
import
{
fetchLanggeniusVersion
,
fetchUserProfile
,
logout
}
from
'@/service/common'
import
Loading
from
'@/app/components/base/loading'
import
{
AppContextProvider
}
from
'@/context/app-context'
import
DatasetsContext
from
'@/context/datasets-context'
import
type
{
LangGeniusVersionResponse
,
UserProfileResponse
}
from
'@/models/common'
const
isDevelopment
=
process
.
env
.
NODE_ENV
===
'development'
export
type
ICommonLayoutProps
=
{
children
:
React
.
ReactNode
}
const
CommonLayout
:
FC
<
ICommonLayoutProps
>
=
({
children
})
=>
{
useEffect
(()
=>
{
const
SENTRY_DSN
=
document
?.
body
?.
getAttribute
(
'data-public-sentry-dsn'
)
if
(
!
isDevelopment
&&
SENTRY_DSN
)
{
Sentry
.
init
({
dsn
:
SENTRY_DSN
,
integrations
:
[
new
Sentry
.
BrowserTracing
({
}),
new
Sentry
.
Replay
(),
],
tracesSampleRate
:
0.1
,
replaysSessionSampleRate
:
0.1
,
replaysOnErrorSampleRate
:
1.0
,
})
}
},
[])
const
router
=
useRouter
()
const
pathname
=
usePathname
()
const
segments
=
useSelectedLayoutSegments
()
const
pattern
=
pathname
.
replace
(
/.*
\/
app
\/
/
,
''
)
const
[
idOrMethod
]
=
pattern
.
split
(
'/'
)
const
isNotDetailPage
=
idOrMethod
===
'list'
const
pageContainerRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
appId
=
isNotDetailPage
?
''
:
idOrMethod
const
{
data
:
appList
,
mutate
:
mutateApps
}
=
useSWR
({
url
:
'/apps'
,
params
:
{
page
:
1
}
},
fetchAppList
)
const
{
data
:
datasetList
,
mutate
:
mutateDatasets
}
=
useSWR
(
segments
[
0
]
===
'datasets'
?
{
url
:
'/datasets'
,
params
:
{
page
:
1
}
}
:
null
,
fetchDatasets
)
const
{
data
:
userProfileResponse
,
mutate
:
mutateUserProfile
}
=
useSWR
({
url
:
'/account/profile'
,
params
:
{}
},
fetchUserProfile
)
const
[
userProfile
,
setUserProfile
]
=
useState
<
UserProfileResponse
>
()
const
[
langeniusVersionInfo
,
setLangeniusVersionInfo
]
=
useState
<
LangGeniusVersionResponse
>
()
const
updateUserProfileAndVersion
=
async
()
=>
{
if
(
userProfileResponse
&&
!
userProfileResponse
.
bodyUsed
)
{
const
result
=
await
userProfileResponse
.
json
()
setUserProfile
(
result
)
const
current_version
=
userProfileResponse
.
headers
.
get
(
'x-version'
)
const
current_env
=
userProfileResponse
.
headers
.
get
(
'x-env'
)
const
versionData
=
await
fetchLanggeniusVersion
({
url
:
'/version'
,
params
:
{
current_version
}
})
setLangeniusVersionInfo
({
...
versionData
,
current_version
,
latest_version
:
versionData
.
version
,
current_env
})
}
}
useEffect
(()
=>
{
updateUserProfileAndVersion
()
},
[
userProfileResponse
])
if
(
!
appList
||
!
userProfile
||
!
langeniusVersionInfo
)
return
<
Loading
type=
'app'
/>
const
curAppId
=
segments
[
0
]
===
'app'
&&
segments
[
2
]
const
currentDatasetId
=
segments
[
0
]
===
'datasets'
&&
segments
[
2
]
const
currentDataset
=
datasetList
?.
data
?.
find
(
opt
=>
opt
.
id
===
currentDatasetId
)
// if (!isNotDetailPage && !curApp) {
// alert('app not found') // TODO: use toast. Now can not get toast context here.
// // notify({ type: 'error', message: 'App not found' })
// router.push('/apps')
// }
const
onLogout
=
async
()
=>
{
await
logout
({
url
:
'/logout'
,
params
:
{},
})
router
.
push
(
'/signin'
)
}
return
(
<
SWRConfig
value=
{
{
shouldRetryOnError
:
false
,
}
}
>
<
AppContextProvider
value=
{
{
apps
:
appList
.
data
,
mutateApps
,
userProfile
,
mutateUserProfile
,
pageContainerRef
}
}
>
<
DatasetsContext
.
Provider
value=
{
{
datasets
:
datasetList
?.
data
||
[],
mutateDatasets
,
currentDataset
}
}
>
<
div
ref=
{
pageContainerRef
}
className=
'relative flex flex-col h-full overflow-auto bg-gray-100'
>
<
Header
isBordered=
{
[
'/apps'
,
'/datasets'
].
includes
(
pathname
)
}
curAppId=
{
curAppId
||
''
}
userProfile=
{
userProfile
}
onLogout=
{
onLogout
}
langeniusVersionInfo=
{
langeniusVersionInfo
}
/>
{
children
}
</
div
>
</
DatasetsContext
.
Provider
>
</
AppContextProvider
>
</
SWRConfig
>
)
}
export
default
React
.
memo
(
CommonLayout
)
web/app/(commonLayout)/layout.tsx
View file @
d05f7910
import
React
from
"react"
;
import
type
{
FC
}
from
'react'
import
LayoutClient
,
{
ICommonLayoutProps
}
from
"./_layout-client"
;
import
React
from
'react'
import
type
{
ReactNode
}
from
'react'
import
SwrInitor
from
'@/app/components/swr-initor'
import
{
AppContextProvider
}
from
'@/context/app-context'
import
GA
,
{
GaType
}
from
'@/app/components/base/ga'
import
Header
from
'@/app/components/header'
const
Layout
:
FC
<
ICommonLayoutProps
>
=
({
children
})
=>
{
const
Layout
=
({
children
}:
{
children
:
ReactNode
})
=>
{
return
(
<>
<
GA
gaType=
{
GaType
.
admin
}
/>
<
LayoutClient
children=
{
children
}
></
LayoutClient
>
<
SwrInitor
>
<
AppContextProvider
>
<
Header
/>
{
children
}
</
AppContextProvider
>
</
SwrInitor
>
</>
)
}
...
...
@@ -16,4 +23,4 @@ export const metadata = {
title
:
'Dify'
,
}
export
default
Layout
\ No newline at end of file
export
default
Layout
web/app/components/header/account-dropdown/index.tsx
View file @
d05f7910
'use client'
import
{
useTranslation
}
from
'react-i18next'
import
{
Fragment
,
useState
}
from
'react'
import
{
useRouter
}
from
'next/navigation'
import
{
useContext
}
from
'use-context-selector'
import
classNames
from
'classnames'
import
Link
from
'next/link'
...
...
@@ -13,24 +14,33 @@ import WorkplaceSelector from './workplace-selector'
import
type
{
LangGeniusVersionResponse
,
UserProfileResponse
}
from
'@/models/common'
import
I18n
from
'@/context/i18n'
import
Avatar
from
'@/app/components/base/avatar'
import
{
logout
}
from
'@/service/common'
type
IAppSelectorProps
=
{
userProfile
:
UserProfileResponse
onLogout
:
()
=>
void
langeniusVersionInfo
:
LangGeniusVersionResponse
}
export
default
function
AppSelector
({
userProfile
,
onLogout
,
langeniusVersionInfo
}:
IAppSelectorProps
)
{
export
default
function
AppSelector
({
userProfile
,
langeniusVersionInfo
}:
IAppSelectorProps
)
{
const
itemClassName
=
`
flex items-center w-full h-10 px-3 text-gray-700 text-[14px]
rounded-lg font-normal hover:bg-gray-100 cursor-pointer
`
const
router
=
useRouter
()
const
[
settingVisible
,
setSettingVisible
]
=
useState
(
false
)
const
[
aboutVisible
,
setAboutVisible
]
=
useState
(
false
)
const
{
locale
}
=
useContext
(
I18n
)
const
{
t
}
=
useTranslation
()
const
handleLogout
=
async
()
=>
{
await
logout
({
url
:
'/logout'
,
params
:
{},
})
router
.
push
(
'/signin'
)
}
return
(
<
div
className=
""
>
<
Menu
as=
"div"
className=
"relative inline-block text-left"
>
...
...
@@ -107,7 +117,7 @@ export default function AppSelector({ userProfile, onLogout, langeniusVersionInf
</
Menu
.
Item
>
</
div
>
<
Menu
.
Item
>
<
div
className=
'p-1'
onClick=
{
()
=>
on
Logout
()
}
>
<
div
className=
'p-1'
onClick=
{
()
=>
handle
Logout
()
}
>
<
div
className=
'flex items-center justify-between h-12 px-3 rounded-lg cursor-pointer group hover:bg-gray-100'
>
...
...
web/app/components/header/index.tsx
View file @
d05f7910
'use client'
import
{
useEffect
,
useState
}
from
'react'
import
type
{
FC
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useSelectedLayoutSegment
}
from
'next/navigation'
import
{
use
Pathname
,
use
SelectedLayoutSegment
}
from
'next/navigation'
import
classNames
from
'classnames'
import
{
CommandLineIcon
}
from
'@heroicons/react/24/solid'
import
Link
from
'next/link'
...
...
@@ -10,19 +9,14 @@ import AccountDropdown from './account-dropdown'
import
AppNav
from
'./app-nav'
import
DatasetNav
from
'./dataset-nav'
import
s
from
'./index.module.css'
import
type
{
GithubRepo
,
LangGeniusVersionResponse
,
UserProfileResponse
}
from
'@/models/common'
import
type
{
GithubRepo
}
from
'@/models/common'
import
{
WorkspaceProvider
}
from
'@/context/workspace-context'
import
{
useAppContext
}
from
'@/context/app-context'
import
{
Grid01
}
from
'@/app/components/base/icons/src/vender/line/layout'
import
{
Grid01
as
Grid01Solid
}
from
'@/app/components/base/icons/src/vender/solid/layout'
import
{
PuzzlePiece01
}
from
'@/app/components/base/icons/src/vender/line/development'
import
{
PuzzlePiece01
as
PuzzlePiece01Solid
}
from
'@/app/components/base/icons/src/vender/solid/development'
export
type
IHeaderProps
=
{
userProfile
:
UserProfileResponse
onLogout
:
()
=>
void
langeniusVersionInfo
:
LangGeniusVersionResponse
isBordered
:
boolean
}
const
navClassName
=
`
flex items-center relative mr-3 px-3 h-8 rounded-xl
font-medium text-sm
...
...
@@ -32,18 +26,16 @@ const headerEnvClassName: { [k: string]: string } = {
DEVELOPMENT
:
'bg-[#FEC84B] border-[#FDB022] text-[#93370D]'
,
TESTING
:
'bg-[#A5F0FC] border-[#67E3F9] text-[#164C63]'
,
}
const
Header
:
FC
<
IHeaderProps
>
=
({
userProfile
,
onLogout
,
langeniusVersionInfo
,
isBordered
,
})
=>
{
const
Header
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
pathname
=
usePathname
()
const
{
userProfile
,
langeniusVersionInfo
}
=
useAppContext
()
const
showEnvTag
=
langeniusVersionInfo
.
current_env
===
'TESTING'
||
langeniusVersionInfo
.
current_env
===
'DEVELOPMENT'
const
selectedSegment
=
useSelectedLayoutSegment
()
const
isPluginsComingSoon
=
selectedSegment
===
'plugins-coming-soon'
const
isExplore
=
selectedSegment
===
'explore'
const
[
starCount
,
setStarCount
]
=
useState
(
0
)
const
isBordered
=
[
'/apps'
,
'/datasets'
].
includes
(
pathname
)
useEffect
(()
=>
{
globalThis
.
fetch
(
'https://api.github.com/repos/langgenius/dify'
).
then
(
res
=>
res
.
json
()).
then
((
data
:
GithubRepo
)
=>
{
...
...
@@ -136,7 +128,7 @@ const Header: FC<IHeaderProps> = ({
)
}
<
WorkspaceProvider
>
<
AccountDropdown
userProfile=
{
userProfile
}
onLogout=
{
onLogout
}
langeniusVersionInfo=
{
langeniusVersionInfo
}
/>
<
AccountDropdown
userProfile=
{
userProfile
}
langeniusVersionInfo=
{
langeniusVersionInfo
}
/>
</
WorkspaceProvider
>
</
div
>
</
div
>
...
...
web/app/components/sentry-initor.tsx
0 → 100644
View file @
d05f7910
'use client'
import
{
useEffect
}
from
'react'
import
*
as
Sentry
from
'@sentry/react'
const
isDevelopment
=
process
.
env
.
NODE_ENV
===
'development'
const
SentryInit
=
({
children
,
}:
{
children
:
React
.
ReactElement
})
=>
{
useEffect
(()
=>
{
const
SENTRY_DSN
=
document
?.
body
?.
getAttribute
(
'data-public-sentry-dsn'
)
if
(
!
isDevelopment
&&
SENTRY_DSN
)
{
Sentry
.
init
({
dsn
:
SENTRY_DSN
,
integrations
:
[
new
Sentry
.
BrowserTracing
({
}),
new
Sentry
.
Replay
(),
],
tracesSampleRate
:
0.1
,
replaysSessionSampleRate
:
0.1
,
replaysOnErrorSampleRate
:
1.0
,
})
}
},
[])
return
children
}
export
default
SentryInit
web/app/components/swr-initor.tsx
0 → 100644
View file @
d05f7910
'use client'
import
{
SWRConfig
}
from
'swr'
import
type
{
ReactNode
}
from
'react'
type
SwrInitorProps
=
{
children
:
ReactNode
}
const
SwrInitor
=
({
children
,
}:
SwrInitorProps
)
=>
{
return
(
<
SWRConfig
value=
{
{
shouldRetryOnError
:
false
,
}
}
>
{
children
}
</
SWRConfig
>
)
}
export
default
SwrInitor
web/app/layout.tsx
View file @
d05f7910
import
I18nServer
from
'./components/i18n-server'
import
SentryInitor
from
'./components/sentry-initor'
import
{
getLocaleOnServer
}
from
'@/i18n/server'
import
'./styles/globals.css'
...
...
@@ -14,6 +15,7 @@ const LocaleLayout = ({
children
:
React
.
ReactNode
})
=>
{
const
locale
=
getLocaleOnServer
()
return
(
<
html
lang=
{
locale
??
'en'
}
className=
"h-full"
>
<
body
...
...
@@ -23,8 +25,10 @@ const LocaleLayout = ({
data
-
public
-
edition=
{
process
.
env
.
NEXT_PUBLIC_EDITION
}
data
-
public
-
sentry
-
dsn=
{
process
.
env
.
NEXT_PUBLIC_SENTRY_DSN
}
>
{
/* @ts-expect-error Async Server Component */
}
<
I18nServer
locale=
{
locale
}
>
{
children
}
</
I18nServer
>
<
SentryInitor
>
{
/* @ts-expect-error Async Server Component */
}
<
I18nServer
locale=
{
locale
}
>
{
children
}
</
I18nServer
>
</
SentryInitor
>
</
body
>
</
html
>
)
...
...
web/context/app-context.tsx
View file @
d05f7910
'use client'
import
{
createRef
,
useEffect
,
useRef
,
useState
}
from
'react'
import
useSWR
from
'swr'
import
{
createContext
,
useContext
,
useContextSelector
}
from
'use-context-selector'
import
type
{
FC
,
ReactNode
}
from
'react'
import
{
fetchAppList
}
from
'@/service/apps'
import
Loading
from
'@/app/components/base/loading'
import
{
fetchLanggeniusVersion
,
fetchUserProfile
}
from
'@/service/common'
import
type
{
App
}
from
'@/types/app'
import
type
{
UserProfileResponse
}
from
'@/models/common'
import
{
createRef
,
FC
,
PropsWithChildren
}
from
'react'
export
const
useSelector
=
<
T
extends
any
>
(selector: (value: AppContextValue) =
>
T): T =
>
useContextSelector(AppContext, selector);
import
type
{
LangGeniusVersionResponse
,
UserProfileResponse
}
from
'@/models/common'
export
type
AppContextValue
=
{
apps
:
App
[]
mutateApps
:
()
=>
void
userProfile
:
UserProfileResponse
mutateUserProfile
:
()
=>
void
pageContainerRef
:
React
.
RefObject
<
HTMLDivElement
>
,
useSelector
:
typeof
useSelector
,
pageContainerRef
:
React
.
RefObject
<
HTMLDivElement
>
langeniusVersionInfo
:
LangGeniusVersionResponse
useSelector
:
typeof
useSelector
}
const
AppContext
=
createContext
<
AppContextValue
>
({
...
...
@@ -27,18 +30,59 @@ const AppContext = createContext<AppContextValue>({
},
mutateUserProfile
:
()
=>
{
},
pageContainerRef
:
createRef
(),
langeniusVersionInfo
:
{
current_env
:
''
,
current_version
:
''
,
latest_version
:
''
,
release_date
:
''
,
release_notes
:
''
,
version
:
''
,
can_auto_update
:
false
,
},
useSelector
,
})
export type AppContextProviderProps = PropsWithChildren
<
{
value
:
Omit
<
AppContextValue
,
'
useSelector
'
>
}
>
export
function
useSelector
<
T
>
(
selector
:
(
value
:
AppContextValue
)
=>
T
):
T
{
return
useContextSelector
(
AppContext
,
selector
)
}
export
type
AppContextProviderProps
=
{
children
:
ReactNode
}
export
const
AppContextProvider
:
FC
<
AppContextProviderProps
>
=
({
children
})
=>
{
const
pageContainerRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
{
data
:
appList
,
mutate
:
mutateApps
}
=
useSWR
({
url
:
'/apps'
,
params
:
{
page
:
1
}
},
fetchAppList
)
const
{
data
:
userProfileResponse
,
mutate
:
mutateUserProfile
}
=
useSWR
({
url
:
'/account/profile'
,
params
:
{}
},
fetchUserProfile
)
const
[
userProfile
,
setUserProfile
]
=
useState
<
UserProfileResponse
>
()
const
[
langeniusVersionInfo
,
setLangeniusVersionInfo
]
=
useState
<
LangGeniusVersionResponse
>
()
const
updateUserProfileAndVersion
=
async
()
=>
{
if
(
userProfileResponse
&&
!
userProfileResponse
.
bodyUsed
)
{
const
result
=
await
userProfileResponse
.
json
()
setUserProfile
(
result
)
const
current_version
=
userProfileResponse
.
headers
.
get
(
'x-version'
)
const
current_env
=
userProfileResponse
.
headers
.
get
(
'x-env'
)
const
versionData
=
await
fetchLanggeniusVersion
({
url
:
'/version'
,
params
:
{
current_version
}
})
setLangeniusVersionInfo
({
...
versionData
,
current_version
,
latest_version
:
versionData
.
version
,
current_env
})
}
}
useEffect
(()
=>
{
updateUserProfileAndVersion
()
},
[
userProfileResponse
])
export const AppContextProvider: FC
<
AppContextProviderProps
>
= (
{
value
,
children
}
) =
>
(
<
AppContext
.
Provider
value=
{
{
...
value
,
useSelector
}
}
>
{
children
}
</
AppContext
.
Provider
>
)
if
(
!
appList
||
!
userProfile
||
!
langeniusVersionInfo
)
return
<
Loading
type=
'app'
/>
return
(
<
AppContext
.
Provider
value=
{
{
apps
:
appList
.
data
,
mutateApps
,
userProfile
,
mutateUserProfile
,
pageContainerRef
,
langeniusVersionInfo
,
useSelector
}
}
>
<
div
ref=
{
pageContainerRef
}
className=
'relative flex flex-col h-full overflow-auto bg-gray-100'
>
{
children
}
</
div
>
</
AppContext
.
Provider
>
)
}
export
const
useAppContext
=
()
=>
useContext
(
AppContext
)
...
...
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