Compare commits

..

8 Commits

Author SHA1 Message Date
c2fc4f4822 Use node 22 2026-03-03 13:31:35 +08:00
67e0eeefd2 Add permission 2026-03-02 21:36:34 +08:00
cf58035fa2 Add heapsnapshot-signal 2026-03-02 19:57:16 +08:00
097f10d920 Merge branch 'hotfix/1.13.0-fix.2' into deploy/memory-usage 2026-03-02 19:33:07 +08:00
c34d05141e fix: fix chat assistant response mode blocking is not work (#32394)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-02 18:34:17 +08:00
302701b303 chore: remove serwist 2026-03-02 17:19:15 +08:00
8080159eaf fix: remove REDIRECT_URL_KEY from url (#32770) 2026-03-02 16:06:33 +08:00
c730fec1e4 chore: bump version to 1.13.0 (#32147) 2026-02-11 17:08:49 +08:00
33 changed files with 272 additions and 830 deletions

View File

@ -157,7 +157,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline):
id=self._message_id,
mode=self._conversation_mode,
message_id=self._message_id,
answer=cast(str, self._task_state.llm_result.message.content),
answer=self._task_state.llm_result.message.get_text_content(),
created_at=self._message_created_at,
**extras,
),
@ -170,7 +170,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline):
mode=self._conversation_mode,
conversation_id=self._conversation_id,
message_id=self._message_id,
answer=cast(str, self._task_state.llm_result.message.content),
answer=self._task_state.llm_result.message.get_text_content(),
created_at=self._message_created_at,
**extras,
),
@ -283,7 +283,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline):
# handle output moderation
output_moderation_answer = self.handle_output_moderation_when_task_finished(
cast(str, self._task_state.llm_result.message.content)
self._task_state.llm_result.message.get_text_content()
)
if output_moderation_answer:
self._task_state.llm_result.message.content = output_moderation_answer
@ -397,7 +397,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline):
message.message_unit_price = usage.prompt_unit_price
message.message_price_unit = usage.prompt_price_unit
message.answer = (
PromptTemplateParser.remove_template_variables(cast(str, llm_result.message.content).strip())
PromptTemplateParser.remove_template_variables(llm_result.message.get_text_content().strip())
if llm_result.message.content
else ""
)

View File

@ -1,6 +1,6 @@
[project]
name = "dify-api"
version = "1.12.1"
version = "1.13.0"
requires-python = ">=3.11,<3.13"
dependencies = [

View File

@ -131,33 +131,54 @@ class AppGenerateService:
elif app_model.mode == AppMode.ADVANCED_CHAT:
workflow_id = args.get("workflow_id")
workflow = cls._get_workflow(app_model, invoke_from, workflow_id)
with rate_limit_context(rate_limit, request_id):
payload = AppExecutionParams.new(
app_model=app_model,
workflow=workflow,
user=user,
args=args,
invoke_from=invoke_from,
streaming=streaming,
call_depth=0,
)
payload_json = payload.model_dump_json()
def on_subscribe():
workflow_based_app_execution_task.delay(payload_json)
if streaming:
# Streaming mode: subscribe to SSE and enqueue the execution on first subscriber
with rate_limit_context(rate_limit, request_id):
payload = AppExecutionParams.new(
app_model=app_model,
workflow=workflow,
user=user,
args=args,
invoke_from=invoke_from,
streaming=True,
call_depth=0,
)
payload_json = payload.model_dump_json()
on_subscribe = cls._build_streaming_task_on_subscribe(on_subscribe)
generator = AdvancedChatAppGenerator()
return rate_limit.generate(
generator.convert_to_event_stream(
generator.retrieve_events(
AppMode.ADVANCED_CHAT,
payload.workflow_run_id,
on_subscribe=on_subscribe,
def on_subscribe():
workflow_based_app_execution_task.delay(payload_json)
on_subscribe = cls._build_streaming_task_on_subscribe(on_subscribe)
generator = AdvancedChatAppGenerator()
return rate_limit.generate(
generator.convert_to_event_stream(
generator.retrieve_events(
AppMode.ADVANCED_CHAT,
payload.workflow_run_id,
on_subscribe=on_subscribe,
),
),
),
request_id=request_id,
)
request_id=request_id,
)
else:
# Blocking mode: run synchronously and return JSON instead of SSE
# Keep behaviour consistent with WORKFLOW blocking branch.
advanced_generator = AdvancedChatAppGenerator()
return rate_limit.generate(
advanced_generator.convert_to_event_stream(
advanced_generator.generate(
app_model=app_model,
workflow=workflow,
user=user,
args=args,
invoke_from=invoke_from,
workflow_run_id=str(uuid.uuid4()),
streaming=False,
)
),
request_id=request_id,
)
elif app_model.mode == AppMode.WORKFLOW:
workflow_id = args.get("workflow_id")
workflow = cls._get_workflow(app_model, invoke_from, workflow_id)

View File

@ -63,3 +63,56 @@ def test_workflow_blocking_injects_pause_state_config(mocker, monkeypatch):
pause_state_config = call_kwargs.get("pause_state_config")
assert pause_state_config is not None
assert pause_state_config.state_owner_user_id == "owner-id"
def test_advanced_chat_blocking_returns_dict_and_does_not_use_event_retrieval(mocker, monkeypatch):
"""
Regression test: ADVANCED_CHAT in blocking mode should return a plain dict
(non-streaming), and must not go through the async retrieve_events path.
Keeps behavior consistent with WORKFLOW blocking branch.
"""
# Disable billing and stub RateLimit to a no-op that just passes values through
monkeypatch.setattr(app_generate_service_module.dify_config, "BILLING_ENABLED", False)
mocker.patch("services.app_generate_service.RateLimit", _DummyRateLimit)
# Arrange a fake workflow and wire AppGenerateService._get_workflow to return it
workflow = MagicMock()
workflow.id = "workflow-id"
mocker.patch.object(AppGenerateService, "_get_workflow", return_value=workflow)
# Spy on the streaming retrieval path to ensure it's NOT called
retrieve_spy = mocker.patch("services.app_generate_service.AdvancedChatAppGenerator.retrieve_events")
# Make AdvancedChatAppGenerator.generate return a plain dict when streaming=False
generate_spy = mocker.patch(
"services.app_generate_service.AdvancedChatAppGenerator.generate",
return_value={"result": "ok"},
)
# Minimal app model for ADVANCED_CHAT
app_model = MagicMock()
app_model.mode = AppMode.ADVANCED_CHAT
app_model.id = "app-id"
app_model.tenant_id = "tenant-id"
app_model.max_active_requests = 0
app_model.is_agent = False
user = MagicMock()
user.id = "user-id"
# Must include query and inputs for AdvancedChatAppGenerator
args = {"workflow_id": "wf-1", "query": "hello", "inputs": {}}
# Act: call service with streaming=False (blocking mode)
result = AppGenerateService.generate(
app_model=app_model,
user=user,
args=args,
invoke_from=MagicMock(),
streaming=False,
)
# Assert: returns the dict from generate(), and did not call retrieve_events()
assert result == {"result": "ok"}
assert generate_spy.call_args.kwargs.get("streaming") is False
retrieve_spy.assert_not_called()

2
api/uv.lock generated
View File

@ -1366,7 +1366,7 @@ wheels = [
[[package]]
name = "dify-api"
version = "1.12.1"
version = "1.13.0"
source = { virtual = "." }
dependencies = [
{ name = "aliyun-log-python-sdk" },

View File

@ -21,7 +21,7 @@ services:
# API service
api:
image: langgenius/dify-api:1.12.1
image: langgenius/dify-api:1.13.0
restart: always
environment:
# Use the shared environment variables.
@ -63,7 +63,7 @@ services:
# worker service
# The Celery worker for processing all queues (dataset, workflow, mail, etc.)
worker:
image: langgenius/dify-api:1.12.1
image: langgenius/dify-api:1.13.0
restart: always
environment:
# Use the shared environment variables.
@ -102,7 +102,7 @@ services:
# worker_beat service
# Celery beat for scheduling periodic tasks.
worker_beat:
image: langgenius/dify-api:1.12.1
image: langgenius/dify-api:1.13.0
restart: always
environment:
# Use the shared environment variables.
@ -132,7 +132,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:1.12.1
image: langgenius/dify-web:1.13.0
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}

View File

@ -715,7 +715,7 @@ services:
# API service
api:
image: langgenius/dify-api:1.12.1
image: langgenius/dify-api:1.13.0
restart: always
environment:
# Use the shared environment variables.
@ -757,7 +757,7 @@ services:
# worker service
# The Celery worker for processing all queues (dataset, workflow, mail, etc.)
worker:
image: langgenius/dify-api:1.12.1
image: langgenius/dify-api:1.13.0
restart: always
environment:
# Use the shared environment variables.
@ -796,7 +796,7 @@ services:
# worker_beat service
# Celery beat for scheduling periodic tasks.
worker_beat:
image: langgenius/dify-api:1.12.1
image: langgenius/dify-api:1.13.0
restart: always
environment:
# Use the shared environment variables.
@ -826,7 +826,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:1.12.1
image: langgenius/dify-web:1.13.0
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}

View File

@ -1,5 +1,5 @@
# base image
FROM node:24-alpine AS base
FROM node:22-alpine AS base
LABEL maintainer="takatost@gmail.com"
# if you located in China, you can use aliyun mirror to speed up
@ -81,6 +81,8 @@ COPY --chown=dify:dify --chmod=755 docker/entrypoint.sh ./entrypoint.sh
ARG COMMIT_SHA
ENV COMMIT_SHA=${COMMIT_SHA}
RUN chown -R dify:dify /app/web
USER dify
EXPOSE 3000
ENTRYPOINT ["/bin/sh", "./entrypoint.sh"]

View File

@ -1,3 +0,0 @@
export const OAUTH_AUTHORIZE_PENDING_KEY = 'oauth_authorize_pending'
export const REDIRECT_URL_KEY = 'oauth_redirect_url'
export const OAUTH_AUTHORIZE_PENDING_TTL = 60 * 3

View File

@ -7,7 +7,6 @@ import {
RiMailLine,
RiTranslate2,
} from '@remixicon/react'
import dayjs from 'dayjs'
import { useRouter, useSearchParams } from 'next/navigation'
import * as React from 'react'
import { useEffect, useRef } from 'react'
@ -17,22 +16,10 @@ import Button from '@/app/components/base/button'
import Loading from '@/app/components/base/loading'
import Toast from '@/app/components/base/toast'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { setPostLoginRedirect } from '@/app/signin/utils/post-login-redirect'
import { useAppContext } from '@/context/app-context'
import { useIsLogin } from '@/service/use-common'
import { useAuthorizeOAuthApp, useOAuthAppInfo } from '@/service/use-oauth'
import {
OAUTH_AUTHORIZE_PENDING_KEY,
OAUTH_AUTHORIZE_PENDING_TTL,
REDIRECT_URL_KEY,
} from './constants'
function setItemWithExpiry(key: string, value: string, ttl: number) {
const item = {
value,
expiry: dayjs().add(ttl, 'seconds').unix(),
}
localStorage.setItem(key, JSON.stringify(item))
}
function buildReturnUrl(pathname: string, search: string) {
try {
@ -86,8 +73,8 @@ export default function OAuthAuthorize() {
const onLoginSwitchClick = () => {
try {
const returnUrl = buildReturnUrl('/account/oauth/authorize', `?client_id=${encodeURIComponent(client_id)}&redirect_uri=${encodeURIComponent(redirect_uri)}`)
setItemWithExpiry(OAUTH_AUTHORIZE_PENDING_KEY, returnUrl, OAUTH_AUTHORIZE_PENDING_TTL)
router.push(`/signin?${REDIRECT_URL_KEY}=${encodeURIComponent(returnUrl)}`)
setPostLoginRedirect(returnUrl)
router.push('/signin')
}
catch {
router.push('/signin')
@ -145,7 +132,7 @@ export default function OAuthAuthorize() {
<div className="text-[var(--color-saas-dify-blue-inverted)]">{authAppInfo?.app_label[language] || authAppInfo?.app_label?.en_US || t('unknownApp', { ns: 'oauth' })}</div>
{!isLoggedIn && <div className="text-text-primary">{t('tips.notLoggedIn', { ns: 'oauth' })}</div>}
</div>
<div className="body-md-regular text-text-secondary">{isLoggedIn ? `${authAppInfo?.app_label[language] || authAppInfo?.app_label?.en_US || t('unknownApp', { ns: 'oauth' })} ${t('tips.loggedIn', { ns: 'oauth' })}` : t('tips.needLogin', { ns: 'oauth' })}</div>
<div className="text-text-secondary body-md-regular">{isLoggedIn ? `${authAppInfo?.app_label[language] || authAppInfo?.app_label?.en_US || t('unknownApp', { ns: 'oauth' })} ${t('tips.loggedIn', { ns: 'oauth' })}` : t('tips.needLogin', { ns: 'oauth' })}</div>
</div>
{isLoggedIn && userProfile && (
@ -154,7 +141,7 @@ export default function OAuthAuthorize() {
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} />
<div>
<div className="system-md-semi-bold text-text-secondary">{userProfile.name}</div>
<div className="system-xs-regular text-text-tertiary">{userProfile.email}</div>
<div className="text-text-tertiary system-xs-regular">{userProfile.email}</div>
</div>
</div>
<Button variant="tertiary" size="small" onClick={onLoginSwitchClick}>{t('switchAccount', { ns: 'oauth' })}</Button>
@ -166,7 +153,7 @@ export default function OAuthAuthorize() {
{authAppInfo!.scope.split(/\s+/).filter(Boolean).map((scope: string) => {
const Icon = SCOPE_INFO_MAP[scope]
return (
<div key={scope} className="body-sm-medium flex items-center gap-2 text-text-secondary">
<div key={scope} className="flex items-center gap-2 text-text-secondary body-sm-medium">
{Icon ? <Icon.icon className="h-4 w-4" /> : <RiAccountCircleLine className="h-4 w-4" />}
{Icon.label}
</div>
@ -199,7 +186,7 @@ export default function OAuthAuthorize() {
</defs>
</svg>
</div>
<div className="system-xs-regular mt-3 text-text-tertiary">{t('tips.common', { ns: 'oauth' })}</div>
<div className="mt-3 text-text-tertiary system-xs-regular">{t('tips.common', { ns: 'oauth' })}</div>
</div>
)
}

View File

@ -84,7 +84,7 @@ export const AppInitializer = ({
return
}
const redirectUrl = resolvePostLoginRedirect(searchParams)
const redirectUrl = resolvePostLoginRedirect()
if (redirectUrl) {
location.replace(redirectUrl)
return

View File

@ -96,8 +96,10 @@ const buildAppContext = (overrides: Partial<AppContextValue> = {}): AppContextVa
isCurrentWorkspaceEditor: false,
isCurrentWorkspaceDatasetOperator: false,
mutateUserProfile: vi.fn(),
mutateCurrentWorkspace: vi.fn(),
langGeniusVersionInfo,
isLoadingCurrentWorkspace: false,
isValidatingCurrentWorkspace: false,
}
const useSelector: AppContextValue['useSelector'] = selector => selector({ ...base, useSelector })
return {

View File

@ -7,7 +7,6 @@ import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useProviderContext } from '@/context/provider-context'
import { updateCurrentWorkspace } from '@/service/common'
import { useInvalidateCurrentWorkspace } from '@/service/use-common'
import CustomWebAppBrand from './index'
vi.mock('@/app/components/base/toast', () => ({
@ -16,9 +15,6 @@ vi.mock('@/app/components/base/toast', () => ({
vi.mock('@/service/common', () => ({
updateCurrentWorkspace: vi.fn(),
}))
vi.mock('@/service/use-common', () => ({
useInvalidateCurrentWorkspace: vi.fn(),
}))
vi.mock('@/context/app-context', () => ({
useAppContext: vi.fn(),
}))
@ -41,8 +37,6 @@ const mockUseProviderContext = vi.mocked(useProviderContext)
const mockUseGlobalPublicStore = vi.mocked(useGlobalPublicStore)
const mockImageUpload = vi.mocked(imageUpload)
const mockGetImageUploadErrorMessage = vi.mocked(getImageUploadErrorMessage)
const mockInvalidateCurrentWorkspace = vi.fn()
vi.mocked(useInvalidateCurrentWorkspace).mockReturnValue(mockInvalidateCurrentWorkspace)
const defaultPlanUsage = {
buildApps: 0,
@ -68,6 +62,7 @@ describe('CustomWebAppBrand', () => {
remove_webapp_brand: false,
},
},
mutateCurrentWorkspace: vi.fn(),
isCurrentWorkspaceManager: true,
} as any)
mockUseProviderContext.mockReturnValue({
@ -97,6 +92,7 @@ describe('CustomWebAppBrand', () => {
remove_webapp_brand: false,
},
},
mutateCurrentWorkspace: vi.fn(),
isCurrentWorkspaceManager: false,
} as any)
@ -105,7 +101,8 @@ describe('CustomWebAppBrand', () => {
expect(fileInput).toBeDisabled()
})
it('toggles remove brand switch and calls the backend + invalidate', async () => {
it('toggles remove brand switch and calls the backend + mutate', async () => {
const mutateMock = vi.fn()
mockUseAppContext.mockReturnValue({
currentWorkspace: {
custom_config: {
@ -113,6 +110,7 @@ describe('CustomWebAppBrand', () => {
remove_webapp_brand: false,
},
},
mutateCurrentWorkspace: mutateMock,
isCurrentWorkspaceManager: true,
} as any)
@ -124,7 +122,7 @@ describe('CustomWebAppBrand', () => {
url: '/workspaces/custom-config',
body: { remove_webapp_brand: true },
}))
await waitFor(() => expect(mockInvalidateCurrentWorkspace).toHaveBeenCalled())
await waitFor(() => expect(mutateMock).toHaveBeenCalled())
})
it('shows cancel/apply buttons after successful upload and cancels properly', async () => {

View File

@ -24,7 +24,6 @@ import { useProviderContext } from '@/context/provider-context'
import {
updateCurrentWorkspace,
} from '@/service/common'
import { useInvalidateCurrentWorkspace } from '@/service/use-common'
import { cn } from '@/utils/classnames'
const ALLOW_FILE_EXTENSIONS = ['svg', 'png']
@ -35,9 +34,9 @@ const CustomWebAppBrand = () => {
const { plan, enableBilling } = useProviderContext()
const {
currentWorkspace,
mutateCurrentWorkspace,
isCurrentWorkspaceManager,
} = useAppContext()
const invalidateCurrentWorkspace = useInvalidateCurrentWorkspace()
const [fileId, setFileId] = useState('')
const [imgKey, setImgKey] = useState(() => Date.now())
const [uploadProgress, setUploadProgress] = useState(0)
@ -84,7 +83,7 @@ const CustomWebAppBrand = () => {
replace_webapp_logo: fileId,
},
})
invalidateCurrentWorkspace()
mutateCurrentWorkspace()
setFileId('')
setImgKey(Date.now())
}
@ -97,7 +96,7 @@ const CustomWebAppBrand = () => {
replace_webapp_logo: '',
},
})
invalidateCurrentWorkspace()
mutateCurrentWorkspace()
}
const handleSwitch = async (checked: boolean) => {
@ -107,7 +106,7 @@ const CustomWebAppBrand = () => {
remove_webapp_brand: checked,
},
})
invalidateCurrentWorkspace()
mutateCurrentWorkspace()
}
const handleCancel = () => {
@ -117,7 +116,7 @@ const CustomWebAppBrand = () => {
return (
<div className="py-4">
<div className="mb-2 flex items-center justify-between rounded-xl bg-background-section-burn p-4 text-text-primary system-md-medium">
<div className="system-md-medium mb-2 flex items-center justify-between rounded-xl bg-background-section-burn p-4 text-text-primary">
{t('webapp.removeBrand', { ns: 'custom' })}
<Switch
size="l"
@ -128,8 +127,8 @@ const CustomWebAppBrand = () => {
</div>
<div className={cn('flex h-14 items-center justify-between rounded-xl bg-background-section-burn px-4', webappBrandRemoved && 'opacity-30')}>
<div>
<div className="text-text-primary system-md-medium">{t('webapp.changeLogo', { ns: 'custom' })}</div>
<div className="text-text-tertiary system-xs-regular">{t('webapp.changeLogoTip', { ns: 'custom' })}</div>
<div className="system-md-medium text-text-primary">{t('webapp.changeLogo', { ns: 'custom' })}</div>
<div className="system-xs-regular text-text-tertiary">{t('webapp.changeLogoTip', { ns: 'custom' })}</div>
</div>
<div className="flex items-center">
{(!uploadDisabled && webappLogo && !webappBrandRemoved) && (
@ -205,7 +204,7 @@ const CustomWebAppBrand = () => {
<div className="mt-2 text-xs text-[#D92D20]">{t('uploadedFail', { ns: 'custom' })}</div>
)}
<div className="mb-2 mt-5 flex items-center gap-2">
<div className="shrink-0 text-text-tertiary system-xs-medium-uppercase">{t('overview.appInfo.preview', { ns: 'appOverview' })}</div>
<div className="system-xs-medium-uppercase shrink-0 text-text-tertiary">{t('overview.appInfo.preview', { ns: 'appOverview' })}</div>
<Divider bgStyle="gradient" className="grow" />
</div>
<div className="relative mb-2 flex items-center gap-3">
@ -216,7 +215,7 @@ const CustomWebAppBrand = () => {
<div className={cn('inline-flex h-8 w-8 items-center justify-center rounded-lg border border-divider-regular', 'bg-components-icon-bg-blue-light-solid')}>
<BubbleTextMod className="h-4 w-4 text-components-avatar-shape-fill-stop-100" />
</div>
<div className="grow text-text-secondary system-md-semibold">Chatflow App</div>
<div className="system-md-semibold grow text-text-secondary">Chatflow App</div>
<div className="p-1.5">
<RiLayoutLeft2Line className="h-4 w-4 text-text-tertiary" />
</div>
@ -247,7 +246,7 @@ const CustomWebAppBrand = () => {
<div className="flex items-center gap-1.5">
{!webappBrandRemoved && (
<>
<div className="text-text-tertiary system-2xs-medium-uppercase">POWERED BY</div>
<div className="system-2xs-medium-uppercase text-text-tertiary">POWERED BY</div>
{
systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
? <img src={systemFeatures.branding.workspace_logo} alt="logo" className="block h-5 w-auto" />
@ -263,12 +262,12 @@ const CustomWebAppBrand = () => {
<div className="flex w-[138px] grow flex-col justify-between p-2 pr-0">
<div className="flex grow flex-col justify-between rounded-l-2xl border-[0.5px] border-r-0 border-components-panel-border-subtle bg-chatbot-bg pb-4 pl-[22px] pt-16">
<div className="w-[720px] rounded-2xl border border-divider-subtle bg-chat-bubble-bg px-4 py-3">
<div className="mb-1 text-text-primary body-md-regular">Hello! How can I assist you today?</div>
<div className="body-md-regular mb-1 text-text-primary">Hello! How can I assist you today?</div>
<Button size="small">
<div className="h-2 w-[144px] rounded-sm bg-text-quaternary opacity-20"></div>
</Button>
</div>
<div className="flex h-[52px] w-[578px] items-center rounded-xl border border-components-chat-input-border bg-components-panel-bg-blur pl-3.5 text-text-placeholder shadow-md backdrop-blur-sm body-lg-regular">Talk to Dify</div>
<div className="body-lg-regular flex h-[52px] w-[578px] items-center rounded-xl border border-components-chat-input-border bg-components-panel-bg-blur pl-3.5 text-text-placeholder shadow-md backdrop-blur-sm">Talk to Dify</div>
</div>
</div>
</div>
@ -279,14 +278,14 @@ const CustomWebAppBrand = () => {
<div className={cn('inline-flex h-8 w-8 items-center justify-center rounded-lg border border-divider-regular', 'bg-components-icon-bg-indigo-solid')}>
<RiExchange2Fill className="h-4 w-4 text-components-avatar-shape-fill-stop-100" />
</div>
<div className="grow text-text-secondary system-md-semibold">Workflow App</div>
<div className="system-md-semibold grow text-text-secondary">Workflow App</div>
<div className="p-1.5">
<RiLayoutLeft2Line className="h-4 w-4 text-text-tertiary" />
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex h-10 shrink-0 items-center border-b-2 border-components-tab-active text-text-primary system-md-semibold-uppercase">RUN ONCE</div>
<div className="flex h-10 grow items-center border-b-2 border-transparent text-text-tertiary system-md-semibold-uppercase">RUN BATCH</div>
<div className="system-md-semibold-uppercase flex h-10 shrink-0 items-center border-b-2 border-components-tab-active text-text-primary">RUN ONCE</div>
<div className="system-md-semibold-uppercase flex h-10 grow items-center border-b-2 border-transparent text-text-tertiary">RUN BATCH</div>
</div>
</div>
<div className="grow bg-components-panel-bg">
@ -294,7 +293,7 @@ const CustomWebAppBrand = () => {
<div className="mb-1 py-2">
<div className="h-2 w-20 rounded-sm bg-text-quaternary opacity-20"></div>
</div>
<div className="h-16 w-full rounded-lg bg-components-input-bg-normal"></div>
<div className="h-16 w-full rounded-lg bg-components-input-bg-normal "></div>
</div>
<div className="flex items-center justify-between px-4 py-3">
<Button size="small">
@ -309,7 +308,7 @@ const CustomWebAppBrand = () => {
<div className="flex h-12 shrink-0 items-center gap-1.5 bg-components-panel-bg p-4 pt-3">
{!webappBrandRemoved && (
<>
<div className="text-text-tertiary system-2xs-medium-uppercase">POWERED BY</div>
<div className="system-2xs-medium-uppercase text-text-tertiary">POWERED BY</div>
{
systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
? <img src={systemFeatures.branding.workspace_logo} alt="logo" className="block h-5 w-auto" />

View File

@ -6,9 +6,10 @@ import {
RiBrainLine,
} from '@remixicon/react'
import { useDebounce } from 'ahooks'
import { useMemo } from 'react'
import { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { IS_CLOUD_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useProviderContext } from '@/context/provider-context'
import { cn } from '@/utils/classnames'
@ -33,6 +34,7 @@ const FixedModelProvider = ['langgenius/openai/openai', 'langgenius/anthropic/an
const ModelProviderPage = ({ searchText }: Props) => {
const debouncedSearchText = useDebounce(searchText, { wait: 500 })
const { t } = useTranslation()
const { mutateCurrentWorkspace, isValidatingCurrentWorkspace } = useAppContext()
const { data: textGenerationDefaultModel, isLoading: isTextGenerationDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textGeneration)
const { data: embeddingsDefaultModel, isLoading: isEmbeddingsDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textEmbedding)
const { data: rerankDefaultModel, isLoading: isRerankDefaultModelLoading } = useDefaultModel(ModelTypeEnum.rerank)
@ -90,10 +92,14 @@ const ModelProviderPage = ({ searchText }: Props) => {
return [filteredConfiguredProviders, filteredNotConfiguredProviders]
}, [configuredProviders, debouncedSearchText, notConfiguredProviders])
useEffect(() => {
mutateCurrentWorkspace()
}, [mutateCurrentWorkspace])
return (
<div className="relative -mt-2 pt-1">
<div className={cn('mb-2 flex items-center')}>
<div className="grow text-text-primary system-md-semibold">{t('modelProvider.models', { ns: 'common' })}</div>
<div className="system-md-semibold grow text-text-primary">{t('modelProvider.models', { ns: 'common' })}</div>
<div className={cn(
'relative flex shrink-0 items-center justify-end gap-2 rounded-lg border border-transparent p-px',
defaultModelNotConfigured && 'border-components-panel-border bg-components-panel-bg-blur pl-2 shadow-xs',
@ -101,7 +107,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
>
{defaultModelNotConfigured && <div className="absolute bottom-0 left-0 right-0 top-0 opacity-40" style={{ background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)' }} />}
{defaultModelNotConfigured && (
<div className="flex items-center gap-1 text-text-primary system-xs-medium">
<div className="system-xs-medium flex items-center gap-1 text-text-primary">
<RiAlertFill className="h-4 w-4 text-text-warning-secondary" />
<span className="max-w-[460px] truncate" title={t('modelProvider.notConfigured', { ns: 'common' })}>{t('modelProvider.notConfigured', { ns: 'common' })}</span>
</div>
@ -117,14 +123,14 @@ const ModelProviderPage = ({ searchText }: Props) => {
/>
</div>
</div>
{IS_CLOUD_EDITION && <QuotaPanel providers={providers} />}
{IS_CLOUD_EDITION && <QuotaPanel providers={providers} isLoading={isValidatingCurrentWorkspace} />}
{!filteredConfiguredProviders?.length && (
<div className="mb-2 rounded-[10px] bg-workflow-process-bg p-4">
<div className="flex h-10 w-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur">
<RiBrainLine className="h-5 w-5 text-text-primary" />
</div>
<div className="mt-2 text-text-secondary system-sm-medium">{t('modelProvider.emptyProviderTitle', { ns: 'common' })}</div>
<div className="mt-1 text-text-tertiary system-xs-regular">{t('modelProvider.emptyProviderTip', { ns: 'common' })}</div>
<div className="system-sm-medium mt-2 text-text-secondary">{t('modelProvider.emptyProviderTitle', { ns: 'common' })}</div>
<div className="system-xs-regular mt-1 text-text-tertiary">{t('modelProvider.emptyProviderTip', { ns: 'common' })}</div>
</div>
)}
{!!filteredConfiguredProviders?.length && (
@ -139,7 +145,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
)}
{!!filteredNotConfiguredProviders?.length && (
<>
<div className="mb-2 flex items-center pt-2 text-text-primary system-md-semibold">{t('modelProvider.toBeConfigured', { ns: 'common' })}</div>
<div className="system-md-semibold mb-2 flex items-center pt-2 text-text-primary">{t('modelProvider.toBeConfigured', { ns: 'common' })}</div>
<div className="relative">
{filteredNotConfiguredProviders?.map(provider => (
<ProviderAddedCard

View File

@ -48,12 +48,14 @@ const providerKeyToPluginId: Record<ModelProviderQuotaGetPaid, string> = {
type QuotaPanelProps = {
providers: ModelProvider[]
isLoading?: boolean
}
const QuotaPanel: FC<QuotaPanelProps> = ({
providers,
isLoading = false,
}) => {
const { t } = useTranslation()
const { currentWorkspace, isLoadingCurrentWorkspace } = useAppContext()
const { currentWorkspace } = useAppContext()
const { trial_models } = useGlobalPublicStore(s => s.systemFeatures)
const credits = Math.max((currentWorkspace.trial_credits - currentWorkspace.trial_credits_used) || 0, 0)
const providerMap = useMemo(() => new Map(
@ -96,7 +98,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
}
}, [providers, isShowInstallModal, hideInstallFromMarketplace])
if (isLoadingCurrentWorkspace) {
if (isLoading) {
return (
<div className="my-2 flex min-h-[72px] items-center justify-center rounded-xl border-[0.5px] border-components-panel-border bg-third-party-model-bg-default shadow-xs">
<Loading />
@ -104,18 +106,15 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
)
}
if (!currentWorkspace.id)
return null
return (
<div className={cn('my-2 min-w-[72px] shrink-0 rounded-xl border-[0.5px] pb-2.5 pl-4 pr-2.5 pt-3 shadow-xs', credits <= 0 ? 'border-state-destructive-border hover:bg-state-destructive-hover' : 'border-components-panel-border bg-third-party-model-bg-default')}>
<div className="mb-2 flex h-4 items-center text-text-tertiary system-xs-medium-uppercase">
<div className="system-xs-medium-uppercase mb-2 flex h-4 items-center text-text-tertiary">
{t('modelProvider.quota', { ns: 'common' })}
<Tooltip popupContent={t('modelProvider.card.tip', { ns: 'common', modelNames: trial_models.map(key => modelNameMap[key as keyof typeof modelNameMap]).filter(Boolean).join(', ') })} />
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1 text-xs text-text-tertiary">
<span className="mr-0.5 text-text-secondary system-md-semibold-uppercase">{formatNumber(credits)}</span>
<span className="system-md-semibold-uppercase mr-0.5 text-text-secondary">{formatNumber(credits)}</span>
<span>{t('modelProvider.credits', { ns: 'common' })}</span>
{currentWorkspace.next_credit_reset_date
? (

View File

@ -1,43 +0,0 @@
'use client'
import { SerwistProvider } from '@serwist/turbopack/react'
import { useEffect } from 'react'
import { IS_DEV } from '@/config'
import { env } from '@/env'
import { isClient } from '@/utils/client'
export function PWAProvider({ children }: { children: React.ReactNode }) {
if (IS_DEV) {
return <DisabledPWAProvider>{children}</DisabledPWAProvider>
}
const basePath = env.NEXT_PUBLIC_BASE_PATH
const swUrl = `${basePath}/serwist/sw.js`
return (
<SerwistProvider swUrl={swUrl}>
{children}
</SerwistProvider>
)
}
function DisabledPWAProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (isClient && 'serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations()
.then((registrations) => {
registrations.forEach((registration) => {
registration.unregister()
.catch((error) => {
console.error('Error unregistering service worker:', error)
})
})
})
.catch((error) => {
console.error('Error unregistering service workers:', error)
})
}
}, [])
return <>{children}</>
}

View File

@ -12,7 +12,6 @@ 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 { PWAProvider } from './components/provider/serwist'
import SentryInitializer from './components/sentry-initializer'
import RoutePrefixHandle from './routePrefixHandle'
import './styles/globals.css'
@ -60,35 +59,33 @@ const LocaleLayout = async ({
className="color-scheme h-full select-auto"
{...datasetMap}
>
<PWAProvider>
<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 />
</PWAProvider>
<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 />
</body>
</html>
)

View File

@ -1,12 +0,0 @@
import { createSerwistRoute } from '@serwist/turbopack'
import { env } from '@/env'
const basePath = env.NEXT_PUBLIC_BASE_PATH
export const { dynamic, dynamicParams, revalidate, generateStaticParams, GET } = createSerwistRoute({
swSrc: 'app/sw.ts',
nextConfig: {
basePath,
},
useNativeEsbuild: true,
})

View File

@ -57,7 +57,7 @@ export default function CheckCode() {
router.replace(`/signin/invite-settings?${searchParams.toString()}`)
}
else {
const redirectUrl = resolvePostLoginRedirect(searchParams)
const redirectUrl = resolvePostLoginRedirect()
router.replace(redirectUrl || '/apps')
}
}
@ -95,8 +95,8 @@ export default function CheckCode() {
<RiMailSendFill className="h-6 w-6 text-2xl text-text-accent-light-mode-only" />
</div>
<div className="pb-4 pt-2">
<h2 className="title-4xl-semi-bold text-text-primary">{t('checkCode.checkYourEmail', { ns: 'login' })}</h2>
<p className="body-md-regular mt-2 text-text-secondary">
<h2 className="text-text-primary title-4xl-semi-bold">{t('checkCode.checkYourEmail', { ns: 'login' })}</h2>
<p className="mt-2 text-text-secondary body-md-regular">
<span>
{t('checkCode.tipsPrefix', { ns: 'login' })}
<strong>{email}</strong>
@ -107,7 +107,7 @@ export default function CheckCode() {
</div>
<form onSubmit={handleSubmit}>
<label htmlFor="code" className="system-md-semibold mb-1 text-text-secondary">{t('checkCode.verificationCode', { ns: 'login' })}</label>
<label htmlFor="code" className="mb-1 text-text-secondary system-md-semibold">{t('checkCode.verificationCode', { ns: 'login' })}</label>
<Input
ref={codeInputRef}
id="code"
@ -127,7 +127,7 @@ export default function CheckCode() {
<div className="inline-block rounded-full bg-background-default-dimmed p-1">
<RiArrowLeftLine size={12} />
</div>
<span className="system-xs-regular ml-2">{t('back', { ns: 'login' })}</span>
<span className="ml-2 system-xs-regular">{t('back', { ns: 'login' })}</span>
</div>
</div>
)

View File

@ -78,7 +78,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
router.replace(`/signin/invite-settings?${searchParams.toString()}`)
}
else {
const redirectUrl = resolvePostLoginRedirect(searchParams)
const redirectUrl = resolvePostLoginRedirect()
router.replace(redirectUrl || '/apps')
}
}
@ -105,7 +105,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
return (
<form onSubmit={noop}>
<div className="mb-3">
<label htmlFor="email" className="system-md-semibold my-2 text-text-secondary">
<label htmlFor="email" className="my-2 text-text-secondary system-md-semibold">
{t('email', { ns: 'login' })}
</label>
<div className="mt-1">
@ -124,7 +124,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
<div className="mb-3">
<label htmlFor="password" className="my-2 flex items-center justify-between">
<span className="system-md-semibold text-text-secondary">{t('password', { ns: 'login' })}</span>
<span className="text-text-secondary system-md-semibold">{t('password', { ns: 'login' })}</span>
<Link
href={`/reset-password?${searchParams.toString()}`}
className={`system-xs-regular ${isEmailSetup ? 'text-components-button-secondary-accent-text' : 'pointer-events-none text-components-button-secondary-accent-text-disabled'}`}

View File

@ -56,7 +56,7 @@ export default function InviteSettingsPage() {
if (res.result === 'success') {
// Tokens are now stored in cookies by the backend
await setLocaleOnClient(language, false)
const redirectUrl = resolvePostLoginRedirect(searchParams)
const redirectUrl = resolvePostLoginRedirect()
router.replace(redirectUrl || '/apps')
}
}
@ -72,7 +72,7 @@ export default function InviteSettingsPage() {
<div className="flex flex-col md:w-[400px]">
<div className="mx-auto w-full">
<div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle text-2xl font-bold shadow-lg">🤷</div>
<h2 className="title-4xl-semi-bold text-text-primary">{t('invalid', { ns: 'login' })}</h2>
<h2 className="text-text-primary title-4xl-semi-bold">{t('invalid', { ns: 'login' })}</h2>
</div>
<div className="mx-auto mt-6 w-full">
<Button variant="primary" className="w-full !text-sm">
@ -89,11 +89,11 @@ export default function InviteSettingsPage() {
<RiAccountCircleLine className="h-6 w-6 text-2xl text-text-accent-light-mode-only" />
</div>
<div className="pb-4 pt-2">
<h2 className="title-4xl-semi-bold text-text-primary">{t('setYourAccount', { ns: 'login' })}</h2>
<h2 className="text-text-primary title-4xl-semi-bold">{t('setYourAccount', { ns: 'login' })}</h2>
</div>
<form onSubmit={noop}>
<div className="mb-5">
<label htmlFor="name" className="system-md-semibold my-2 text-text-secondary">
<label htmlFor="name" className="my-2 text-text-secondary system-md-semibold">
{t('name', { ns: 'login' })}
</label>
<div className="mt-1">
@ -114,7 +114,7 @@ export default function InviteSettingsPage() {
</div>
</div>
<div className="mb-5">
<label htmlFor="name" className="system-md-semibold my-2 text-text-secondary">
<label htmlFor="name" className="my-2 text-text-secondary system-md-semibold">
{t('interfaceLanguage', { ns: 'login' })}
</label>
<div className="mt-1">
@ -129,7 +129,7 @@ export default function InviteSettingsPage() {
</div>
{/* timezone */}
<div className="mb-5">
<label htmlFor="timezone" className="system-md-semibold text-text-secondary">
<label htmlFor="timezone" className="text-text-secondary system-md-semibold">
{t('timezone', { ns: 'login' })}
</label>
<div className="mt-1">
@ -153,11 +153,11 @@ export default function InviteSettingsPage() {
</div>
</form>
{!systemFeatures.branding.enabled && (
<div className="system-xs-regular mt-2 block w-full text-text-tertiary">
<div className="mt-2 block w-full text-text-tertiary system-xs-regular">
{t('license.tip', { ns: 'login' })}
&nbsp;
<Link
className="system-xs-medium text-text-accent-secondary"
className="text-text-accent-secondary system-xs-medium"
target="_blank"
rel="noopener noreferrer"
href={LICENSE_LINK}

View File

@ -42,7 +42,7 @@ const NormalForm = () => {
try {
if (isLoggedIn) {
setIsRedirecting(true)
const redirectUrl = resolvePostLoginRedirect(searchParams)
const redirectUrl = resolvePostLoginRedirect()
router.replace(redirectUrl || '/apps')
return
}
@ -98,8 +98,8 @@ const NormalForm = () => {
<RiContractLine className="h-5 w-5" />
<RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" />
</div>
<p className="system-sm-medium text-text-primary">{t('licenseLost', { ns: 'login' })}</p>
<p className="system-xs-regular mt-1 text-text-tertiary">{t('licenseLostTip', { ns: 'login' })}</p>
<p className="text-text-primary system-sm-medium">{t('licenseLost', { ns: 'login' })}</p>
<p className="mt-1 text-text-tertiary system-xs-regular">{t('licenseLostTip', { ns: 'login' })}</p>
</div>
</div>
</div>
@ -114,8 +114,8 @@ const NormalForm = () => {
<RiContractLine className="h-5 w-5" />
<RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" />
</div>
<p className="system-sm-medium text-text-primary">{t('licenseExpired', { ns: 'login' })}</p>
<p className="system-xs-regular mt-1 text-text-tertiary">{t('licenseExpiredTip', { ns: 'login' })}</p>
<p className="text-text-primary system-sm-medium">{t('licenseExpired', { ns: 'login' })}</p>
<p className="mt-1 text-text-tertiary system-xs-regular">{t('licenseExpiredTip', { ns: 'login' })}</p>
</div>
</div>
</div>
@ -130,8 +130,8 @@ const NormalForm = () => {
<RiContractLine className="h-5 w-5" />
<RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" />
</div>
<p className="system-sm-medium text-text-primary">{t('licenseInactive', { ns: 'login' })}</p>
<p className="system-xs-regular mt-1 text-text-tertiary">{t('licenseInactiveTip', { ns: 'login' })}</p>
<p className="text-text-primary system-sm-medium">{t('licenseInactive', { ns: 'login' })}</p>
<p className="mt-1 text-text-tertiary system-xs-regular">{t('licenseInactiveTip', { ns: 'login' })}</p>
</div>
</div>
</div>
@ -144,12 +144,12 @@ const NormalForm = () => {
{isInviteLink
? (
<div className="mx-auto w-full">
<h2 className="title-4xl-semi-bold text-text-primary">
<h2 className="text-text-primary title-4xl-semi-bold">
{t('join', { ns: 'login' })}
{workspaceName}
</h2>
{!systemFeatures.branding.enabled && (
<p className="body-md-regular mt-2 text-text-tertiary">
<p className="mt-2 text-text-tertiary body-md-regular">
{t('joinTipStart', { ns: 'login' })}
{workspaceName}
{t('joinTipEnd', { ns: 'login' })}
@ -159,8 +159,8 @@ const NormalForm = () => {
)
: (
<div className="mx-auto w-full">
<h2 className="title-4xl-semi-bold text-text-primary">{systemFeatures.branding.enabled ? t('pageTitleForE', { ns: 'login' }) : t('pageTitle', { ns: 'login' })}</h2>
<p className="body-md-regular mt-2 text-text-tertiary">{t('welcome', { ns: 'login' })}</p>
<h2 className="text-text-primary title-4xl-semi-bold">{systemFeatures.branding.enabled ? t('pageTitleForE', { ns: 'login' }) : t('pageTitle', { ns: 'login' })}</h2>
<p className="mt-2 text-text-tertiary body-md-regular">{t('welcome', { ns: 'login' })}</p>
</div>
)}
<div className="relative">
@ -177,7 +177,7 @@ const NormalForm = () => {
<div className="relative mt-6">
<div className="flex items-center">
<div className="h-px flex-1 bg-gradient-to-r from-background-gradient-mask-transparent to-divider-regular"></div>
<span className="system-xs-medium-uppercase px-3 text-text-tertiary">{t('or', { ns: 'login' })}</span>
<span className="px-3 text-text-tertiary system-xs-medium-uppercase">{t('or', { ns: 'login' })}</span>
<div className="h-px flex-1 bg-gradient-to-l from-background-gradient-mask-transparent to-divider-regular"></div>
</div>
</div>
@ -190,7 +190,7 @@ const NormalForm = () => {
<MailAndCodeAuth isInvite={isInviteLink} />
{systemFeatures.enable_email_password_login && (
<div className="cursor-pointer py-1 text-center" onClick={() => { updateAuthType('password') }}>
<span className="system-xs-medium text-components-button-secondary-accent-text">{t('usePassword', { ns: 'login' })}</span>
<span className="text-components-button-secondary-accent-text system-xs-medium">{t('usePassword', { ns: 'login' })}</span>
</div>
)}
</>
@ -200,7 +200,7 @@ const NormalForm = () => {
<MailAndPasswordAuth isInvite={isInviteLink} isEmailSetup={systemFeatures.is_email_setup} allowRegistration={systemFeatures.is_allow_register} />
{systemFeatures.enable_email_code_login && (
<div className="cursor-pointer py-1 text-center" onClick={() => { updateAuthType('code') }}>
<span className="system-xs-medium text-components-button-secondary-accent-text">{t('useVerificationCode', { ns: 'login' })}</span>
<span className="text-components-button-secondary-accent-text system-xs-medium">{t('useVerificationCode', { ns: 'login' })}</span>
</div>
)}
</>
@ -227,8 +227,8 @@ const NormalForm = () => {
<div className="shadows-shadow-lg mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow">
<RiDoorLockLine className="h-5 w-5" />
</div>
<p className="system-sm-medium text-text-primary">{t('noLoginMethod', { ns: 'login' })}</p>
<p className="system-xs-regular mt-1 text-text-tertiary">{t('noLoginMethodTip', { ns: 'login' })}</p>
<p className="text-text-primary system-sm-medium">{t('noLoginMethod', { ns: 'login' })}</p>
<p className="mt-1 text-text-tertiary system-xs-regular">{t('noLoginMethodTip', { ns: 'login' })}</p>
</div>
<div className="relative my-2 py-2">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
@ -239,11 +239,11 @@ const NormalForm = () => {
)}
{!systemFeatures.branding.enabled && (
<>
<div className="system-xs-regular mt-2 block w-full text-text-tertiary">
<div className="mt-2 block w-full text-text-tertiary system-xs-regular">
{t('tosDesc', { ns: 'login' })}
&nbsp;
<Link
className="system-xs-medium text-text-secondary hover:underline"
className="text-text-secondary system-xs-medium hover:underline"
target="_blank"
rel="noopener noreferrer"
href="https://dify.ai/terms"
@ -252,7 +252,7 @@ const NormalForm = () => {
</Link>
&nbsp;&&nbsp;
<Link
className="system-xs-medium text-text-secondary hover:underline"
className="text-text-secondary system-xs-medium hover:underline"
target="_blank"
rel="noopener noreferrer"
href="https://dify.ai/privacy"
@ -261,11 +261,11 @@ const NormalForm = () => {
</Link>
</div>
{IS_CE_EDITION && (
<div className="w-hull system-xs-regular mt-2 block text-text-tertiary">
<div className="w-hull mt-2 block text-text-tertiary system-xs-regular">
{t('goToInit', { ns: 'login' })}
&nbsp;
<Link
className="system-xs-medium text-text-secondary hover:underline"
className="text-text-secondary system-xs-medium hover:underline"
href="/install"
>
{t('setAdminAccount', { ns: 'login' })}

View File

@ -1,37 +1,15 @@
import type { ReadonlyURLSearchParams } from 'next/navigation'
import dayjs from 'dayjs'
import { OAUTH_AUTHORIZE_PENDING_KEY, REDIRECT_URL_KEY } from '@/app/account/oauth/authorize/constants'
let postLoginRedirect: string | null = null
function getItemWithExpiry(key: string): string | null {
const itemStr = localStorage.getItem(key)
if (!itemStr)
return null
try {
const item = JSON.parse(itemStr)
localStorage.removeItem(key)
if (!item?.value)
return null
return dayjs().unix() > item.expiry ? null : item.value
}
catch {
return null
}
export const setPostLoginRedirect = (value: string | null) => {
postLoginRedirect = value
}
export const resolvePostLoginRedirect = (searchParams: ReadonlyURLSearchParams) => {
const redirectUrl = searchParams.get(REDIRECT_URL_KEY)
if (redirectUrl) {
try {
localStorage.removeItem(OAUTH_AUTHORIZE_PENDING_KEY)
return decodeURIComponent(redirectUrl)
}
catch (e) {
console.error('Failed to decode redirect URL:', e)
return redirectUrl
}
export const resolvePostLoginRedirect = () => {
if (postLoginRedirect) {
const redirectUrl = postLoginRedirect
postLoginRedirect = null
return redirectUrl
}
return getItemWithExpiry(OAUTH_AUTHORIZE_PENDING_KEY)
return null
}

View File

@ -1,59 +0,0 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import type { PrecacheEntry, SerwistGlobalConfig } from 'serwist'
import { defaultCache } from '@serwist/turbopack/worker'
import { Serwist } from 'serwist'
import { withLeadingSlash } from 'ufo'
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 normalizeManifestUrl = (url: string): string => {
if (url.startsWith('/serwist/'))
return url.replace(/^\/serwist\//, '/')
return withLeadingSlash(url)
}
const manifest = self.__SW_MANIFEST?.map((entry) => {
if (typeof entry === 'string')
return normalizeManifestUrl(entry)
return {
...entry,
url: normalizeManifestUrl(entry.url),
}
})
const serwist = new Serwist({
precacheEntries: manifest,
skipWaiting: true,
disableDevLogs: true,
clientsClaim: true,
navigationPreload: true,
runtimeCaching: defaultCache,
fallbacks: {
entries: [
{
url: offlineUrl,
matcher({ request }) {
return request.destination === 'document'
},
},
],
},
})
serwist.addEventListeners()

View File

@ -26,9 +26,11 @@ export type AppContextValue = {
isCurrentWorkspaceOwner: boolean
isCurrentWorkspaceEditor: boolean
isCurrentWorkspaceDatasetOperator: boolean
mutateCurrentWorkspace: VoidFunction
langGeniusVersionInfo: LangGeniusVersionResponse
useSelector: typeof useSelector
isLoadingCurrentWorkspace: boolean
isValidatingCurrentWorkspace: boolean
}
const userProfilePlaceholder = {
@ -71,9 +73,11 @@ const AppContext = createContext<AppContextValue>({
isCurrentWorkspaceEditor: false,
isCurrentWorkspaceDatasetOperator: false,
mutateUserProfile: noop,
mutateCurrentWorkspace: noop,
langGeniusVersionInfo: initialLangGeniusVersionInfo,
useSelector,
isLoadingCurrentWorkspace: false,
isValidatingCurrentWorkspace: false,
})
export function useSelector<T>(selector: (value: AppContextValue) => T): T {
@ -88,7 +92,7 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
const queryClient = useQueryClient()
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const { data: userProfileResp } = useUserProfile()
const { data: currentWorkspaceResp, isPending: isLoadingCurrentWorkspace } = useCurrentWorkspace()
const { data: currentWorkspaceResp, isPending: isLoadingCurrentWorkspace, isFetching: isValidatingCurrentWorkspace } = useCurrentWorkspace()
const langGeniusVersionQuery = useLangGeniusVersion(
userProfileResp?.meta.currentVersion,
!systemFeatures.branding.enabled,
@ -120,6 +124,10 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
queryClient.invalidateQueries({ queryKey: ['common', 'user-profile'] })
}, [queryClient])
const mutateCurrentWorkspace = useCallback(() => {
queryClient.invalidateQueries({ queryKey: ['common', 'current-workspace'] })
}, [queryClient])
// #region Zendesk conversation fields
useEffect(() => {
if (ZENDESK_FIELD_IDS.ENVIRONMENT && langGeniusVersionInfo?.current_env) {
@ -191,7 +199,9 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
isCurrentWorkspaceOwner,
isCurrentWorkspaceEditor,
isCurrentWorkspaceDatasetOperator,
mutateCurrentWorkspace,
isLoadingCurrentWorkspace,
isValidatingCurrentWorkspace,
}}
>
<div className="flex h-full flex-col overflow-y-auto">

View File

@ -43,4 +43,4 @@ export NEXT_PUBLIC_MAX_PARALLEL_LIMIT=${MAX_PARALLEL_LIMIT}
export NEXT_PUBLIC_MAX_ITERATIONS_NUM=${MAX_ITERATIONS_NUM}
export NEXT_PUBLIC_MAX_TREE_DEPTH=${MAX_TREE_DEPTH}
pm2 start /app/web/server.js --name dify-web --cwd /app/web -i ${PM2_INSTANCES} --no-daemon
node --heapsnapshot-signal=SIGUSR2 /app/web/server.js

View File

@ -278,9 +278,6 @@
}
},
"app/account/oauth/authorize/page.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 4
},
"ts/no-explicit-any": {
"count": 1
}
@ -3076,6 +3073,12 @@
}
},
"app/components/custom/custom-web-app-brand/index.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 12
},
"tailwindcss/no-unnecessary-whitespace": {
"count": 1
},
"ts/no-explicit-any": {
"count": 2
}
@ -4512,6 +4515,11 @@
"count": 3
}
},
"app/components/header/account-setting/model-provider-page/index.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 5
}
},
"app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 3
@ -4715,6 +4723,11 @@
"count": 3
}
},
"app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 2
}
},
"app/components/header/account-setting/model-provider-page/provider-icon/index.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 1
@ -8577,29 +8590,16 @@
"count": 6
}
},
"app/signin/check-code/page.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 4
}
},
"app/signin/components/mail-and-code-auth.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 1
}
},
"app/signin/components/mail-and-password-auth.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 2
},
"ts/no-explicit-any": {
"count": 1
}
},
"app/signin/invite-settings/page.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 7
}
},
"app/signin/layout.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 1
@ -8608,11 +8608,6 @@
"count": 1
}
},
"app/signin/normal-form.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 20
}
},
"app/signin/one-more-step.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 7

View File

@ -25,7 +25,6 @@ const remoteImageURLs = ([hasSetWebPrefix ? new URL(`${env.NEXT_PUBLIC_WEB_PREFI
const nextConfig: NextConfig = {
basePath: env.NEXT_PUBLIC_BASE_PATH,
serverExternalPackages: ['esbuild'],
transpilePackages: ['@t3-oss/env-core', '@t3-oss/env-nextjs', 'echarts', 'zrender'],
turbopack: {
rules: codeInspectorPlugin({

View File

@ -1,7 +1,7 @@
{
"name": "dify-web",
"type": "module",
"version": "1.12.1",
"version": "1.13.0",
"private": true,
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a",
"imports": {
@ -176,7 +176,6 @@
"@next/eslint-plugin-next": "16.1.6",
"@next/mdx": "16.1.5",
"@rgrove/parse-xml": "4.2.0",
"@serwist/turbopack": "9.5.4",
"@storybook/addon-docs": "10.2.0",
"@storybook/addon-links": "10.2.0",
"@storybook/addon-onboarding": "10.2.0",
@ -215,7 +214,6 @@
"autoprefixer": "10.4.21",
"code-inspector-plugin": "1.3.6",
"cross-env": "10.1.0",
"esbuild": "0.27.2",
"eslint": "9.39.2",
"eslint-plugin-better-tailwindcss": "https://pkg.pr.new/hyoban/eslint-plugin-better-tailwindcss@c0161c7",
"eslint-plugin-hyoban": "0.11.1",
@ -234,7 +232,6 @@
"postcss-js": "5.0.3",
"react-scan": "0.4.3",
"sass": "1.93.2",
"serwist": "9.5.4",
"storybook": "10.2.0",
"tailwindcss": "3.4.19",
"tsx": "4.21.0",

350
web/pnpm-lock.yaml generated
View File

@ -402,9 +402,6 @@ importers:
'@rgrove/parse-xml':
specifier: 4.2.0
version: 4.2.0
'@serwist/turbopack':
specifier: 9.5.4
version: 9.5.4(@swc/helpers@0.5.18)(esbuild-wasm@0.27.2)(esbuild@0.27.2)(next@16.1.5(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(react@19.2.4)(typescript@5.9.3)
'@storybook/addon-docs':
specifier: 10.2.0
version: 10.2.0(@types/react@19.2.9)(esbuild@0.27.2)(rollup@4.56.0)(storybook@10.2.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3))
@ -519,9 +516,6 @@ importers:
cross-env:
specifier: 10.1.0
version: 10.1.0
esbuild:
specifier: 0.27.2
version: 0.27.2
eslint:
specifier: 9.39.2
version: 9.39.2(jiti@1.21.7)
@ -576,9 +570,6 @@ importers:
sass:
specifier: 1.93.2
version: 1.93.2
serwist:
specifier: 9.5.4
version: 9.5.4(browserslist@4.28.1)(typescript@5.9.3)
storybook:
specifier: 10.2.0
version: 10.2.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@ -942,10 +933,6 @@ packages:
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
'@egoist/tailwindcss-icons@1.9.2':
resolution: {integrity: sha512-I6XsSykmhu2cASg5Hp/ICLsJ/K/1aXPaSKjgbWaNp2xYnb4We/arWMmkhhV+9CglOFCUbqx0A3mM2kWV32ZIhw==}
peerDependencies:
@ -2118,10 +2105,6 @@ packages:
react: '>=18'
react-dom: '>=18'
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@pkgr/core@0.2.9':
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@ -2588,48 +2571,6 @@ packages:
peerDependencies:
react: ^16.14.0 || 17.x || 18.x || 19.x
'@serwist/build@9.5.4':
resolution: {integrity: sha512-FTiNsNb3luKsLIxjKCvkPiqFZSbx7yVNOFGSUhp4lyfzgnelT1M3/lMC88kLiak90emkuFjSkQgwa6OnyhMZlQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
typescript: '>=5.0.0'
peerDependenciesMeta:
typescript:
optional: true
'@serwist/turbopack@9.5.4':
resolution: {integrity: sha512-HerOIc2z3LWbFVq/gXK44I99KdF+x0uBI7cPHb+Q3q0WpF50d/i5fV5pZZXCf3LCqtc9oH0VlY6FWDcjWjHI8g==}
engines: {node: '>=18.0.0'}
peerDependencies:
esbuild: 0.27.2
esbuild-wasm: '>=0.25.0 <1.0.0'
next: '>=14.0.0'
react: '>=18.0.0'
typescript: '>=5.0.0'
peerDependenciesMeta:
esbuild:
optional: true
esbuild-wasm:
optional: true
typescript:
optional: true
'@serwist/utils@9.5.4':
resolution: {integrity: sha512-uyriGQF1qjNEHXXfsd8XJ5kfK3/MezEaUw//XdHjZeJ0LvLamrgnLJGQQoyJqUfEPCiJ4jJwc4uYMB9LjLiHxA==}
peerDependencies:
browserslist: '>=4'
peerDependenciesMeta:
browserslist:
optional: true
'@serwist/window@9.5.4':
resolution: {integrity: sha512-52t2G+TgiWDdRwGG0ArU28uy6/oQYICQfNLHs4ywybyS6mHy3BxHFl+JjB5vhg8znIG1LMpGvOmS5b7AuPVYDw==}
peerDependencies:
typescript: '>=5.0.0'
peerDependenciesMeta:
typescript:
optional: true
'@sindresorhus/base62@1.0.0':
resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==}
engines: {node: '>=18'}
@ -2775,87 +2716,12 @@ packages:
'@svgdotjs/svg.js@3.2.5':
resolution: {integrity: sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==}
'@swc/core-darwin-arm64@1.15.11':
resolution: {integrity: sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.15.11':
resolution: {integrity: sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.15.11':
resolution: {integrity: sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.15.11':
resolution: {integrity: sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-arm64-musl@1.15.11':
resolution: {integrity: sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-x64-gnu@1.15.11':
resolution: {integrity: sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-linux-x64-musl@1.15.11':
resolution: {integrity: sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-win32-arm64-msvc@1.15.11':
resolution: {integrity: sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.15.11':
resolution: {integrity: sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.15.11':
resolution: {integrity: sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.15.11':
resolution: {integrity: sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
peerDependenciesMeta:
'@swc/helpers':
optional: true
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@swc/helpers@0.5.18':
resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==}
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
'@t3-oss/env-core@0.13.10':
resolution: {integrity: sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g==}
peerDependencies:
@ -4071,10 +3937,6 @@ packages:
resolution: {integrity: sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==}
engines: {node: '>= 12.0.0'}
common-tags@1.8.2:
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
engines: {node: '>=4.0.0'}
compare-versions@6.1.1:
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
@ -4515,11 +4377,6 @@ packages:
esast-util-from-js@2.0.1:
resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
esbuild-wasm@0.27.2:
resolution: {integrity: sha512-eUTnl8eh+v8UZIZh4MrMOKDAc8Lm7+NqP3pyuTORGFY1s/o9WoiJgKnwXy+te2J3hX7iRbFSHEyig7GsPeeJyw==}
engines: {node: '>=18'}
hasBin: true
esbuild@0.27.2:
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
engines: {node: '>=18'}
@ -5059,11 +4916,6 @@ packages:
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
glob@10.5.0:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@11.1.0:
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
engines: {node: 20 || >=22}
@ -5405,9 +5257,6 @@ packages:
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
engines: {node: '>=8'}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jackspeak@4.1.1:
resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
engines: {node: 20 || >=22}
@ -5626,9 +5475,6 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash.sortby@4.7.0:
resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
lodash@4.17.23:
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
@ -5649,9 +5495,6 @@ packages:
lowlight@1.20.0:
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lru-cache@11.2.5:
resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==}
engines: {node: 20 || >=22}
@ -6188,10 +6031,6 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
path-scurry@2.0.1:
resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
engines: {node: 20 || >=22}
@ -6359,10 +6198,6 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
pretty-bytes@6.1.1:
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
engines: {node: ^14.13.1 || >=16.0.0}
pretty-format@27.5.1:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@ -6809,14 +6644,6 @@ packages:
server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
serwist@9.5.4:
resolution: {integrity: sha512-uTHBzpIeA6rE3oyRt392MbtNQDs2JVZelKD1KkT18UkhX6HRwCeassoI1Nd1h52DqYqa7ZfBeldJ4awy+PYrnQ==}
peerDependencies:
typescript: '>=5.0.0'
peerDependenciesMeta:
typescript:
optional: true
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@ -6892,11 +6719,6 @@ packages:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
deprecated: The work that was done in this beta branch won't be included in future versions
space-separated-tokens@1.1.5:
resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
@ -7158,9 +6980,6 @@ packages:
resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
engines: {node: '>=16'}
tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
tr46@6.0.0:
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
engines: {node: '>=20'}
@ -7554,9 +7373,6 @@ packages:
web-vitals@5.1.0:
resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==}
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
webidl-conversions@8.0.1:
resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
engines: {node: '>=20'}
@ -7595,9 +7411,6 @@ packages:
resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
engines: {node: '>=20'}
whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@ -8205,8 +8018,6 @@ snapshots:
'@csstools/css-tokenizer@3.0.4': {}
'@discoveryjs/json-ext@0.5.7': {}
'@egoist/tailwindcss-icons@1.9.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@iconify/utils': 3.1.0
@ -9429,9 +9240,6 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@pkgjs/parseargs@0.11.0':
optional: true
'@pkgr/core@0.2.9': {}
'@polka/url@1.0.0-next.29':
@ -9863,52 +9671,6 @@ snapshots:
hoist-non-react-statics: 3.3.2
react: 19.2.4
'@serwist/build@9.5.4(browserslist@4.28.1)(typescript@5.9.3)':
dependencies:
'@serwist/utils': 9.5.4(browserslist@4.28.1)
common-tags: 1.8.2
glob: 10.5.0
pretty-bytes: 6.1.1
source-map: 0.8.0-beta.0
zod: 4.3.6
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- browserslist
'@serwist/turbopack@9.5.4(@swc/helpers@0.5.18)(esbuild-wasm@0.27.2)(esbuild@0.27.2)(next@16.1.5(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(react@19.2.4)(typescript@5.9.3)':
dependencies:
'@serwist/build': 9.5.4(browserslist@4.28.1)(typescript@5.9.3)
'@serwist/utils': 9.5.4(browserslist@4.28.1)
'@serwist/window': 9.5.4(browserslist@4.28.1)(typescript@5.9.3)
'@swc/core': 1.15.11(@swc/helpers@0.5.18)
browserslist: 4.28.1
kolorist: 1.8.0
next: 16.1.5(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2)
react: 19.2.4
semver: 7.7.3
serwist: 9.5.4(browserslist@4.28.1)(typescript@5.9.3)
zod: 4.3.6
optionalDependencies:
esbuild: 0.27.2
esbuild-wasm: 0.27.2
typescript: 5.9.3
transitivePeerDependencies:
- '@swc/helpers'
'@serwist/utils@9.5.4(browserslist@4.28.1)':
optionalDependencies:
browserslist: 4.28.1
'@serwist/window@9.5.4(browserslist@4.28.1)(typescript@5.9.3)':
dependencies:
'@types/trusted-types': 2.0.7
serwist: 9.5.4(browserslist@4.28.1)(typescript@5.9.3)
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- browserslist
'@sindresorhus/base62@1.0.0': {}
'@solid-primitives/event-listener@2.4.3(solid-js@1.9.11)':
@ -10089,55 +9851,6 @@ snapshots:
'@svgdotjs/svg.js@3.2.5': {}
'@swc/core-darwin-arm64@1.15.11':
optional: true
'@swc/core-darwin-x64@1.15.11':
optional: true
'@swc/core-linux-arm-gnueabihf@1.15.11':
optional: true
'@swc/core-linux-arm64-gnu@1.15.11':
optional: true
'@swc/core-linux-arm64-musl@1.15.11':
optional: true
'@swc/core-linux-x64-gnu@1.15.11':
optional: true
'@swc/core-linux-x64-musl@1.15.11':
optional: true
'@swc/core-win32-arm64-msvc@1.15.11':
optional: true
'@swc/core-win32-ia32-msvc@1.15.11':
optional: true
'@swc/core-win32-x64-msvc@1.15.11':
optional: true
'@swc/core@1.15.11(@swc/helpers@0.5.18)':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.25
optionalDependencies:
'@swc/core-darwin-arm64': 1.15.11
'@swc/core-darwin-x64': 1.15.11
'@swc/core-linux-arm-gnueabihf': 1.15.11
'@swc/core-linux-arm64-gnu': 1.15.11
'@swc/core-linux-arm64-musl': 1.15.11
'@swc/core-linux-x64-gnu': 1.15.11
'@swc/core-linux-x64-musl': 1.15.11
'@swc/core-win32-arm64-msvc': 1.15.11
'@swc/core-win32-ia32-msvc': 1.15.11
'@swc/core-win32-x64-msvc': 1.15.11
'@swc/helpers': 0.5.18
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@ -10146,10 +9859,6 @@ snapshots:
dependencies:
tslib: 2.8.1
'@swc/types@0.1.25':
dependencies:
'@swc/counter': 0.1.3
'@t3-oss/env-core@0.13.10(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6)':
optionalDependencies:
typescript: 5.9.3
@ -10655,7 +10364,8 @@ snapshots:
'@types/sortablejs@1.15.8': {}
'@types/trusted-types@2.0.7': {}
'@types/trusted-types@2.0.7':
optional: true
'@types/unist@2.0.11': {}
@ -11568,8 +11278,6 @@ snapshots:
comment-parser@1.4.5: {}
common-tags@1.8.2: {}
compare-versions@6.1.1: {}
confbox@0.1.8: {}
@ -12020,9 +11728,6 @@ snapshots:
esast-util-from-estree: 2.0.0
vfile-message: 4.0.3
esbuild-wasm@0.27.2:
optional: true
esbuild@0.27.2:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.2
@ -12771,15 +12476,6 @@ snapshots:
glob-to-regexp@0.4.1:
optional: true
glob@10.5.0:
dependencies:
foreground-child: 3.3.1
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
glob@11.1.0:
dependencies:
foreground-child: 3.3.1
@ -13164,12 +12860,6 @@ snapshots:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jackspeak@4.1.1:
dependencies:
'@isaacs/cliui': 8.0.2
@ -13395,8 +13085,6 @@ snapshots:
lodash.merge@4.6.2: {}
lodash.sortby@4.7.0: {}
lodash@4.17.23: {}
log-update@6.1.0:
@ -13420,8 +13108,6 @@ snapshots:
fault: 1.0.4
highlight.js: 10.7.3
lru-cache@10.4.3: {}
lru-cache@11.2.5: {}
lru-cache@5.1.1:
@ -14261,11 +13947,6 @@ snapshots:
path-parse@1.0.7: {}
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
minipass: 7.1.2
path-scurry@2.0.1:
dependencies:
lru-cache: 11.2.5
@ -14426,8 +14107,6 @@ snapshots:
prelude-ls@1.2.1: {}
pretty-bytes@6.1.1: {}
pretty-format@27.5.1:
dependencies:
ansi-regex: 5.0.1
@ -15003,15 +14682,6 @@ snapshots:
server-only@0.0.1: {}
serwist@9.5.4(browserslist@4.28.1)(typescript@5.9.3):
dependencies:
'@serwist/utils': 9.5.4(browserslist@4.28.1)
idb: 8.0.3
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- browserslist
sharp@0.33.5:
dependencies:
color: 4.2.3
@ -15137,10 +14807,6 @@ snapshots:
source-map@0.7.6: {}
source-map@0.8.0-beta.0:
dependencies:
whatwg-url: 7.1.0
space-separated-tokens@1.1.5: {}
space-separated-tokens@2.0.2: {}
@ -15421,10 +15087,6 @@ snapshots:
dependencies:
tldts: 7.0.17
tr46@1.0.1:
dependencies:
punycode: 2.3.1
tr46@6.0.0:
dependencies:
punycode: 2.3.1
@ -15797,8 +15459,6 @@ snapshots:
web-vitals@5.1.0: {}
webidl-conversions@4.0.2: {}
webidl-conversions@8.0.1: {}
webpack-sources@3.3.3:
@ -15852,12 +15512,6 @@ snapshots:
tr46: 6.0.0
webidl-conversions: 8.0.1
whatwg-url@7.1.0:
dependencies:
lodash.sortby: 4.7.0
tr46: 1.0.1
webidl-conversions: 4.0.2
which@2.0.2:
dependencies:
isexe: 2.0.0

View File

@ -1,129 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dify - Offline</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: white;
text-align: center;
padding: 20px;
}
.container {
max-width: 600px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2);
}
.icon {
width: 100px;
height: 100px;
margin: 0 auto 30px;
background: rgba(255, 255, 255, 0.2);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
}
h1 {
font-size: 32px;
font-weight: 600;
margin-bottom: 15px;
}
p {
font-size: 18px;
line-height: 1.6;
opacity: 0.9;
margin-bottom: 30px;
}
button {
background: white;
color: #764ba2;
border: none;
padding: 15px 30px;
font-size: 16px;
font-weight: 600;
border-radius: 50px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(0);
}
@media (max-width: 640px) {
.container {
padding: 30px;
}
h1 {
font-size: 24px;
}
p {
font-size: 16px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="icon">
</div>
<h1>You're Offline</h1>
<p>
It looks like you've lost your internet connection.
Some features may not be available until you're back online.
</p>
<button onclick="window.location.reload()">
Try Again
</button>
</div>
<script>
// Check for connection status changes
window.addEventListener('online', function() {
window.location.reload();
});
// Periodically check if online
setInterval(function() {
fetch(window.location.origin, { method: 'HEAD' })
.then(function() {
window.location.reload();
})
.catch(function() {
// Still offline
});
}, 5000);
</script>
</body>
</html>

View File

@ -22,7 +22,7 @@ import type {
UserProfileResponse,
} from '@/models/common'
import type { RETRIEVE_METHOD } from '@/types/app'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useMutation, useQuery } from '@tanstack/react-query'
import { IS_DEV } from '@/config'
import { get, post } from './base'
import { useInvalid } from './use-base'
@ -318,15 +318,6 @@ export const useInvalidDataSourceIntegrates = () => {
return useInvalid(commonQueryKeys.dataSourceIntegrates)
}
export const useInvalidateCurrentWorkspace = () => {
const queryClient = useQueryClient()
return () => {
queryClient.invalidateQueries({
queryKey: commonQueryKeys.currentWorkspace,
})
}
}
export const usePluginProviders = () => {
return useQuery<PluginProvider[] | null>({
queryKey: commonQueryKeys.pluginProviders,