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
39ea967b
Unverified
Commit
39ea967b
authored
Jun 29, 2023
by
zxhlyh
Committed by
GitHub
Jun 29, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refact common layout (#490)
parent
da04ff04
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 @
da04ff04
'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 @
39ea967b
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 @
39ea967b
'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 @
39ea967b
'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 @
39ea967b
'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 @
39ea967b
'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 @
39ea967b
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 @
39ea967b
'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