diff --git a/ee/tabby-ui/app/(dashboard)/components/main-content.tsx b/ee/tabby-ui/app/(dashboard)/components/main-content.tsx
new file mode 100644
index 000000000000..1e05d9db306b
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/components/main-content.tsx
@@ -0,0 +1,25 @@
+'use client'
+
+import { ScrollArea } from '@/components/ui/scroll-area'
+import { BANNER_HEIGHT, useShowDemoBanner } from '@/components/demo-banner'
+import { Header } from '@/components/header'
+
+export default function MainContent({
+ children
+}: {
+ children: React.ReactNode
+}) {
+ const [isShowDemoBanner] = useShowDemoBanner()
+ const style = isShowDemoBanner
+ ? { height: `calc(100vh - ${BANNER_HEIGHT})` }
+ : { height: '100vh' }
+ return (
+ <>
+ {/* Wraps right hand side into ScrollArea, making scroll bar consistent across all browsers */}
+
+
+ {children}
+
+ >
+ )
+}
diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
index 2d5ba85f256c..a6869d596d73 100644
--- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
+++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
@@ -22,6 +22,8 @@ import {
IconLightingBolt,
IconUser
} from '@/components/ui/icons'
+import { ScrollArea } from '@/components/ui/scroll-area'
+import { BANNER_HEIGHT, useShowDemoBanner } from '@/components/demo-banner'
export interface SidebarProps {
children?: React.ReactNode
@@ -30,12 +32,17 @@ export interface SidebarProps {
export default function Sidebar({ children, className }: SidebarProps) {
const [{ data }] = useMe()
+ const [isShowDemoBanner] = useShowDemoBanner()
const isAdmin = data?.me.isAdmin
+ const style = isShowDemoBanner
+ ? { height: `calc(100vh - ${BANNER_HEIGHT})` }
+ : { height: '100vh' }
return (
-
-
+
)
}
diff --git a/ee/tabby-ui/app/(dashboard)/layout.tsx b/ee/tabby-ui/app/(dashboard)/layout.tsx
index 1d8fdedb86b4..391b2af67fc0 100644
--- a/ee/tabby-ui/app/(dashboard)/layout.tsx
+++ b/ee/tabby-ui/app/(dashboard)/layout.tsx
@@ -1,8 +1,6 @@
import { Metadata } from 'next'
-import { ScrollArea } from '@/components/ui/scroll-area'
-import { Header } from '@/components/header'
-
+import MainContent from './components/main-content'
import Sidebar from './components/sidebar'
export const metadata: Metadata = {
@@ -12,20 +10,15 @@ export const metadata: Metadata = {
}
}
-interface DashboardLayoutProps {
+export default function RootLayout({
+ children
+}: {
children: React.ReactNode
-}
-
-export default function RootLayout({ children }: DashboardLayoutProps) {
+}) {
return (
-
- {/* Wraps right hand side into ScrollArea, making scroll bar consistent across all browsers */}
-
-
- {children}
-
+ {children}
)
}
diff --git a/ee/tabby-ui/app/layout.tsx b/ee/tabby-ui/app/layout.tsx
index 87e4fba747d7..c933da358d35 100644
--- a/ee/tabby-ui/app/layout.tsx
+++ b/ee/tabby-ui/app/layout.tsx
@@ -6,6 +6,7 @@ import '@/app/globals.css'
import { fontMono, fontSans } from '@/lib/fonts'
import { cn } from '@/lib/utils'
+import { DemoBanner } from '@/components/demo-banner'
import { Providers } from '@/components/providers'
import { TailwindIndicator } from '@/components/tailwind-indicator'
@@ -37,7 +38,10 @@ export default function RootLayout({ children }: RootLayoutProps) {
)}
>
- {children}
+
+
+ {children}
+
diff --git a/ee/tabby-ui/components/demo-banner.tsx b/ee/tabby-ui/components/demo-banner.tsx
new file mode 100644
index 000000000000..0fd51ec4c85b
--- /dev/null
+++ b/ee/tabby-ui/components/demo-banner.tsx
@@ -0,0 +1,96 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { isNil } from 'lodash-es'
+import { useTheme } from 'next-themes'
+import useSWRImmutable from 'swr/immutable'
+
+import { useIsDemoMode } from '@/lib/hooks/use-server-info'
+import { cn } from '@/lib/utils'
+import {
+ IconClose,
+ IconGitFork,
+ IconGithub,
+ IconStar
+} from '@/components/ui/icons'
+
+export const BANNER_HEIGHT = '3.5rem'
+
+export function useShowDemoBanner(): [boolean, (isShow: boolean) => void] {
+ const isDemoMode = useIsDemoMode()
+ const [isShow, setIsShow] = useState(false)
+
+ useEffect(() => {
+ if (!isNil(isDemoMode)) {
+ setIsShow(isDemoMode)
+ }
+ }, [isDemoMode])
+
+ return [isShow, setIsShow]
+}
+
+export function DemoBanner() {
+ const [isShow, setIsShow] = useShowDemoBanner()
+ const [slackIconFill, setSlackIconFill] = useState('')
+ const { theme } = useTheme()
+ const { data } = useSWRImmutable(
+ 'https://api.github.com/repos/TabbyML/tabby',
+ (url: string) => fetch(url).then(res => res.json())
+ )
+
+ useEffect(() => {
+ setSlackIconFill(theme === 'dark' ? '#171615' : '#ECECEC')
+ }, [isShow, theme])
+
+ const style = isShow ? { height: BANNER_HEIGHT } : { height: 0 }
+ return (
+
+ )
+}
diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx
index 815189c08547..28dfb7da1336 100644
--- a/ee/tabby-ui/components/ui/icons.tsx
+++ b/ee/tabby-ui/components/ui/icons.tsx
@@ -2,7 +2,7 @@
import * as React from 'react'
// FIXME(wwayne): Review each icons and consider re-export from `lucide-react`.
-import { BookOpenText, ChevronsDownUp, Mail } from 'lucide-react'
+import { BookOpenText, ChevronsDownUp, GitFork, Mail, Star } from 'lucide-react'
import { cn } from '@/lib/utils'
@@ -561,44 +561,44 @@ function IconSlack({ className, ...props }: React.ComponentProps<'svg'>) {
@@ -1400,6 +1400,20 @@ function IconChevronsDownUp({
)
}
+const IconStar = ({
+ className,
+ ...props
+}: React.ComponentProps) => (
+
+)
+
+const IconGitFork = ({
+ className,
+ ...props
+}: React.ComponentProps) => (
+
+)
+
export {
IconEdit,
IconNextChat,
@@ -1471,5 +1485,7 @@ export {
IconActivity,
IconBookOpenText,
IconMail,
- IconChevronsDownUp
+ IconChevronsDownUp,
+ IconStar,
+ IconGitFork
}