Merge remote-tracking branch 'origin/main' into feat/support-agent-sandbox

This commit is contained in:
yyh
2026-01-13 10:15:59 +08:00
65 changed files with 4917 additions and 3856 deletions

View File

@ -29,6 +29,7 @@ import { CheckModal } from '@/hooks/use-pay'
import { useInfiniteAppList } from '@/service/use-apps'
import { AppModeEnum } from '@/types/app'
import { cn } from '@/utils/classnames'
import { isServer } from '@/utils/client'
import AppCard from './app-card'
import { AppCardSkeleton } from './app-card-skeleton'
import Empty from './empty'
@ -71,7 +72,7 @@ const List = () => {
// 1) Normalize legacy/incorrect query params like ?mode=discover -> ?category=all
useEffect(() => {
// avoid running on server
if (typeof window === 'undefined')
if (isServer)
return
const mode = searchParams.get('mode')
if (!mode)

View File

@ -11,6 +11,7 @@ import DifyLogo from '@/app/components/base/logo/dify-logo'
import Tooltip from '@/app/components/base/tooltip'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { cn } from '@/utils/classnames'
import { isClient } from '@/utils/client'
import {
useEmbeddedChatbotContext,
} from '../context'
@ -40,7 +41,6 @@ const Header: FC<IHeaderProps> = ({
allInputsHidden,
} = useEmbeddedChatbotContext()
const isClient = typeof window !== 'undefined'
const isIframe = isClient ? window.self !== window.top : false
const [parentOrigin, setParentOrigin] = useState('')
const [showToggleExpandButton, setShowToggleExpandButton] = useState(false)

View File

@ -0,0 +1,3 @@
'use client'
export { SerwistProvider } from '@serwist/turbopack/react'

View File

@ -13,6 +13,7 @@ import Tooltip from '@/app/components/base/tooltip'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { useGetLanguage } from '@/context/i18n'
import { isServer } from '@/utils/client'
import { formatNumber } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import BlockIcon from '../block-icon'
@ -49,14 +50,14 @@ const FeaturedTools = ({
const language = useGetLanguage()
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (typeof window === 'undefined')
if (isServer)
return false
const stored = window.localStorage.getItem(STORAGE_KEY)
return stored === 'true'
})
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
const stored = window.localStorage.getItem(STORAGE_KEY)
if (stored !== null)
@ -64,7 +65,7 @@ const FeaturedTools = ({
}, [])
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])

View File

@ -12,6 +12,7 @@ import Tooltip from '@/app/components/base/tooltip'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { useGetLanguage } from '@/context/i18n'
import { isServer } from '@/utils/client'
import { formatNumber } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import BlockIcon from '../block-icon'
@ -42,14 +43,14 @@ const FeaturedTriggers = ({
const language = useGetLanguage()
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (typeof window === 'undefined')
if (isServer)
return false
const stored = window.localStorage.getItem(STORAGE_KEY)
return stored === 'true'
})
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
const stored = window.localStorage.getItem(STORAGE_KEY)
if (stored !== null)
@ -57,7 +58,7 @@ const FeaturedTriggers = ({
}, [])
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])

View File

@ -11,6 +11,7 @@ import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid
import Loading from '@/app/components/base/loading'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
import { useRAGRecommendedPlugins } from '@/service/use-tools'
import { isServer } from '@/utils/client'
import { getMarketplaceUrl } from '@/utils/var'
import List from './list'
@ -29,14 +30,14 @@ const RAGToolRecommendations = ({
}: RAGToolRecommendationsProps) => {
const { t } = useTranslation()
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (typeof window === 'undefined')
if (isServer)
return false
const stored = window.localStorage.getItem(STORAGE_KEY)
return stored === 'true'
})
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
const stored = window.localStorage.getItem(STORAGE_KEY)
if (stored !== null)
@ -44,7 +45,7 @@ const RAGToolRecommendations = ({
}, [])
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])

View File

@ -12,6 +12,7 @@ import { ToastProvider } from './components/base/toast'
import BrowserInitializer from './components/browser-initializer'
import { ReactScanLoader } from './components/devtools/react-scan/loader'
import { I18nServerProvider } from './components/provider/i18n-server'
import { SerwistProvider } from './components/provider/serwist'
import SentryInitializer from './components/sentry-initializer'
import RoutePrefixHandle from './routePrefixHandle'
import './styles/globals.css'
@ -39,6 +40,9 @@ const LocaleLayout = async ({
}) => {
const locale = await getLocaleOnServer()
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ''
const swUrl = `${basePath}/serwist/sw.js`
const datasetMap: Record<DatasetAttr, string | undefined> = {
[DatasetAttr.DATA_API_PREFIX]: process.env.NEXT_PUBLIC_API_PREFIX,
[DatasetAttr.DATA_PUBLIC_API_PREFIX]: process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX,
@ -92,33 +96,35 @@ const LocaleLayout = async ({
className="color-scheme h-full select-auto"
{...datasetMap}
>
<ReactScanLoader />
<JotaiProvider>
<ThemeProvider
attribute="data-theme"
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme={false}
>
<NuqsAdapter>
<BrowserInitializer>
<SentryInitializer>
<TanstackQueryInitializer>
<I18nServerProvider>
<ToastProvider>
<GlobalPublicStoreProvider>
{children}
</GlobalPublicStoreProvider>
</ToastProvider>
</I18nServerProvider>
</TanstackQueryInitializer>
</SentryInitializer>
</BrowserInitializer>
</NuqsAdapter>
</ThemeProvider>
</JotaiProvider>
<RoutePrefixHandle />
<SerwistProvider swUrl={swUrl}>
<ReactScanLoader />
<JotaiProvider>
<ThemeProvider
attribute="data-theme"
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme={false}
>
<NuqsAdapter>
<BrowserInitializer>
<SentryInitializer>
<TanstackQueryInitializer>
<I18nServerProvider>
<ToastProvider>
<GlobalPublicStoreProvider>
{children}
</GlobalPublicStoreProvider>
</ToastProvider>
</I18nServerProvider>
</TanstackQueryInitializer>
</SentryInitializer>
</BrowserInitializer>
</NuqsAdapter>
</ThemeProvider>
</JotaiProvider>
<RoutePrefixHandle />
</SerwistProvider>
</body>
</html>
)

View File

@ -0,0 +1,14 @@
import { spawnSync } from 'node:child_process'
import { randomUUID } from 'node:crypto'
import { createSerwistRoute } from '@serwist/turbopack'
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ''
const revision = spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8' }).stdout?.trim() || randomUUID()
export const { dynamic, dynamicParams, revalidate, generateStaticParams, GET } = createSerwistRoute({
additionalPrecacheEntries: [{ url: `${basePath}/_offline.html`, revision }],
swSrc: 'app/sw.ts',
nextConfig: {
basePath,
},
})

104
web/app/sw.ts Normal file
View File

@ -0,0 +1,104 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import type { PrecacheEntry, SerwistGlobalConfig } from 'serwist'
import { CacheableResponsePlugin, CacheFirst, ExpirationPlugin, NetworkFirst, Serwist, StaleWhileRevalidate } from 'serwist'
declare global {
// eslint-disable-next-line ts/consistent-type-definitions
interface WorkerGlobalScope extends SerwistGlobalConfig {
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined
}
}
declare const self: ServiceWorkerGlobalScope
const scopePathname = new URL(self.registration.scope).pathname
const basePath = scopePathname.replace(/\/serwist\/$/, '').replace(/\/$/, '')
const offlineUrl = `${basePath}/_offline.html`
const serwist = new Serwist({
precacheEntries: self.__SW_MANIFEST,
skipWaiting: true,
clientsClaim: true,
navigationPreload: true,
runtimeCaching: [
{
matcher: ({ url }) => url.origin === 'https://fonts.googleapis.com',
handler: new CacheFirst({
cacheName: 'google-fonts',
plugins: [
new CacheableResponsePlugin({ statuses: [0, 200] }),
new ExpirationPlugin({
maxEntries: 4,
maxAgeSeconds: 365 * 24 * 60 * 60,
}),
],
}),
},
{
matcher: ({ url }) => url.origin === 'https://fonts.gstatic.com',
handler: new CacheFirst({
cacheName: 'google-fonts-webfonts',
plugins: [
new CacheableResponsePlugin({ statuses: [0, 200] }),
new ExpirationPlugin({
maxEntries: 4,
maxAgeSeconds: 365 * 24 * 60 * 60,
}),
],
}),
},
{
matcher: ({ request }) => request.destination === 'image',
handler: new CacheFirst({
cacheName: 'images',
plugins: [
new CacheableResponsePlugin({ statuses: [0, 200] }),
new ExpirationPlugin({
maxEntries: 64,
maxAgeSeconds: 30 * 24 * 60 * 60,
}),
],
}),
},
{
matcher: ({ request }) => request.destination === 'script' || request.destination === 'style',
handler: new StaleWhileRevalidate({
cacheName: 'static-resources',
plugins: [
new ExpirationPlugin({
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60,
}),
],
}),
},
{
matcher: ({ url, sameOrigin }) => sameOrigin && url.pathname.startsWith('/api/'),
handler: new NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 10,
plugins: [
new ExpirationPlugin({
maxEntries: 16,
maxAgeSeconds: 60 * 60,
}),
],
}),
},
],
fallbacks: {
entries: [
{
url: offlineUrl,
matcher({ request }) {
return request.destination === 'document'
},
},
],
},
})
serwist.addEventListeners()