Bläddra i källkod

Feat/header ssr (#594)

zxhlyh 1 år sedan
förälder
incheckning
3e1d5ac51b
50 ändrade filer med 916 tillägg och 275 borttagningar
  1. 4 1
      web/app/(commonLayout)/layout.tsx
  2. 15 13
      web/app/components/base/avatar/index.tsx
  3. 8 0
      web/app/components/base/icons/assets/public/common/dify.svg
  4. 5 0
      web/app/components/base/icons/assets/public/common/github.svg
  5. 5 0
      web/app/components/base/icons/assets/vender/line/arrows/arrow-up-right.svg
  6. 5 0
      web/app/components/base/icons/assets/vender/line/arrows/chevron-down.svg
  7. 5 0
      web/app/components/base/icons/assets/vender/line/arrows/chevron-right.svg
  8. 5 0
      web/app/components/base/icons/assets/vender/line/general/check.svg
  9. 5 0
      web/app/components/base/icons/assets/vender/line/general/log-out-01.svg
  10. 5 0
      web/app/components/base/icons/assets/vender/solid/development/terminal-square.svg
  11. 5 0
      web/app/components/base/icons/assets/vender/solid/education/beaker-02.svg
  12. 62 0
      web/app/components/base/icons/src/public/common/Dify.json
  13. 14 0
      web/app/components/base/icons/src/public/common/Dify.tsx
  14. 36 0
      web/app/components/base/icons/src/public/common/Github.json
  15. 14 0
      web/app/components/base/icons/src/public/common/Github.tsx
  16. 2 0
      web/app/components/base/icons/src/public/common/index.ts
  17. 39 0
      web/app/components/base/icons/src/vender/line/arrows/ArrowUpRight.json
  18. 14 0
      web/app/components/base/icons/src/vender/line/arrows/ArrowUpRight.tsx
  19. 39 0
      web/app/components/base/icons/src/vender/line/arrows/ChevronDown.json
  20. 14 0
      web/app/components/base/icons/src/vender/line/arrows/ChevronDown.tsx
  21. 39 0
      web/app/components/base/icons/src/vender/line/arrows/ChevronRight.json
  22. 14 0
      web/app/components/base/icons/src/vender/line/arrows/ChevronRight.tsx
  23. 3 0
      web/app/components/base/icons/src/vender/line/arrows/index.ts
  24. 39 0
      web/app/components/base/icons/src/vender/line/general/Check.json
  25. 14 0
      web/app/components/base/icons/src/vender/line/general/Check.tsx
  26. 39 0
      web/app/components/base/icons/src/vender/line/general/LogOut01.json
  27. 14 0
      web/app/components/base/icons/src/vender/line/general/LogOut01.tsx
  28. 2 0
      web/app/components/base/icons/src/vender/line/general/index.ts
  29. 38 0
      web/app/components/base/icons/src/vender/solid/development/TerminalSquare.json
  30. 14 0
      web/app/components/base/icons/src/vender/solid/development/TerminalSquare.tsx
  31. 1 0
      web/app/components/base/icons/src/vender/solid/development/index.ts
  32. 38 0
      web/app/components/base/icons/src/vender/solid/education/Beaker02.json
  33. 14 0
      web/app/components/base/icons/src/vender/solid/education/Beaker02.tsx
  34. 1 0
      web/app/components/base/icons/src/vender/solid/education/index.ts
  35. 34 0
      web/app/components/header/HeaderWrapper.tsx
  36. 1 1
      web/app/components/header/account-about/index.module.css
  37. 8 8
      web/app/components/header/account-about/index.tsx
  38. 93 92
      web/app/components/header/account-dropdown/index.tsx
  39. 19 18
      web/app/components/header/account-dropdown/workplace-selector/index.tsx
  40. 2 0
      web/app/components/header/app-nav/index.tsx
  41. 0 3
      web/app/components/header/assets/beaker.svg
  42. 0 3
      web/app/components/header/assets/github-icon.svg
  43. 2 0
      web/app/components/header/dataset-nav/index.tsx
  44. 46 0
      web/app/components/header/env-nav/index.tsx
  45. 37 0
      web/app/components/header/explore-nav/index.tsx
  46. 40 0
      web/app/components/header/github-star/index.tsx
  47. 0 15
      web/app/components/header/index.module.css
  48. 25 120
      web/app/components/header/index.tsx
  49. 37 0
      web/app/components/header/plugin-nav/index.tsx
  50. 1 1
      web/context/app-context.tsx

+ 4 - 1
web/app/(commonLayout)/layout.tsx

@@ -3,6 +3,7 @@ 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 HeaderWrapper from '@/app/components/header/HeaderWrapper'
 import Header from '@/app/components/header'
 
 const Layout = ({ children }: { children: ReactNode }) => {
@@ -11,7 +12,9 @@ const Layout = ({ children }: { children: ReactNode }) => {
       <GA gaType={GaType.admin} />
       <SwrInitor>
         <AppContextProvider>
-          <Header />
+          <HeaderWrapper>
+            <Header />
+          </HeaderWrapper>
           {children}
         </AppContextProvider>
       </SwrInitor>

+ 15 - 13
web/app/components/base/avatar/index.tsx

@@ -1,39 +1,41 @@
 'use client'
 import cn from 'classnames'
 
-interface IAvatarProps {
+type AvatarProps = {
   name: string
   avatar?: string
   size?: number
   className?: string
+  textClassName?: string
 }
 const Avatar = ({
   name,
   avatar,
   size = 30,
-  className
-}: IAvatarProps) => {
-  const avatarClassName = `shrink-0 flex items-center rounded-full bg-primary-600`
-  const style = { width: `${size}px`, height:`${size}px`, fontSize: `${size}px`, lineHeight: `${size}px` }
+  className,
+  textClassName,
+}: AvatarProps) => {
+  const avatarClassName = 'shrink-0 flex items-center rounded-full bg-primary-600'
+  const style = { width: `${size}px`, height: `${size}px`, fontSize: `${size}px`, lineHeight: `${size}px` }
 
   if (avatar) {
     return (
-      <img 
-        className={cn(avatarClassName, className)} 
+      <img
+        className={cn(avatarClassName, className)}
         style={style}
-        alt={name} 
+        alt={name}
         src={avatar}
       />
     )
   }
 
   return (
-    <div 
-      className={cn(avatarClassName, className)} 
+    <div
+      className={cn(avatarClassName, className)}
       style={style}
     >
-      <div 
-        className={`text-center text-white scale-[0.4]`} 
+      <div
+        className={cn(textClassName, 'text-center text-white scale-[0.4]')}
         style={style}
       >
         {name[0].toLocaleUpperCase()}
@@ -42,4 +44,4 @@ const Avatar = ({
   )
 }
 
-export default Avatar
+export default Avatar

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 8 - 0
web/app/components/base/icons/assets/public/common/dify.svg


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5 - 0
web/app/components/base/icons/assets/public/common/github.svg


+ 5 - 0
web/app/components/base/icons/assets/vender/line/arrows/arrow-up-right.svg

@@ -0,0 +1,5 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="arrow-up-right">
+<path id="Icon" d="M4.08325 9.91665L9.91659 4.08331M9.91659 4.08331H4.08325M9.91659 4.08331V9.91665" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

+ 5 - 0
web/app/components/base/icons/assets/vender/line/arrows/chevron-down.svg

@@ -0,0 +1,5 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="chevron-down">
+<path id="Icon" d="M3 4.5L6 7.5L9 4.5" stroke="#344054" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

+ 5 - 0
web/app/components/base/icons/assets/vender/line/arrows/chevron-right.svg

@@ -0,0 +1,5 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="chevron-right">
+<path id="Icon" d="M5.25 10.5L8.75 7L5.25 3.5" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

+ 5 - 0
web/app/components/base/icons/assets/vender/line/general/check.svg

@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="check">
+<path id="Icon" d="M13.3334 4L6.00008 11.3333L2.66675 8" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

+ 5 - 0
web/app/components/base/icons/assets/vender/line/general/log-out-01.svg

@@ -0,0 +1,5 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="log-out-01">
+<path id="Icon" d="M9.33333 9.91667L12.25 7M12.25 7L9.33333 4.08333M12.25 7H5.25M5.25 1.75H4.55C3.56991 1.75 3.07986 1.75 2.70552 1.94074C2.37623 2.10852 2.10852 2.37623 1.94074 2.70552C1.75 3.07986 1.75 3.56991 1.75 4.55V9.45C1.75 10.4301 1.75 10.9201 1.94074 11.2945C2.10852 11.6238 2.37623 11.8915 2.70552 12.0593C3.07986 12.25 3.56991 12.25 4.55 12.25H5.25" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5 - 0
web/app/components/base/icons/assets/vender/solid/development/terminal-square.svg


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5 - 0
web/app/components/base/icons/assets/vender/solid/education/beaker-02.svg


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 62 - 0
web/app/components/base/icons/src/public/common/Dify.json


+ 14 - 0
web/app/components/base/icons/src/public/common/Dify.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Dify.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 36 - 0
web/app/components/base/icons/src/public/common/Github.json


+ 14 - 0
web/app/components/base/icons/src/public/common/Github.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Github.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 2 - 0
web/app/components/base/icons/src/public/common/index.ts

@@ -0,0 +1,2 @@
+export { default as Dify } from './Dify'
+export { default as Github } from './Github'

+ 39 - 0
web/app/components/base/icons/src/vender/line/arrows/ArrowUpRight.json

@@ -0,0 +1,39 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "14",
+			"height": "14",
+			"viewBox": "0 0 14 14",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "arrow-up-right"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M4.08325 9.91665L9.91659 4.08331M9.91659 4.08331H4.08325M9.91659 4.08331V9.91665",
+							"stroke": "currentColor",
+							"stroke-width": "1.25",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "ArrowUpRight"
+}

+ 14 - 0
web/app/components/base/icons/src/vender/line/arrows/ArrowUpRight.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './ArrowUpRight.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 39 - 0
web/app/components/base/icons/src/vender/line/arrows/ChevronDown.json

@@ -0,0 +1,39 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "12",
+			"height": "12",
+			"viewBox": "0 0 12 12",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "chevron-down"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M3 4.5L6 7.5L9 4.5",
+							"stroke": "currentColor",
+							"stroke-width": "1.5",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "ChevronDown"
+}

+ 14 - 0
web/app/components/base/icons/src/vender/line/arrows/ChevronDown.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './ChevronDown.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 39 - 0
web/app/components/base/icons/src/vender/line/arrows/ChevronRight.json

@@ -0,0 +1,39 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "14",
+			"height": "14",
+			"viewBox": "0 0 14 14",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "chevron-right"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M5.25 10.5L8.75 7L5.25 3.5",
+							"stroke": "currentColor",
+							"stroke-width": "1.25",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "ChevronRight"
+}

+ 14 - 0
web/app/components/base/icons/src/vender/line/arrows/ChevronRight.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './ChevronRight.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 3 - 0
web/app/components/base/icons/src/vender/line/arrows/index.ts

@@ -1,2 +1,5 @@
 export { default as ArrowNarrowLeft } from './ArrowNarrowLeft'
+export { default as ArrowUpRight } from './ArrowUpRight'
+export { default as ChevronDown } from './ChevronDown'
+export { default as ChevronRight } from './ChevronRight'
 export { default as RefreshCw05 } from './RefreshCw05'

+ 39 - 0
web/app/components/base/icons/src/vender/line/general/Check.json

@@ -0,0 +1,39 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "16",
+			"height": "16",
+			"viewBox": "0 0 16 16",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "check"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M13.3334 4L6.00008 11.3333L2.66675 8",
+							"stroke": "currentColor",
+							"stroke-width": "1.5",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "Check"
+}

+ 14 - 0
web/app/components/base/icons/src/vender/line/general/Check.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Check.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 39 - 0
web/app/components/base/icons/src/vender/line/general/LogOut01.json

@@ -0,0 +1,39 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "14",
+			"height": "14",
+			"viewBox": "0 0 14 14",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "log-out-01"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M9.33333 9.91667L12.25 7M12.25 7L9.33333 4.08333M12.25 7H5.25M5.25 1.75H4.55C3.56991 1.75 3.07986 1.75 2.70552 1.94074C2.37623 2.10852 2.10852 2.37623 1.94074 2.70552C1.75 3.07986 1.75 3.56991 1.75 4.55V9.45C1.75 10.4301 1.75 10.9201 1.94074 11.2945C2.10852 11.6238 2.37623 11.8915 2.70552 12.0593C3.07986 12.25 3.56991 12.25 4.55 12.25H5.25",
+							"stroke": "currentColor",
+							"stroke-width": "1.25",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "LogOut01"
+}

+ 14 - 0
web/app/components/base/icons/src/vender/line/general/LogOut01.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './LogOut01.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 2 - 0
web/app/components/base/icons/src/vender/line/general/index.ts

@@ -1,4 +1,6 @@
+export { default as Check } from './Check'
 export { default as Loading02 } from './Loading02'
+export { default as LogOut01 } from './LogOut01'
 export { default as Trash03 } from './Trash03'
 export { default as XClose } from './XClose'
 export { default as X } from './X'

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 38 - 0
web/app/components/base/icons/src/vender/solid/development/TerminalSquare.json


+ 14 - 0
web/app/components/base/icons/src/vender/solid/development/TerminalSquare.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './TerminalSquare.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 1 - 0
web/app/components/base/icons/src/vender/solid/development/index.ts

@@ -2,3 +2,4 @@ export { default as Container } from './Container'
 export { default as Database02 } from './Database02'
 export { default as Database03 } from './Database03'
 export { default as PuzzlePiece01 } from './PuzzlePiece01'
+export { default as TerminalSquare } from './TerminalSquare'

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 38 - 0
web/app/components/base/icons/src/vender/solid/education/Beaker02.json


+ 14 - 0
web/app/components/base/icons/src/vender/solid/education/Beaker02.tsx

@@ -0,0 +1,14 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Beaker02.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
+  props,
+  ref,
+) => <IconBase {...props} ref={ref} data={data as IconData} />)
+
+export default Icon

+ 1 - 0
web/app/components/base/icons/src/vender/solid/education/index.ts

@@ -0,0 +1 @@
+export { default as Beaker02 } from './Beaker02'

+ 34 - 0
web/app/components/header/HeaderWrapper.tsx

@@ -0,0 +1,34 @@
+'use client'
+import classNames from 'classnames'
+import { usePathname } from 'next/navigation'
+import s from './index.module.css'
+import { useAppContext } from '@/context/app-context'
+
+type HeaderWrapperProps = {
+  children: React.ReactNode
+}
+
+const HeaderWrapper = ({
+  children,
+}: HeaderWrapperProps) => {
+  const pathname = usePathname()
+  const { langeniusVersionInfo } = useAppContext()
+  const isBordered = ['/apps', '/datasets'].includes(pathname)
+
+  return (
+    <div className={classNames(
+      'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14',
+      s.header,
+      isBordered ? 'border-b border-gray-200' : '',
+    )}
+    >
+      <div className={classNames(
+        s[`header-${langeniusVersionInfo.current_env}`],
+        'flex flex-1 items-center justify-between px-4',
+      )}>
+        {children}
+      </div>
+    </div>
+  )
+}
+export default HeaderWrapper

+ 1 - 1
web/app/components/header/account-about/index.module.css

@@ -1,7 +1,7 @@
 .logo-icon {
   background: url(../assets/logo-icon.png) center center no-repeat;
   background-size: 32px;
-  box-shadow: 0px 4px 6px -1px rgba(0, 0, 0, 0.05), 0px 2px 4px -2px rgba(0, 0, 0, 0.05);
+  box-shadow: 0px 2px 4px -2px rgba(0, 0, 0, 0.05), 0px 4px 6px -1px rgba(0, 0, 0, 0.05);
 }
 
 .logo-text {

+ 8 - 8
web/app/components/header/account-about/index.tsx

@@ -1,10 +1,11 @@
 'use client'
 import { useTranslation } from 'react-i18next'
-import { XMarkIcon } from '@heroicons/react/24/outline'
 import classNames from 'classnames'
 import Link from 'next/link'
 import s from './index.module.css'
 import Modal from '@/app/components/base/modal'
+import { XClose } from '@/app/components/base/icons/src/vender/line/general'
+import { Dify } from '@/app/components/base/icons/src/public/common'
 import type { LangGeniusVersionResponse } from '@/models/common'
 import { IS_CE_EDITION } from '@/config'
 
@@ -30,16 +31,15 @@ export default function AccountAbout({
       className={s.modal}
     >
       <div className='relative'>
-        <XMarkIcon className='absolute top-0 -right-2 w-4 h-4 cursor-pointer' onClick={onCancel} />
+        <div className='absolute -top-2 -right-4 flex justify-center items-center w-8 h-8 cursor-pointer' onClick={onCancel}>
+          <XClose className='w-4 h-4 text-gray-500' />
+        </div>
         <div>
           <div className={classNames(
             s['logo-icon'],
-            'mx-auto mb-3 w-12 h-12 bg-white rounded-xl border border-gray-200',
-          )} />
-          <div className={classNames(
-            s['logo-text'],
-            'mx-auto mb-2',
+            'mx-auto mb-3 w-12 h-12 bg-white rounded-xl border-[0.5px] border-gray-200',
           )} />
+          <Dify className='mx-auto mb-2' />
           <div className='mb-3 text-center text-xs font-normal text-gray-500'>Version {langeniusVersionInfo?.current_version}</div>
           <div className='mb-4 text-center text-xs font-normal text-gray-700'>
             <div>© 2023 LangGenius, Inc., Contributors.</div>
@@ -55,7 +55,7 @@ export default function AccountAbout({
             </div>
           </div>
         </div>
-        <div className='mb-4 h-0 border-[0.5px] border-gray-200' />
+        <div className='mb-4 -mx-8 h-[0.5px] bg-gray-200' />
         <div className='flex justify-between items-center'>
           <div className='text-xs font-medium text-gray-800'>
             {

+ 93 - 92
web/app/components/header/account-dropdown/index.tsx

@@ -5,26 +5,22 @@ import { useRouter } from 'next/navigation'
 import { useContext } from 'use-context-selector'
 import classNames from 'classnames'
 import Link from 'next/link'
-import { ArrowRightOnRectangleIcon, ArrowTopRightOnSquareIcon, ChevronDownIcon } from '@heroicons/react/24/solid'
 import { Menu, Transition } from '@headlessui/react'
 import Indicator from '../indicator'
 import AccountSetting from '../account-setting'
 import AccountAbout from '../account-about'
 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'
+import { useAppContext } from '@/context/app-context'
+import { ArrowUpRight, ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
+import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
 
-type IAppSelectorProps = {
-  userProfile: UserProfileResponse
-  langeniusVersionInfo: LangGeniusVersionResponse
-}
-
-export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppSelectorProps) {
+export default function AppSelector() {
   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
+    flex items-center w-full h-9 px-3 text-gray-700 text-[14px]
+    rounded-lg font-normal hover:bg-gray-50 cursor-pointer
   `
   const router = useRouter()
   const [settingVisible, setSettingVisible] = useState(false)
@@ -32,6 +28,7 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS
 
   const { locale } = useContext(I18n)
   const { t } = useTranslation()
+  const { userProfile, langeniusVersionInfo } = useAppContext()
 
   const handleLogout = async () => {
     await logout({
@@ -44,90 +41,94 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS
   return (
     <div className="">
       <Menu as="div" className="relative inline-block text-left">
-        <div>
-          <Menu.Button
-            className="
-              inline-flex items-center h-[38px]
-              rounded-xl pl-2 pr-2.5 text-[14px] font-normal
-              text-gray-800 hover:bg-gray-200
-            "
-          >
-            <Avatar name={userProfile.name} className='mr-2' />
-            {userProfile.name}
-            <ChevronDownIcon
-              className="w-3 h-3 ml-1"
-              aria-hidden="true"
-            />
-          </Menu.Button>
-        </div>
-        <Transition
-          as={Fragment}
-          enter="transition ease-out duration-100"
-          enterFrom="transform opacity-0 scale-95"
-          enterTo="transform opacity-100 scale-100"
-          leave="transition ease-in duration-75"
-          leaveFrom="transform opacity-100 scale-100"
-          leaveTo="transform opacity-0 scale-95"
-        >
-          <Menu.Items
-            className="
-              absolute right-0 mt-1.5 w-60 max-w-80
-              divide-y divide-gray-100 origin-top-right rounded-lg bg-white
-              shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)]
-            "
-          >
-            <Menu.Item>
-              <div className='flex flex-nowrap items-center px-4 py-[13px]'>
-                <Avatar name={userProfile.name} size={36} className='mr-3' />
-                <div className='grow'>
-                  <div className='leading-5 font-normal text-[14px] text-gray-800 break-all'>{userProfile.name}</div>
-                  <div className='leading-[18px] text-xs font-normal text-gray-500 break-all'>{userProfile.email}</div>
-                </div>
-              </div>
-            </Menu.Item>
-            <div className='px-1 py-1'>
-              <div className='mt-2 px-3 text-xs font-medium text-gray-500'>{t('common.userProfile.workspace')}</div>
-              <WorkplaceSelector />
-            </div>
-            <div className="px-1 py-1">
-              <Menu.Item>
-                <div className={itemClassName} onClick={() => setSettingVisible(true)}>
-                  <div>{t('common.userProfile.settings')}</div>
-                </div>
-              </Menu.Item>
-              <Menu.Item>
-                <Link
-                  className={classNames(itemClassName, 'group justify-between')}
-                  href={
-                    locale === 'zh-Hans' ? 'https://docs.dify.ai/v/zh-hans/' : 'https://docs.dify.ai/'
-                  }
-                  target='_blank'>
-                  <div>{t('common.userProfile.helpCenter')}</div>
-                  <ArrowTopRightOnSquareIcon className='hidden w-4 h-4 group-hover:flex' />
-                </Link>
-              </Menu.Item>
-              <Menu.Item>
-                <div className={classNames(itemClassName, 'justify-between')} onClick={() => setAboutVisible(true)}>
-                  <div>{t('common.userProfile.about')}</div>
-                  <div className='flex items-center'>
-                    <div className='mr-2 text-xs font-normal text-gray-500'>{langeniusVersionInfo.current_version}</div>
-                    <Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
-                  </div>
-                </div>
-              </Menu.Item>
-            </div>
-            <Menu.Item>
-              <div className='p-1' onClick={() => handleLogout()}>
-                <div
-                  className='flex items-center justify-between h-12 px-3 rounded-lg cursor-pointer group hover:bg-gray-100'
+        {
+          ({ open }) => (
+            <>
+              <div>
+                <Menu.Button
+                  className={`
+                    inline-flex items-center
+                    rounded-[20px] py-1 pr-2.5 pl-1 text-sm
+                  text-gray-700 hover:bg-gray-200
+                    ${open && 'bg-gray-200'}
+                  `}
                 >
-                  <div className='font-normal text-[14px] text-gray-700'>{t('common.userProfile.logout')}</div>
-                  <ArrowRightOnRectangleIcon className='hidden w-4 h-4 group-hover:flex' />
-                </div>
+                  <Avatar name={userProfile.name} className='mr-2' size={32} />
+                  {userProfile.name}
+                  <ChevronDown className="w-3 h-3 ml-1 text-gray-700"/>
+                </Menu.Button>
               </div>
-            </Menu.Item>
-          </Menu.Items>
-        </Transition>
+              <Transition
+                as={Fragment}
+                enter="transition ease-out duration-100"
+                enterFrom="transform opacity-0 scale-95"
+                enterTo="transform opacity-100 scale-100"
+                leave="transition ease-in duration-75"
+                leaveFrom="transform opacity-100 scale-100"
+                leaveTo="transform opacity-0 scale-95"
+              >
+                <Menu.Items
+                  className="
+                    absolute right-0 mt-1.5 w-60 max-w-80
+                    divide-y divide-gray-100 origin-top-right rounded-lg bg-white
+                    shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)]
+                  "
+                >
+                  <Menu.Item>
+                    <div className='flex flex-nowrap items-center px-4 py-[13px]'>
+                      <Avatar name={userProfile.name} size={36} className='mr-3' />
+                      <div className='grow'>
+                        <div className='leading-5 font-normal text-[14px] text-gray-800 break-all'>{userProfile.name}</div>
+                        <div className='leading-[18px] text-xs font-normal text-gray-500 break-all'>{userProfile.email}</div>
+                      </div>
+                    </div>
+                  </Menu.Item>
+                  <div className='px-1 py-1'>
+                    <div className='mt-2 px-3 text-xs font-medium text-gray-500'>{t('common.userProfile.workspace')}</div>
+                    <WorkplaceSelector />
+                  </div>
+                  <div className="px-1 py-1">
+                    <Menu.Item>
+                      <div className={itemClassName} onClick={() => setSettingVisible(true)}>
+                        <div>{t('common.userProfile.settings')}</div>
+                      </div>
+                    </Menu.Item>
+                    <Menu.Item>
+                      <Link
+                        className={classNames(itemClassName, 'group justify-between')}
+                        href={
+                          locale === 'zh-Hans' ? 'https://docs.dify.ai/v/zh-hans/' : 'https://docs.dify.ai/'
+                        }
+                        target='_blank'>
+                        <div>{t('common.userProfile.helpCenter')}</div>
+                        <ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
+                      </Link>
+                    </Menu.Item>
+                    <Menu.Item>
+                      <div className={classNames(itemClassName, 'justify-between')} onClick={() => setAboutVisible(true)}>
+                        <div>{t('common.userProfile.about')}</div>
+                        <div className='flex items-center'>
+                          <div className='mr-2 text-xs font-normal text-gray-500'>{langeniusVersionInfo.current_version}</div>
+                          <Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
+                        </div>
+                      </div>
+                    </Menu.Item>
+                  </div>
+                  <Menu.Item>
+                    <div className='p-1' onClick={() => handleLogout()}>
+                      <div
+                        className='flex items-center justify-between h-9 px-3 rounded-lg cursor-pointer group hover:bg-gray-50'
+                      >
+                        <div className='font-normal text-[14px] text-gray-700'>{t('common.userProfile.logout')}</div>
+                        <LogOut01 className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
+                      </div>
+                    </div>
+                  </Menu.Item>
+                </Menu.Items>
+              </Transition>
+            </>
+          )
+        }
       </Menu>
       {
         settingVisible && <AccountSetting onCancel={() => setSettingVisible(false)} />

+ 19 - 18
web/app/components/header/account-dropdown/workplace-selector/index.tsx

@@ -1,20 +1,21 @@
 import { Fragment } from 'react'
-import { switchWorkspace } from '@/service/common'
-import { Menu, Transition } from '@headlessui/react'
-import { ChevronRightIcon, CheckIcon } from '@heroicons/react/24/outline'
-import cn from 'classnames'
-import s from './index.module.css'
 import { useContext } from 'use-context-selector'
-import { ToastContext } from '@/app/components/base/toast'
 import { useTranslation } from 'react-i18next'
 import { useRouter } from 'next/navigation'
+import { Menu, Transition } from '@headlessui/react'
+import cn from 'classnames'
+import s from './index.module.css'
+import { switchWorkspace } from '@/service/common'
 import { useWorkspacesContext } from '@/context/workspace-context'
+import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
+import { Check } from '@/app/components/base/icons/src/vender/line/general'
+import { ToastContext } from '@/app/components/base/toast'
 
 const itemClassName = `
   flex items-center px-3 py-2 h-10 cursor-pointer
 `
 const itemIconClassName = `
-  shrink-0 mr-2 w-6 h-6 bg-[#EFF4FF] rounded-md
+  shrink-0 mr-2 flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600
 `
 const itemNameClassName = `
   grow mr-2 text-sm text-gray-700 text-left
@@ -32,12 +33,12 @@ const WorkplaceSelector = () => {
 
   const handleSwitchWorkspace = async (tenant_id: string) => {
     try {
-      await switchWorkspace({ url: `/workspaces/switch`, body: { tenant_id } })
+      await switchWorkspace({ url: '/workspaces/switch', body: { tenant_id } })
       notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
       router.replace('/apps')
-    } catch (e) {
+    }
+    catch (e) {
       notify({ type: 'error', message: t('common.provider.saveFailed') })
-    } finally {
     }
   }
 
@@ -49,12 +50,12 @@ const WorkplaceSelector = () => {
             <Menu.Button className={cn(
               `
                 ${itemClassName} w-full
-                group hover:bg-gray-50 cursor-pointer ${open && 'bg-gray-50'}
-              `
+                group hover:bg-gray-50 cursor-pointer ${open && 'bg-gray-50'} rounded-lg
+              `,
             )}>
-              <div className={itemIconClassName} />
+              <div className={itemIconClassName}>{currentWrokspace?.name[0].toLocaleUpperCase()}</div>
               <div className={`${itemNameClassName} truncate`}>{currentWrokspace?.name}</div>
-              <ChevronRightIcon className='shrink-0 w-[14px] h-[14px]' />
+              <ChevronRight className='shrink-0 w-[14px] h-[14px] text-gray-500' />
             </Menu.Button>
             <Transition
               as={Fragment}
@@ -71,16 +72,16 @@ const WorkplaceSelector = () => {
                     absolute top-[1px] min-w-[200px] z-10 bg-white border-[0.5px] border-gray-200
                     divide-y divide-gray-100 origin-top-right rounded-xl
                   `,
-                  s.popup
+                  s.popup,
                 )}
               >
                 <div className="px-1 py-1">
                   {
                     workspaces.map(workspace => (
                       <div className={itemClassName} key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
-                        <div className={itemIconClassName} />
+                        <div className={itemIconClassName}>{workspace.name[0].toLocaleUpperCase()}</div>
                         <div className={itemNameClassName}>{workspace.name}</div>
-                        {workspace.current && <CheckIcon className={itemCheckClassName} />}
+                        {workspace.current && <Check className={itemCheckClassName} />}
                       </div>
                     ))
                   }
@@ -94,4 +95,4 @@ const WorkplaceSelector = () => {
   )
 }
 
-export default WorkplaceSelector
+export default WorkplaceSelector

+ 2 - 0
web/app/components/header/app-nav/index.tsx

@@ -1,3 +1,5 @@
+'use client'
+
 import { useCallback, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useParams, usePathname } from 'next/navigation'

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 3
web/app/components/header/assets/beaker.svg


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 3
web/app/components/header/assets/github-icon.svg


+ 2 - 0
web/app/components/header/dataset-nav/index.tsx

@@ -1,3 +1,5 @@
+'use client'
+
 import { useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useParams, useRouter } from 'next/navigation'

+ 46 - 0
web/app/components/header/env-nav/index.tsx

@@ -0,0 +1,46 @@
+'use client'
+
+import { useTranslation } from 'react-i18next'
+import { useAppContext } from '@/context/app-context'
+import { Beaker02 } from '@/app/components/base/icons/src/vender/solid/education'
+import { TerminalSquare } from '@/app/components/base/icons/src/vender/solid/development'
+
+const headerEnvClassName: { [k: string]: string } = {
+  DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
+  TESTING: 'bg-[#A5F0FC] border-[#67E3F9] text-[#164C63]',
+}
+
+const EnvNav = () => {
+  const { t } = useTranslation()
+  const { langeniusVersionInfo } = useAppContext()
+  const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT'
+
+  if (!showEnvTag)
+    return null
+
+  return (
+    <div className={`
+      flex items-center h-[22px] mr-4 rounded-md px-2 text-xs font-medium border
+      ${headerEnvClassName[langeniusVersionInfo.current_env]}
+    `}>
+      {
+        langeniusVersionInfo.current_env === 'TESTING' && (
+          <>
+            <Beaker02 className='w-3 h-3 mr-1' />
+            {t('common.environment.testing')}
+          </>
+        )
+      }
+      {
+        langeniusVersionInfo.current_env === 'DEVELOPMENT' && (
+          <>
+            <TerminalSquare className='w-3 h-3 mr-1' />
+            {t('common.environment.development')}
+          </>
+        )
+      }
+    </div>
+  )
+}
+
+export default EnvNav

+ 37 - 0
web/app/components/header/explore-nav/index.tsx

@@ -0,0 +1,37 @@
+'use client'
+
+import { useTranslation } from 'react-i18next'
+import Link from 'next/link'
+import { useSelectedLayoutSegment } from 'next/navigation'
+import classNames from 'classnames'
+import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
+import { Grid01 as Grid01Solid } from '@/app/components/base/icons/src/vender/solid/layout'
+
+type ExploreNavProps = {
+  className?: string
+}
+
+const ExploreNav = ({
+  className,
+}: ExploreNavProps) => {
+  const { t } = useTranslation()
+  const selectedSegment = useSelectedLayoutSegment()
+  const actived = selectedSegment === 'explore'
+
+  return (
+    <Link href="/explore/apps" className={classNames(
+      className, 'group',
+      actived && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
+      actived ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
+    )}>
+      {
+        actived
+          ? <Grid01Solid className='mr-2 w-4 h-4' />
+          : <Grid01 className='mr-2 w-4 h-4' />
+      }
+      {t('common.menus.explore')}
+    </Link>
+  )
+}
+
+export default ExploreNav

+ 40 - 0
web/app/components/header/github-star/index.tsx

@@ -0,0 +1,40 @@
+import { Github } from '@/app/components/base/icons/src/public/common'
+import type { GithubRepo } from '@/models/common'
+
+const getStar = async () => {
+  const res = await fetch('https://api.github.com/repos/langgenius/dify')
+
+  if (!res.ok)
+    throw new Error('Failed to fetch data')
+
+  return res.json()
+}
+
+const GithubStar = async () => {
+  let githubRepo: GithubRepo = { stargazers_count: 0 }
+
+  if (process.env.NODE_ENV === 'development')
+    return null
+
+  try {
+    githubRepo = await getStar()
+  }
+  catch (e) {
+    return null
+  }
+
+  return (
+    <a
+      href='https://github.com/langgenius/dify'
+      target='_blank'
+      className='flex items-center leading-[18px] border border-gray-200 rounded-md text-xs text-gray-700 font-semibold overflow-hidden'>
+      <div className='flex items-center px-2 py-1 bg-gray-100'>
+        <Github className='mr-1 w-[18px] h-[18px]' />
+        Star
+      </div>
+      <div className='px-2 py-1 bg-white border-l border-gray-200'>{`${githubRepo.stargazers_count}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}</div>
+    </a>
+  )
+}
+
+export default GithubStar

+ 0 - 15
web/app/components/header/index.module.css

@@ -15,23 +15,8 @@
   background-size: contain;
 }
 
-.github-icon {
-  width: 18px;
-  height: 18px;
-  background: url(./assets/github-icon.svg) center center no-repeat;
-  background-size: contain;
-}
-
 .alpha {
   width: 12px;
   height: 12px;
   background: url(./assets/alpha.svg) center center no-repeat;
-}
-
-.beaker-icon {
-  width: 12px;
-  height: 12px;
-  margin-right: 4px;
-  background: url(./assets/beaker.svg) center center no-repeat;
-  background-size: contain;
 }

+ 25 - 120
web/app/components/header/index.tsx

@@ -1,138 +1,43 @@
-'use client'
-import { useEffect, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { usePathname, useSelectedLayoutSegment } from 'next/navigation'
-import classNames from 'classnames'
-import { CommandLineIcon } from '@heroicons/react/24/solid'
 import Link from 'next/link'
 import AccountDropdown from './account-dropdown'
 import AppNav from './app-nav'
 import DatasetNav from './dataset-nav'
+import EnvNav from './env-nav'
+import ExploreNav from './explore-nav'
+import GithubStar from './github-star'
+import PluginNav from './plugin-nav'
 import s from './index.module.css'
-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'
 
 const navClassName = `
   flex items-center relative mr-3 px-3 h-8 rounded-xl
   font-medium text-sm
   cursor-pointer
 `
-const headerEnvClassName: { [k: string]: string } = {
-  DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
-  TESTING: 'bg-[#A5F0FC] border-[#67E3F9] text-[#164C63]',
-}
-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) => {
-      setStarCount(data.stargazers_count)
-    })
-  }, [])
 
+const Header = () => {
   return (
-    <div className={classNames(
-      'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14',
-      s.header,
-      isBordered ? 'border-b border-gray-200' : '',
-    )}
-    >
-      <div className={classNames(
-        s[`header-${langeniusVersionInfo.current_env}`],
-        'flex flex-1 items-center justify-between px-4',
-      )}>
-        <div className='flex items-center'>
-          <Link href="/apps" className='flex items-center mr-4'>
-            <div className={s.logo} />
-          </Link>
-          {
-            starCount > 0 && (
-              <Link
-                href='https://github.com/langgenius/dify'
-                target='_blank'
-                className='flex items-center leading-[18px] border border-gray-200 rounded-md text-xs text-gray-700 font-semibold overflow-hidden'>
-                <div className='flex items-center px-2 py-1 bg-gray-100'>
-                  <div className={`${s['github-icon']} mr-1 rounded-full`} />
-                  Star
-                </div>
-                <div className='px-2 py-1 bg-white border-l border-gray-200'>{`${starCount}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}</div>
-              </Link>
-            )
-          }
-        </div>
-        <div className='flex items-center'>
-          <Link href="/explore/apps" className={classNames(
-            navClassName, 'group',
-            isExplore && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
-            isExplore ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
-          )}>
-            {
-              isExplore
-                ? <Grid01Solid className='mr-2 w-4 h-4' />
-                : <Grid01 className='mr-2 w-4 h-4' />
-            }
-            {t('common.menus.explore')}
-          </Link>
-          <AppNav />
-          <Link href="/plugins-coming-soon" className={classNames(
-            navClassName, 'group',
-            isPluginsComingSoon && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
-            isPluginsComingSoon ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
-          )}>
-            {
-              isPluginsComingSoon
-                ? <PuzzlePiece01Solid className='mr-2 w-4 h-4' />
-                : <PuzzlePiece01 className='mr-2 w-4 h-4' />
-            }
-            {t('common.menus.plugins')}
-          </Link>
-          <DatasetNav />
-        </div>
-        <div className='flex items-center flex-shrink-0'>
-          {
-            showEnvTag && (
-              <div className={`
-              flex items-center h-[22px] mr-4 rounded-md px-2 text-xs font-medium border
-              ${headerEnvClassName[langeniusVersionInfo.current_env]}
-            `}>
-                {
-                  langeniusVersionInfo.current_env === 'TESTING' && (
-                    <>
-                      <div className={s['beaker-icon']} />
-                      {t('common.environment.testing')}
-                    </>
-                  )
-                }
-                {
-                  langeniusVersionInfo.current_env === 'DEVELOPMENT' && (
-                    <>
-                      <CommandLineIcon className='w-3 h-3 mr-1' />
-                      {t('common.environment.development')}
-                    </>
-                  )
-                }
-              </div>
-            )
-          }
-          <WorkspaceProvider>
-            <AccountDropdown userProfile={userProfile} langeniusVersionInfo={langeniusVersionInfo} />
-          </WorkspaceProvider>
-        </div>
+    <>
+      <div className='flex items-center'>
+        <Link href="/apps" className='flex items-center mr-4'>
+          <div className={s.logo} />
+        </Link>
+        {/* @ts-expect-error Async Server Component */}
+        <GithubStar />
+      </div>
+      <div className='flex items-center'>
+        <ExploreNav className={navClassName} />
+        <AppNav />
+        <PluginNav className={navClassName} />
+        <DatasetNav />
+      </div>
+      <div className='flex items-center flex-shrink-0'>
+        <EnvNav />
+        <WorkspaceProvider>
+          <AccountDropdown />
+        </WorkspaceProvider>
       </div>
-    </div>
+    </>
   )
 }
 export default Header

+ 37 - 0
web/app/components/header/plugin-nav/index.tsx

@@ -0,0 +1,37 @@
+'use client'
+
+import { useTranslation } from 'react-i18next'
+import Link from 'next/link'
+import { useSelectedLayoutSegment } from 'next/navigation'
+import classNames from 'classnames'
+import { PuzzlePiece01 } from '@/app/components/base/icons/src/vender/line/development'
+import { PuzzlePiece01 as PuzzlePiece01Solid } from '@/app/components/base/icons/src/vender/solid/development'
+
+type PluginNavProps = {
+  className?: string
+}
+
+const PluginNav = ({
+  className,
+}: PluginNavProps) => {
+  const { t } = useTranslation()
+  const selectedSegment = useSelectedLayoutSegment()
+  const isPluginsComingSoon = selectedSegment === 'plugins-coming-soon'
+
+  return (
+    <Link href="/plugins-coming-soon" className={classNames(
+      className, 'group',
+      isPluginsComingSoon && 'bg-white shadow-[0_2px_5px_-1px_rgba(0,0,0,0.05),0_2px_4px_-2px_rgba(0,0,0,0.05)]',
+      isPluginsComingSoon ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
+    )}>
+      {
+        isPluginsComingSoon
+          ? <PuzzlePiece01Solid className='mr-2 w-4 h-4' />
+          : <PuzzlePiece01 className='mr-2 w-4 h-4' />
+      }
+      {t('common.menus.plugins')}
+    </Link>
+  )
+}
+
+export default PluginNav

+ 1 - 1
web/context/app-context.tsx

@@ -67,7 +67,7 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
       const result = await userProfileResponse.json()
       setUserProfile(result)
       const current_version = userProfileResponse.headers.get('x-version')
-      const current_env = userProfileResponse.headers.get('x-env')
+      const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env')
       const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } })
       setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
     }