diff --git a/agenta-web/.eslintrc.json b/agenta-web/.eslintrc.json
index 188940439e..fb31c1c1e3 100644
--- a/agenta-web/.eslintrc.json
+++ b/agenta-web/.eslintrc.json
@@ -2,6 +2,8 @@
"extends": ["next/core-web-vitals"],
"rules": {
"react/no-unescaped-entities": 0,
- "react/display-name": 0
+ "react/display-name": 0,
+ "@next/next/no-sync-scripts": 0,
+ "react/no-children-prop": 0
}
}
diff --git a/agenta-web/next.config.js b/agenta-web/next.config.js
index 28a2e7311f..19f9495156 100644
--- a/agenta-web/next.config.js
+++ b/agenta-web/next.config.js
@@ -6,7 +6,7 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
const nextConfig = {
output: "standalone",
reactStrictMode: true,
- pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
+ pageExtensions: ["ts", "tsx", "js", "jsx"],
productionBrowserSourceMaps: true,
transpilePackages: [
"@lobehub/ui",
@@ -26,6 +26,11 @@ const nextConfig = {
images: {
remotePatterns: [{hostname: "fps.cdnpk.net"}],
},
+ ...(process.env.NEXT_PUBLIC_FF === "cloud" && {
+ experimental: {
+ instrumentationHook: true,
+ },
+ }),
async redirects() {
return [
@@ -54,8 +59,58 @@ const nextConfig = {
)
}
+ if (process.env.NEXT_PUBLIC_FF === "cloud") {
+ config.plugins.push(
+ new webpack.DefinePlugin({
+ __SENTRY_DEBUG__: false,
+ __SENTRY_TRACING__: true,
+ __RRWEB_EXCLUDE_IFRAME__: true,
+ __RRWEB_EXCLUDE_SHADOW_DOM__: true,
+ __SENTRY_EXCLUDE_REPLAY_WORKER__: true,
+ }),
+ )
+ }
+
return config
},
}
-module.exports = withBundleAnalyzer(nextConfig)
+if (process.env.NEXT_PUBLIC_FF === "cloud") {
+ const {withSentryConfig} = require("@sentry/nextjs")
+
+ module.exports = withBundleAnalyzer(
+ withSentryConfig(
+ nextConfig,
+ {
+ // For all available options, see:
+ // https://github.com/getsentry/sentry-webpack-plugin#options
+
+ // Suppresses source map uploading logs during build
+ silent: true,
+ org: "agenta-ai",
+ project: "javascript-nextjs",
+ },
+ {
+ // For all available options, see:
+ // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
+
+ // Upload a larger set of source maps for prettier stack traces (increases build time)
+ widenClientFileUpload: true,
+
+ // Transpiles SDK to be compatible with IE11 (increases bundle size)
+ transpileClientSDK: true,
+
+ // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
+ tunnelRoute: "/monitoring",
+
+ // Hides source maps from generated client bundles
+ hideSourceMaps: true,
+
+ // Automatically tree-shake Sentry logger statements to reduce bundle size
+ disableLogger: true,
+ },
+ ),
+ )
+} else {
+ module.exports = withBundleAnalyzer(nextConfig)
+}
diff --git a/agenta-web/src/_app.tsx b/agenta-web/src/_app.tsx
deleted file mode 100644
index e03e20b16c..0000000000
--- a/agenta-web/src/_app.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import "@/styles/globals.css"
-import type {AppProps} from "next/app"
-import Layout from "@/components/Layout/Layout"
-import ThemeContextProvider from "@/components/Layout/ThemeContextProvider"
-
-export default function App({Component, pageProps}: AppProps) {
- return (
-
-
-
-
-
- )
-}
diff --git a/agenta-web/src/components/OrgWrapper/OrgWrapper.tsx b/agenta-web/src/components/OrgWrapper/OrgWrapper.tsx
new file mode 100644
index 0000000000..2cfbc210ae
--- /dev/null
+++ b/agenta-web/src/components/OrgWrapper/OrgWrapper.tsx
@@ -0,0 +1,29 @@
+import {useState, useEffect, useCallback, ComponentType, FC} from "react"
+import {isDemo} from "@/lib/helpers/utils"
+import {dynamicContext} from "@/lib/helpers/dynamic"
+
+type OrgWrapperProps = {children: React.ReactNode}
+
+const OrgWrapper: FC = ({children}) => {
+ const [OrgContextProvider, setOrgContextProvider] =
+ useState | null>(null)
+
+ const initializeOrgProvider = useCallback(async () => {
+ const Provider = await dynamicContext("org.context")
+ setOrgContextProvider(() => Provider.default)
+ }, [])
+
+ useEffect(() => {
+ if (isDemo()) {
+ initializeOrgProvider()
+ }
+ }, [initializeOrgProvider])
+
+ if (isDemo() && OrgContextProvider) {
+ return {children}
+ }
+
+ return <>{children}>
+}
+
+export default OrgWrapper
diff --git a/agenta-web/src/components/Scripts/GlobalScripts.tsx b/agenta-web/src/components/Scripts/GlobalScripts.tsx
new file mode 100644
index 0000000000..e491d3722b
--- /dev/null
+++ b/agenta-web/src/components/Scripts/GlobalScripts.tsx
@@ -0,0 +1,34 @@
+import {useState, useEffect, useCallback, ComponentType} from "react"
+import Head from "next/head"
+import {isDemo} from "@/lib/helpers/utils"
+import {dynamicComponent} from "@/lib/helpers/dynamic"
+
+const GlobalScripts = () => {
+ const [CloudScripts, setCloudScripts] = useState(null)
+
+ const initializeScripts = useCallback(() => {
+ const Scripts = dynamicComponent("Scripts/assets/CloudScripts")
+ setCloudScripts((prev: any) => prev ?? Scripts)
+ }, [])
+
+ useEffect(() => {
+ if (!CloudScripts && isDemo()) {
+ initializeScripts()
+ }
+ }, [initializeScripts, CloudScripts])
+
+ if (isDemo() && CloudScripts) {
+ return
+ }
+
+ return (
+ <>
+
+ Agenta: The LLMOps platform.
+
+
+ >
+ )
+}
+
+export default GlobalScripts
diff --git a/agenta-web/src/lib/helpers/analytics/AgPosthogProvider.tsx b/agenta-web/src/lib/helpers/analytics/AgPosthogProvider.tsx
index 747f99134c..9e9fb4252b 100644
--- a/agenta-web/src/lib/helpers/analytics/AgPosthogProvider.tsx
+++ b/agenta-web/src/lib/helpers/analytics/AgPosthogProvider.tsx
@@ -3,8 +3,10 @@ import {useRouter} from "next/router"
import {useAtom} from "jotai"
import {posthogAtom, type PostHogConfig} from "./store/atoms"
import {CustomPosthogProviderType} from "./types"
+import {CLOUD_CONFIG, OSS_CONFIG} from "./assets/constants"
+import {isDemo} from "../utils"
-const CustomPosthogProvider: CustomPosthogProviderType = ({children, config}) => {
+const CustomPosthogProvider: CustomPosthogProviderType = ({children}) => {
const router = useRouter()
const [loadingPosthog, setLoadingPosthog] = useState(false)
const [posthogClient, setPosthogClient] = useAtom(posthogAtom)
@@ -26,12 +28,12 @@ const CustomPosthogProvider: CustomPosthogProviderType = ({children, config}) =>
if (process.env.NODE_ENV === "development") posthog.debug()
},
capture_pageview: false,
- ...config,
+ ...((isDemo() ? CLOUD_CONFIG : OSS_CONFIG) as Partial),
})
} finally {
setLoadingPosthog(false)
}
- }, [loadingPosthog, config, posthogClient, setPosthogClient])
+ }, [loadingPosthog, posthogClient, setPosthogClient])
useEffect(() => {
initPosthog()
diff --git a/agenta-web/src/lib/helpers/analytics/assets/constants.ts b/agenta-web/src/lib/helpers/analytics/assets/constants.ts
new file mode 100644
index 0000000000..1f416c7d1f
--- /dev/null
+++ b/agenta-web/src/lib/helpers/analytics/assets/constants.ts
@@ -0,0 +1,12 @@
+export const CLOUD_CONFIG = {
+ session_recording: {
+ maskAllInputs: false,
+ maskInputOptions: {
+ password: true,
+ email: true,
+ },
+ },
+}
+export const OSS_CONFIG = {
+ persistence: "localStorage+cookie",
+}
diff --git a/agenta-web/src/lib/helpers/analytics/types.d.ts b/agenta-web/src/lib/helpers/analytics/types.d.ts
index d55cc0d99c..15a6c5b63a 100644
--- a/agenta-web/src/lib/helpers/analytics/types.d.ts
+++ b/agenta-web/src/lib/helpers/analytics/types.d.ts
@@ -3,5 +3,4 @@ import {type PostHogConfig} from "./store/atoms"
export interface CustomPosthogProviderType
extends React.FC<{
children: React.ReactNode
- config: Partial
}> {}
diff --git a/agenta-web/src/lib/helpers/auth/AuthProvider.tsx b/agenta-web/src/lib/helpers/auth/AuthProvider.tsx
new file mode 100644
index 0000000000..92f100a22a
--- /dev/null
+++ b/agenta-web/src/lib/helpers/auth/AuthProvider.tsx
@@ -0,0 +1,37 @@
+import {useEffect, useCallback} from "react"
+import SuperTokensReact, {SuperTokensWrapper} from "supertokens-auth-react"
+import {AuthProviderType} from "./types"
+import {isDemo} from "../utils"
+import {dynamicConfig} from "../dynamic"
+;(async () => {
+ if (typeof window !== "undefined" && isDemo()) {
+ const {frontendConfig} = await dynamicConfig("frontendConfig")
+ SuperTokensReact.init(frontendConfig())
+ }
+})()
+
+const AuthProvider: AuthProviderType = ({children, pageProps}) => {
+ const doRefresh = useCallback(async () => {
+ if (isDemo() && pageProps.fromSupertokens === "needs-refresh") {
+ const session = await import("supertokens-auth-react/recipe/session")
+
+ if (await session.attemptRefreshingSession()) {
+ location.reload()
+ } else {
+ SuperTokensReact.redirectToAuth()
+ }
+ }
+ }, [pageProps.fromSupertokens])
+
+ useEffect(() => {
+ doRefresh()
+ }, [doRefresh])
+
+ if (isDemo() && pageProps.fromSupertokens === "needs-refresh") {
+ return null
+ }
+
+ return isDemo() ? {children} : <>{children}>
+}
+
+export default AuthProvider
diff --git a/agenta-web/src/lib/helpers/auth/types.d.ts b/agenta-web/src/lib/helpers/auth/types.d.ts
new file mode 100644
index 0000000000..65ab18f7a3
--- /dev/null
+++ b/agenta-web/src/lib/helpers/auth/types.d.ts
@@ -0,0 +1,5 @@
+export interface AuthProviderType
+ extends React.FC<{
+ children: React.ReactNode
+ pageProps: any
+ }> {}
diff --git a/agenta-web/src/lib/helpers/dynamic.ts b/agenta-web/src/lib/helpers/dynamic.ts
index effbc89eaa..18018e44fa 100644
--- a/agenta-web/src/lib/helpers/dynamic.ts
+++ b/agenta-web/src/lib/helpers/dynamic.ts
@@ -38,3 +38,11 @@ export async function dynamicLib(path: string, fallback?: any) {
return fallback
}
}
+
+export async function dynamicConfig(path: string, fallback?: any) {
+ try {
+ return await import(`@/config/${path}`)
+ } catch (error) {
+ return fallback
+ }
+}
diff --git a/agenta-web/src/lib/helpers/sentry/hook/useSentryIntegrations.ts b/agenta-web/src/lib/helpers/sentry/hook/useSentryIntegrations.ts
new file mode 100644
index 0000000000..e5e1df0b02
--- /dev/null
+++ b/agenta-web/src/lib/helpers/sentry/hook/useSentryIntegrations.ts
@@ -0,0 +1,20 @@
+import {useCallback, useEffect, useRef} from "react"
+import {dynamicLib} from "../../dynamic"
+import {isDemo} from "../../utils"
+
+export const useSentryIntegrations = () => {
+ const isLoading = useRef(false)
+
+ const initializeSentryIntegrations = useCallback(async () => {
+ const initSentry = await dynamicLib("helpers/sentry/lazyLoadSentryIntegrations")
+ initSentry?.lazyLoadSentryIntegrations()
+ }, [])
+
+ useEffect(() => {
+ if (!isLoading.current && isDemo()) {
+ isLoading.current = true
+
+ initializeSentryIntegrations()
+ }
+ }, [])
+}
diff --git a/agenta-web/src/pages/_app.tsx b/agenta-web/src/pages/_app.tsx
index d47ae0bf67..7f99cda146 100644
--- a/agenta-web/src/pages/_app.tsx
+++ b/agenta-web/src/pages/_app.tsx
@@ -1,5 +1,4 @@
import type {AppProps} from "next/app"
-import Head from "next/head"
import dynamic from "next/dynamic"
import "@/styles/globals.css"
@@ -9,8 +8,12 @@ import ThemeContextProvider from "@/components/Layout/ThemeContextProvider"
import AppContextProvider from "@/contexts/app.context"
import ProfileContextProvider from "@/contexts/profile.context"
import ProjectContextProvider from "@/contexts/project.context"
+import AuthProvider from "@/lib/helpers/auth/AuthProvider"
+import OrgWrapper from "@/components/OrgWrapper/OrgWrapper"
+import GlobalScripts from "@/components/Scripts/GlobalScripts"
import {Inter} from "next/font/google"
import AgSWRConfig from "@/lib/api/SWRConfig"
+import {useSentryIntegrations} from "@/lib/helpers/sentry/hook/useSentryIntegrations"
const NoMobilePageWrapper = dynamicComponent("NoMobilePageWrapper/NoMobilePageWrapper")
const CustomPosthogProvider = dynamic(() => import("@/lib/helpers/analytics/AgPosthogProvider"))
@@ -21,31 +24,31 @@ const inter = Inter({
})
export default function App({Component, pageProps}: AppProps) {
+ useSentryIntegrations()
+
return (
<>
-
- Agenta: The LLMOps platform.
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agenta-web/src/services/observability/api/index.ts b/agenta-web/src/services/observability/api/index.ts
deleted file mode 100644
index 30a1282c03..0000000000
--- a/agenta-web/src/services/observability/api/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-//Prefix convention:
-// - fetch: GET single entity from server
-// - fetchAll: GET all entities from server
-// - create: POST data to server
-// - update: PUT data to server
-// - delete: DELETE data from server