mirror of
https://github.com/langgenius/dify.git
synced 2026-06-23 04:11:09 +08:00
refactor(web): migrate shared localStorage to createLocalStorageState (#37408)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
parent
734211d735
commit
9eca75c7fc
@ -228,9 +228,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/(shareLayout)/webapp-reset-password/page.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
@ -252,9 +249,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
@ -321,11 +315,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/account/(commonLayout)/delete-account/index.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/account/oauth/authorize/layout.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -378,11 +367,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app-sidebar/index.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@ -650,9 +634,6 @@
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-globals": {
|
||||
"count": 6
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 4
|
||||
},
|
||||
@ -832,14 +813,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/create-app-dialog/app-list/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -3940,11 +3913,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/index.tsx": {
|
||||
"tailwindcss/no-duplicate-classes": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/nav/index.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -4679,11 +4647,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/signin/countdown.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/snippet-list/components/snippet-card.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -6213,11 +6176,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx": {
|
||||
"no-restricted-properties": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
@ -7339,9 +7297,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/reset-password/page.tsx": {
|
||||
"no-restricted-globals": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
|
||||
@ -33,9 +33,9 @@
|
||||
- Use local component state for state owned by one component.
|
||||
- Use feature-level Jotai atoms for simple client state shared across components in the same feature, especially when components need a shared source of truth, derived values, or shared actions.
|
||||
- Use existing feature stores for complex or high-frequency interaction state such as workflow canvas, drag, resize, and panel runtime state.
|
||||
- Use `foxact/use-local-storage` only for low-frequency, client-only persistence such as user preferences, dismissed notices, and UI defaults. Do not use localStorage as the live source of truth for app state.
|
||||
- For shared low-frequency, client-only persistence such as user preferences, dismissed notices, and UI defaults, use feature-owned storage modules built with `createLocalStorageState`.
|
||||
- For high-frequency interactions, update the feature state during interaction and persist storage only on commit or settled updates.
|
||||
- Do not access `localStorage`, `window.localStorage`, or `globalThis.localStorage` directly in app code; use the storage hook boundary and preserve existing raw/custom storage formats.
|
||||
- Keep storage keys and raw/custom formats in the owner module; callers should import the named storage hooks instead of scattering direct storage access.
|
||||
- Do not add ad hoc global event listeners for shared state. Prefer atoms, existing stores, or a shared subscription hook so listeners are centralized and deduplicated.
|
||||
|
||||
## Agent V2 Frontend
|
||||
|
||||
@ -4,9 +4,9 @@ import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import AppDetailNav from '@/app/components/app-sidebar'
|
||||
|
||||
const mockSetAppSidebarExpand = vi.fn()
|
||||
const mockSetDetailSidebarMode = vi.fn()
|
||||
|
||||
let mockAppSidebarExpand = 'expand'
|
||||
let mockDetailSidebarMode = 'expand'
|
||||
let mockPathname = '/app/app-1/logs'
|
||||
let mockSelectedSegment = 'logs'
|
||||
let mockIsHovering = true
|
||||
@ -18,24 +18,8 @@ vi.mock('react-i18next', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/app/store', () => ({
|
||||
useStore: (selector: (state: Record<string, unknown>) => unknown) => selector({
|
||||
appDetail: {
|
||||
id: 'app-1',
|
||||
name: 'Demo App',
|
||||
mode: 'chat',
|
||||
icon: '🤖',
|
||||
icon_type: 'emoji',
|
||||
icon_background: '#FFEAD5',
|
||||
icon_url: null,
|
||||
},
|
||||
appSidebarExpand: mockAppSidebarExpand,
|
||||
setAppSidebarExpand: mockSetAppSidebarExpand,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('zustand/react/shallow', () => ({
|
||||
useShallow: (selector: unknown) => selector,
|
||||
vi.mock('@/app/components/main-nav/storage', () => ({
|
||||
useDetailSidebarMode: () => [mockDetailSidebarMode, mockSetDetailSidebarMode],
|
||||
}))
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
@ -129,7 +113,7 @@ describe('App Sidebar Shell Flow', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
localStorage.clear()
|
||||
mockAppSidebarExpand = 'expand'
|
||||
mockDetailSidebarMode = 'expand'
|
||||
mockPathname = '/app/app-1/logs'
|
||||
mockSelectedSegment = 'logs'
|
||||
mockIsHovering = true
|
||||
@ -145,13 +129,13 @@ describe('App Sidebar Shell Flow', () => {
|
||||
expect(logsLink.className).toContain('bg-components-menu-item-bg-active')
|
||||
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(mockSetAppSidebarExpand).toHaveBeenCalledWith('collapse')
|
||||
expect(mockSetDetailSidebarMode).toHaveBeenCalledWith('collapse')
|
||||
|
||||
const preventDefault = vi.fn()
|
||||
hotkeyHandler?.({ preventDefault })
|
||||
|
||||
expect(preventDefault).toHaveBeenCalled()
|
||||
expect(mockSetAppSidebarExpand).toHaveBeenCalledWith('collapse')
|
||||
expect(mockSetDetailSidebarMode).toHaveBeenCalledWith('collapse')
|
||||
})
|
||||
|
||||
it('keeps the normal sidebar on workflow routes', () => {
|
||||
|
||||
@ -85,8 +85,8 @@ vi.mock('@/hooks/use-async-window-open', () => ({
|
||||
useAsyncWindowOpen: () => vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('foxact/use-local-storage', () => ({
|
||||
useSetLocalStorage: () => mockSetEducationVerifying,
|
||||
vi.mock('@/app/education-apply/storage', () => ({
|
||||
useSetEducationVerifying: () => mockSetEducationVerifying,
|
||||
}))
|
||||
|
||||
// ─── External component mocks ───────────────────────────────────────────────
|
||||
|
||||
@ -7,18 +7,17 @@ import type { App } from '@/types/app'
|
||||
import type { I18nKeysByPrefix } from '@/types/i18n'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppCard from '@/app/components/app/overview/app-card'
|
||||
import TriggerCard from '@/app/components/app/overview/trigger-card'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import MCPServiceCard from '@/app/components/tools/mcp/mcp-service-card'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
|
||||
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||
import { isTriggerNode } from '@/app/components/workflow/types'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import {
|
||||
updateAppSiteAccessToken,
|
||||
@ -85,7 +84,7 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
||||
? buildTriggerModeMessage(t('mcp.server.title', { ns: 'tools' }))
|
||||
: null
|
||||
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
|
||||
const updateAppDetail = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@ -6,7 +6,7 @@ import { noop } from 'es-toolkit/function'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown'
|
||||
import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage'
|
||||
import { emailRegex } from '@/config'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
@ -23,6 +23,7 @@ export default function CheckCode() {
|
||||
const [email, setEmail] = useState('')
|
||||
const [loading, setIsLoading] = useState(false)
|
||||
const locale = useLocale()
|
||||
const setCountdownLeftTime = useSetCountdownLeftTime()
|
||||
|
||||
const handleGetEMailVerificationCode = async () => {
|
||||
try {
|
||||
@ -38,7 +39,7 @@ export default function CheckCode() {
|
||||
setIsLoading(true)
|
||||
const res = await sendResetPasswordCode(email, locale)
|
||||
if (res.result === 'success') {
|
||||
localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`)
|
||||
setCountdownLeftTime(`${COUNT_DOWN_TIME_MS}`)
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.set('token', encodeURIComponent(res.data))
|
||||
params.set('email', encodeURIComponent(email))
|
||||
|
||||
@ -4,7 +4,7 @@ import { noop } from 'es-toolkit/function'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown'
|
||||
import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage'
|
||||
import { emailRegex } from '@/config'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { useRouter, useSearchParams } from '@/next/navigation'
|
||||
@ -18,6 +18,7 @@ export default function MailAndCodeAuth() {
|
||||
const [email, setEmail] = useState(emailFromLink)
|
||||
const [loading, setIsLoading] = useState(false)
|
||||
const locale = useLocale()
|
||||
const setCountdownLeftTime = useSetCountdownLeftTime()
|
||||
|
||||
const handleGetEMailVerificationCode = async () => {
|
||||
try {
|
||||
@ -33,7 +34,7 @@ export default function MailAndCodeAuth() {
|
||||
setIsLoading(true)
|
||||
const ret = await sendWebAppEMailLoginCode(email, locale)
|
||||
if (ret.result === 'success') {
|
||||
localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`)
|
||||
setCountdownLeftTime(`${COUNT_DOWN_TIME_MS}`)
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.set('email', encodeURIComponent(email))
|
||||
params.set('token', encodeURIComponent(ret.data))
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown'
|
||||
import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage'
|
||||
import CheckEmail from './components/check-email'
|
||||
import FeedBack from './components/feed-back'
|
||||
import VerifyEmail from './components/verify-email'
|
||||
@ -14,6 +14,7 @@ type DeleteAccountProps = {
|
||||
|
||||
export default function DeleteAccount(props: DeleteAccountProps) {
|
||||
const { t } = useTranslation()
|
||||
const setCountdownLeftTime = useSetCountdownLeftTime()
|
||||
|
||||
const [showVerifyEmail, setShowVerifyEmail] = useState(false)
|
||||
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false)
|
||||
@ -21,10 +22,10 @@ export default function DeleteAccount(props: DeleteAccountProps) {
|
||||
const handleEmailCheckSuccess = useCallback(async () => {
|
||||
try {
|
||||
setShowVerifyEmail(true)
|
||||
localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`)
|
||||
setCountdownLeftTime(`${COUNT_DOWN_TIME_MS}`)
|
||||
}
|
||||
catch (error) { console.error(error) }
|
||||
}, [])
|
||||
}, [setCountdownLeftTime])
|
||||
|
||||
if (showFeedbackDialog)
|
||||
return <FeedBack onCancel={props.onCancel} onConfirm={props.onConfirm} />
|
||||
|
||||
@ -12,8 +12,8 @@ vi.mock('@/next/navigation', () => ({
|
||||
useSearchParams: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('foxact/use-local-storage', () => ({
|
||||
useSetLocalStorage: () => setEducationVerifyingMock,
|
||||
vi.mock('@/app/education-apply/storage', () => ({
|
||||
useSetEducationVerifying: () => setEducationVerifyingMock,
|
||||
}))
|
||||
|
||||
const mockUseSearchParams = vi.mocked(useSearchParams)
|
||||
|
||||
@ -3,19 +3,11 @@ import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import AppDetailNav from '..'
|
||||
|
||||
let mockAppSidebarExpand = 'expand'
|
||||
const mockSetAppSidebarExpand = vi.fn()
|
||||
let mockDetailSidebarMode = 'expand'
|
||||
const mockSetDetailSidebarMode = vi.fn()
|
||||
|
||||
vi.mock('@/app/components/app/store', () => ({
|
||||
useStore: (selector: (state: Record<string, unknown>) => unknown) => selector({
|
||||
appDetail: { id: 'app-1', name: 'Test', mode: 'chat', icon: '🤖', icon_type: 'emoji', icon_background: '#fff' },
|
||||
appSidebarExpand: mockAppSidebarExpand,
|
||||
setAppSidebarExpand: mockSetAppSidebarExpand,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('zustand/react/shallow', () => ({
|
||||
useShallow: (fn: unknown) => fn,
|
||||
vi.mock('@/app/components/main-nav/storage', () => ({
|
||||
useDetailSidebarMode: () => [mockDetailSidebarMode, mockSetDetailSidebarMode],
|
||||
}))
|
||||
|
||||
let mockIsHovering = true
|
||||
@ -79,7 +71,7 @@ const navigation = [
|
||||
describe('AppDetailNav', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockAppSidebarExpand = 'expand'
|
||||
mockDetailSidebarMode = 'expand'
|
||||
mockIsHovering = true
|
||||
mockKeyPressCallback = null
|
||||
})
|
||||
@ -114,7 +106,7 @@ describe('AppDetailNav', () => {
|
||||
})
|
||||
|
||||
it('should apply collapsed width class', () => {
|
||||
mockAppSidebarExpand = 'collapse'
|
||||
mockDetailSidebarMode = 'collapse'
|
||||
const { container } = render(<AppDetailNav navigation={navigation} />)
|
||||
const sidebar = container.firstElementChild as HTMLElement
|
||||
expect(sidebar).toHaveClass('w-14')
|
||||
@ -164,37 +156,30 @@ describe('AppDetailNav', () => {
|
||||
})
|
||||
|
||||
it('should pass collapse mode to nav links when collapsed', () => {
|
||||
mockAppSidebarExpand = 'collapse'
|
||||
mockDetailSidebarMode = 'collapse'
|
||||
render(<AppDetailNav navigation={navigation} />)
|
||||
expect(screen.getByTestId('nav-link-Overview')).toHaveAttribute('data-mode', 'collapse')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Toggle behavior', () => {
|
||||
it('should call setAppSidebarExpand on toggle', async () => {
|
||||
it('should collapse detail sidebar on toggle', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<AppDetailNav navigation={navigation} />)
|
||||
|
||||
await user.click(screen.getByTestId('toggle-button'))
|
||||
|
||||
expect(mockSetAppSidebarExpand).toHaveBeenCalledWith('collapse')
|
||||
expect(mockSetDetailSidebarMode).toHaveBeenCalledWith('collapse')
|
||||
})
|
||||
|
||||
it('should toggle from collapse to expand', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockAppSidebarExpand = 'collapse'
|
||||
mockDetailSidebarMode = 'collapse'
|
||||
render(<AppDetailNav navigation={navigation} />)
|
||||
|
||||
await user.click(screen.getByTestId('toggle-button'))
|
||||
|
||||
expect(mockSetAppSidebarExpand).toHaveBeenCalledWith('expand')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sidebar persistence', () => {
|
||||
it('should persist expand state to localStorage', () => {
|
||||
render(<AppDetailNav navigation={navigation} />)
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('app-detail-collapse-or-expand', 'expand')
|
||||
expect(mockSetDetailSidebarMode).toHaveBeenCalledWith('expand')
|
||||
})
|
||||
})
|
||||
|
||||
@ -218,7 +203,7 @@ describe('AppDetailNav', () => {
|
||||
act(() => {
|
||||
cb!({ preventDefault: vi.fn() })
|
||||
})
|
||||
expect(mockSetAppSidebarExpand).toHaveBeenCalledWith('collapse')
|
||||
expect(mockSetDetailSidebarMode).toHaveBeenCalledWith('collapse')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -106,10 +106,6 @@ vi.mock('@/app/components/workflow/collaboration/core/collaboration-manager', ()
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
NEED_REFRESH_APP_LIST_KEY: 'test-refresh-key',
|
||||
}))
|
||||
|
||||
describe('useAppInfoActions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
@ -4,11 +4,10 @@ import type { CreateAppModalProps } from '@/app/components/explore/create-app-mo
|
||||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { copyApp, deleteApp, exportAppConfig, fetchAppDetail, updateAppInfo } from '@/service/apps'
|
||||
@ -110,7 +109,7 @@ export function useAppInfoActions({ onDetailExpand, resetKey }: UseAppInfoAction
|
||||
setActiveModal(null)
|
||||
}, [setActiveModal])
|
||||
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
|
||||
const emitAppMetaUpdate = useCallback(() => {
|
||||
if (!appDetail?.id)
|
||||
|
||||
@ -4,9 +4,8 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useHotkey } from '@tanstack/react-hotkeys'
|
||||
import { useHover } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useCallback } from 'react'
|
||||
import { useDetailSidebarMode } from '@/app/components/main-nav/storage'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import Divider from '../base/divider'
|
||||
import AppInfo, { AppInfoView } from './app-info'
|
||||
@ -37,28 +36,18 @@ const AppDetailNav = ({
|
||||
iconType = 'app',
|
||||
appInfoActions,
|
||||
}: IAppDetailNavProps) => {
|
||||
const { appSidebarExpand, setAppSidebarExpand } = useAppStore(useShallow(state => ({
|
||||
appSidebarExpand: state.appSidebarExpand,
|
||||
setAppSidebarExpand: state.setAppSidebarExpand,
|
||||
})))
|
||||
const [detailSidebarMode, setDetailSidebarMode] = useDetailSidebarMode()
|
||||
const sidebarRef = React.useRef<HTMLDivElement>(null)
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const expand = appSidebarExpand === 'expand'
|
||||
const expand = detailSidebarMode === 'expand'
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
setAppSidebarExpand(appSidebarExpand === 'expand' ? 'collapse' : 'expand')
|
||||
}, [appSidebarExpand, setAppSidebarExpand])
|
||||
setDetailSidebarMode(detailSidebarMode === 'expand' ? 'collapse' : 'expand')
|
||||
}, [detailSidebarMode, setDetailSidebarMode])
|
||||
|
||||
const isHoveringSidebar = useHover(sidebarRef)
|
||||
|
||||
useEffect(() => {
|
||||
if (appSidebarExpand) {
|
||||
localStorage.setItem('app-detail-collapse-or-expand', appSidebarExpand)
|
||||
setAppSidebarExpand(appSidebarExpand)
|
||||
}
|
||||
}, [appSidebarExpand, setAppSidebarExpand])
|
||||
|
||||
useHotkey('Mod+B', (e) => {
|
||||
e.preventDefault()
|
||||
handleToggle()
|
||||
@ -81,7 +70,7 @@ const AppDetailNav = ({
|
||||
)}
|
||||
>
|
||||
{renderHeader
|
||||
? renderHeader(appSidebarExpand)
|
||||
? renderHeader(detailSidebarMode)
|
||||
: iconType === 'app' && (
|
||||
appInfoActions
|
||||
? (
|
||||
@ -123,12 +112,12 @@ const AppDetailNav = ({
|
||||
)}
|
||||
>
|
||||
{renderNavigation
|
||||
? renderNavigation(appSidebarExpand)
|
||||
? renderNavigation(detailSidebarMode)
|
||||
: navigation.map((item, index) => {
|
||||
return (
|
||||
<NavLink
|
||||
key={index}
|
||||
mode={appSidebarExpand}
|
||||
mode={detailSidebarMode}
|
||||
iconMap={{ selected: item.selectedIcon, normal: item.icon }}
|
||||
name={item.name}
|
||||
href={item.href}
|
||||
@ -137,7 +126,7 @@ const AppDetailNav = ({
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
{iconType !== 'app' && extraInfo && extraInfo(appSidebarExpand)}
|
||||
{iconType !== 'app' && extraInfo && extraInfo(detailSidebarMode)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ import { useStore } from '../store'
|
||||
const resetStore = () => {
|
||||
useStore.setState({
|
||||
appDetail: undefined,
|
||||
appSidebarExpand: '',
|
||||
currentLogItem: undefined,
|
||||
currentLogModalActiveTab: 'DETAIL',
|
||||
showPromptLogModal: false,
|
||||
@ -21,7 +20,6 @@ describe('app store', () => {
|
||||
it('should expose the default state', () => {
|
||||
expect(useStore.getState()).toEqual(expect.objectContaining({
|
||||
appDetail: undefined,
|
||||
appSidebarExpand: '',
|
||||
currentLogItem: undefined,
|
||||
currentLogModalActiveTab: 'DETAIL',
|
||||
showPromptLogModal: false,
|
||||
@ -36,7 +34,6 @@ describe('app store', () => {
|
||||
const currentLogItem = { id: 'message-1' } as ReturnType<typeof useStore.getState>['currentLogItem']
|
||||
|
||||
useStore.getState().setAppDetail(appDetail)
|
||||
useStore.getState().setAppSidebarExpand('logs')
|
||||
useStore.getState().setCurrentLogItem(currentLogItem)
|
||||
useStore.getState().setCurrentLogModalActiveTab('MESSAGE')
|
||||
useStore.getState().setShowPromptLogModal(true)
|
||||
@ -45,7 +42,6 @@ describe('app store', () => {
|
||||
|
||||
expect(useStore.getState()).toEqual(expect.objectContaining({
|
||||
appDetail,
|
||||
appSidebarExpand: 'logs',
|
||||
currentLogItem,
|
||||
currentLogModalActiveTab: 'MESSAGE',
|
||||
showPromptLogModal: true,
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import type { Model } from '@/types/app'
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
const [
|
||||
useAutoGenModel,
|
||||
_useAutoGenModelValue,
|
||||
_useSetAutoGenModel,
|
||||
] = createLocalStorageState<Model>('auto-gen-model')
|
||||
|
||||
export {
|
||||
useAutoGenModel,
|
||||
}
|
||||
@ -39,6 +39,7 @@ import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/com
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import { generateBasicAppFirstTimeRule, generateRule } from '@/service/debug'
|
||||
import { useGenerateRuleTemplate } from '@/service/use-apps'
|
||||
import { useAutoGenModel } from '../auto-gen-model-storage'
|
||||
import IdeaOutput from './idea-output'
|
||||
import InstructionEditorInBasic from './instruction-editor'
|
||||
import InstructionEditorInWorkflow from './instruction-editor-in-workflow'
|
||||
@ -90,10 +91,8 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
onFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const localModel = localStorage.getItem('auto-gen-model')
|
||||
? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model
|
||||
: null
|
||||
const [model, setModel] = React.useState<Model>(localModel || {
|
||||
const [storedModel, setStoredModel] = useAutoGenModel()
|
||||
const [model, setModel] = React.useState<Model>(storedModel || {
|
||||
name: '',
|
||||
provider: '',
|
||||
mode: mode as unknown as ModelModeType,
|
||||
@ -182,11 +181,8 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultModel) {
|
||||
const localModel = localStorage.getItem('auto-gen-model')
|
||||
? JSON.parse(localStorage.getItem('auto-gen-model') || '')
|
||||
: null
|
||||
if (localModel) {
|
||||
setModel(localModel)
|
||||
if (storedModel) {
|
||||
setModel(storedModel)
|
||||
}
|
||||
else {
|
||||
setModel(prev => ({
|
||||
@ -196,7 +192,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
}))
|
||||
}
|
||||
}
|
||||
}, [defaultModel])
|
||||
}, [defaultModel, storedModel])
|
||||
|
||||
const renderLoading = (
|
||||
<div className="flex h-full w-0 grow flex-col items-center justify-center space-y-3">
|
||||
@ -213,8 +209,8 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
mode: newValue.mode as ModelModeType,
|
||||
}
|
||||
setModel(newModel)
|
||||
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||
}, [model, setModel])
|
||||
setStoredModel(newModel)
|
||||
}, [model, setModel, setStoredModel])
|
||||
|
||||
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
|
||||
const newModel = {
|
||||
@ -222,8 +218,8 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
completion_params: newParams as CompletionParams,
|
||||
}
|
||||
setModel(newModel)
|
||||
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||
}, [model, setModel])
|
||||
setStoredModel(newModel)
|
||||
}, [model, setModel, setStoredModel])
|
||||
|
||||
const onGenerate = async () => {
|
||||
if (!isValid())
|
||||
|
||||
@ -19,7 +19,6 @@ import {
|
||||
useBoolean,
|
||||
useSessionStorageState,
|
||||
} from 'ahooks'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -31,6 +30,7 @@ import ModelParameterModal from '@/app/components/header/account-setting/model-p
|
||||
import { generateRule } from '@/service/debug'
|
||||
import { useGenerateRuleTemplate } from '@/service/use-apps'
|
||||
import { languageMap } from '../../../../workflow/nodes/_base/components/editor/code-editor/index'
|
||||
import { useAutoGenModel } from '../auto-gen-model-storage'
|
||||
import IdeaOutput from '../automatic/idea-output'
|
||||
import InstructionEditor from '../automatic/instruction-editor-in-workflow'
|
||||
import ResPlaceholder from '../automatic/res-placeholder'
|
||||
@ -40,7 +40,6 @@ import { GeneratorType } from '../automatic/types'
|
||||
import useGenData from '../automatic/use-gen-data'
|
||||
|
||||
const i18nPrefix = 'generate'
|
||||
const AUTO_GEN_MODEL_STORAGE_KEY = 'auto-gen-model'
|
||||
const defaultCompletionParams = {
|
||||
temperature: 0.7,
|
||||
max_tokens: 0,
|
||||
@ -75,7 +74,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
},
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
const [storedModel, setStoredModel] = useLocalStorage<Model>(AUTO_GEN_MODEL_STORAGE_KEY)
|
||||
const [storedModel, setStoredModel] = useAutoGenModel()
|
||||
const [model, setModel] = React.useState<Model>(storedModel || {
|
||||
name: '',
|
||||
provider: '',
|
||||
|
||||
@ -6,8 +6,8 @@ import { AppACLPermission } from '@/utils/permission'
|
||||
import { useConfiguration } from '../use-configuration'
|
||||
|
||||
const mockSetShowAccountSettingModal = vi.fn()
|
||||
const mockSetAppSidebarExpand = vi.fn()
|
||||
const mockSetShowAppConfigureFeaturesModal = vi.fn()
|
||||
const mockSetDetailSidebarMode = vi.fn()
|
||||
const mockHandleMultipleModelConfigsChange = vi.fn()
|
||||
const mockFetchCollectionList = vi.fn()
|
||||
const mockFetchAppDetailDirect = vi.fn()
|
||||
@ -85,12 +85,15 @@ vi.mock('@/app/components/app/store', () => ({
|
||||
mode: AppModeEnum.CHAT,
|
||||
permission_keys: mockAppPermissionKeys,
|
||||
},
|
||||
setAppSidebarExpand: mockSetAppSidebarExpand,
|
||||
showAppConfigureFeaturesModal: false,
|
||||
setShowAppConfigureFeaturesModal: mockSetShowAppConfigureFeaturesModal,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/main-nav/storage', () => ({
|
||||
useSetDetailSidebarMode: () => mockSetDetailSidebarMode,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useFileUploadConfig: () => ({
|
||||
data: undefined,
|
||||
@ -466,7 +469,7 @@ describe('useConfiguration', () => {
|
||||
expect(mockSetShowAppConfigureFeaturesModal).toHaveBeenCalledWith(true)
|
||||
expect(mockFormattingChangedDispatcher).toHaveBeenCalled()
|
||||
expect(mockHandleMultipleModelConfigsChange).toHaveBeenCalled()
|
||||
expect(mockSetAppSidebarExpand).toHaveBeenCalledWith('collapse')
|
||||
expect(mockSetDetailSidebarMode).toHaveBeenCalledWith('collapse')
|
||||
expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({ payload: 'provider' })
|
||||
expect(mockSetConversationHistoriesRole).toHaveBeenCalledWith({
|
||||
assistant_prefix: 'bot',
|
||||
|
||||
@ -41,6 +41,7 @@ import {
|
||||
useTextGenerationCurrentProviderAndModelAndModelList,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useIntegrationsSetting } from '@/app/components/header/account-setting/use-integrations-setting'
|
||||
import { useSetDetailSidebarMode } from '@/app/components/main-nav/storage'
|
||||
import { ANNOTATION_DEFAULT, DATASET_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
@ -112,12 +113,12 @@ export const useConfiguration = (): ConfigurationViewModel => {
|
||||
const { isLoadingCurrentWorkspace, currentWorkspace, userProfile, workspacePermissionKeys } = useAppContext()
|
||||
const openIntegrationsSetting = useIntegrationsSetting()
|
||||
|
||||
const { appDetail, showAppConfigureFeaturesModal, setAppSidebarExpand, setShowAppConfigureFeaturesModal } = useAppStore(useShallow(state => ({
|
||||
const { appDetail, showAppConfigureFeaturesModal, setShowAppConfigureFeaturesModal } = useAppStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
setAppSidebarExpand: state.setAppSidebarExpand,
|
||||
showAppConfigureFeaturesModal: state.showAppConfigureFeaturesModal,
|
||||
setShowAppConfigureFeaturesModal: state.setShowAppConfigureFeaturesModal,
|
||||
})))
|
||||
const setDetailSidebarMode = useSetDetailSidebarMode()
|
||||
|
||||
const { data: fileUploadConfigResponse } = useFileUploadConfig()
|
||||
const latestPublishedAt = useMemo(() => appDetail?.model_config?.updated_at, [appDetail])
|
||||
@ -568,8 +569,8 @@ export const useConfiguration = (): ConfigurationViewModel => {
|
||||
{ id: `${Date.now()}-no-repeat`, model: '', provider: '', parameters: {} },
|
||||
],
|
||||
)
|
||||
setAppSidebarExpand('collapse')
|
||||
}, [completionParamsState, handleMultipleModelConfigsChange, modelConfig.model_id, modelConfig.provider, setAppSidebarExpand])
|
||||
setDetailSidebarMode('collapse')
|
||||
}, [completionParamsState, handleMultipleModelConfigsChange, modelConfig.model_id, modelConfig.provider, setDetailSidebarMode])
|
||||
|
||||
const onAgentSettingChange = useCallback((config: ModelConfig['agentConfig']) => {
|
||||
setModelConfig(produce(modelConfig, (draft: ModelConfig) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import Apps from '../index'
|
||||
|
||||
@ -52,14 +52,15 @@ vi.mock('@/app/components/app/type-selector', () => ({
|
||||
}))
|
||||
vi.mock('../../app-card', () => ({
|
||||
default: ({ app, canCreate, onCreate }: { app: { app: { name: string } }, canCreate: boolean, onCreate: () => void }) => (
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
data-testid="app-card"
|
||||
data-name={app.app.name}
|
||||
data-can-create={canCreate ? 'true' : 'false'}
|
||||
onClick={onCreate}
|
||||
>
|
||||
{app.app.name}
|
||||
</div>
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
vi.mock('@/app/components/explore/create-app-modal', () => ({
|
||||
|
||||
@ -6,17 +6,16 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiRobot2Line } from '@remixicon/react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import * as React from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppTypeSelector from '@/app/components/app/type-selector'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { DSLImportMode } from '@/models/app'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
@ -52,7 +51,7 @@ const Apps = ({
|
||||
const invalidateAppList = useInvalidateAppList()
|
||||
const allCategoriesEn = AppCategories.RECOMMENDED
|
||||
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
|
||||
const [keywords, setKeywords] = useState('')
|
||||
const [searchKeywords, setSearchKeywords] = useState('')
|
||||
|
||||
@ -2,7 +2,7 @@ import type { App } from '@/types/app'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
|
||||
@ -10,15 +10,14 @@ import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiArrowRightLine, RiArrowRightSLine, RiExchange2Fill } from '@remixicon/react'
|
||||
import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { BubbleTextMod, ChatBot, ListSparkle, Logic } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import Input from '@/app/components/base/input'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
@ -63,7 +62,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
||||
|
||||
const isCreatingRef = useRef(false)
|
||||
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
|
||||
const onCreate = useCallback(async () => {
|
||||
if (!canCreateApp)
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage'
|
||||
import { DSLImportMode, DSLImportStatus } from '@/models/app'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import CreateFromDSLModal, { CreateFromDSLModalTab } from '../index'
|
||||
|
||||
@ -8,13 +8,12 @@ import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import Input from '@/app/components/base/input'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
DSLImportMode,
|
||||
@ -55,7 +54,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
||||
const [versions, setVersions] = useState<{ importedVersion: string, systemVersion: string }>()
|
||||
const [importId, setImportId] = useState<string>()
|
||||
const { handleCheckPluginDependencies } = usePluginDependencies()
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
|
||||
const readFile = useCallback((file: File) => {
|
||||
const reader = new FileReader()
|
||||
|
||||
@ -4,7 +4,6 @@ import { create } from 'zustand'
|
||||
|
||||
type State = {
|
||||
appDetail?: App & Partial<AppSSO>
|
||||
appSidebarExpand: string
|
||||
currentLogItem?: IChatItem
|
||||
currentLogModalActiveTab: string
|
||||
showPromptLogModal: boolean
|
||||
@ -15,7 +14,6 @@ type State = {
|
||||
|
||||
type Action = {
|
||||
setAppDetail: (appDetail?: App & Partial<AppSSO>) => void
|
||||
setAppSidebarExpand: (state: string) => void
|
||||
setCurrentLogItem: (item?: IChatItem) => void
|
||||
setCurrentLogModalActiveTab: (tab: string) => void
|
||||
setShowPromptLogModal: (showPromptLogModal: boolean) => void
|
||||
@ -27,8 +25,6 @@ type Action = {
|
||||
export const useStore = create<State & Action>(set => ({
|
||||
appDetail: undefined,
|
||||
setAppDetail: appDetail => set(() => ({ appDetail })),
|
||||
appSidebarExpand: '',
|
||||
setAppSidebarExpand: appSidebarExpand => set(() => ({ appSidebarExpand })),
|
||||
currentLogItem: undefined,
|
||||
currentLogModalActiveTab: 'DETAIL',
|
||||
setCurrentLogItem: currentLogItem => set(() => ({ currentLogItem })),
|
||||
|
||||
@ -3,8 +3,8 @@ import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import SwitchAppModal from '../index'
|
||||
|
||||
|
||||
@ -16,15 +16,14 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import Input from '@/app/components/base/input'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { deleteApp, switchApp } from '@/service/apps'
|
||||
@ -59,7 +58,7 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
|
||||
const [removeOriginal, setRemoveOriginal] = useState<boolean>(false)
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
|
||||
const goStart = async () => {
|
||||
try {
|
||||
|
||||
@ -238,14 +238,6 @@ vi.mock('@/service/use-apps', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
NEED_REFRESH_APP_LIST_KEY: 'needRefreshAppList',
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-pay', () => ({
|
||||
CheckModal: () => null,
|
||||
}))
|
||||
|
||||
@ -31,15 +31,14 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@langgenius/dify-ui/tooltip'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useId, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import StarIcon from '@/app/components/base/icons/src/vender/Star'
|
||||
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
|
||||
import { buildInstalledAppPath } from '@/app/components/explore/installed-app/routes'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
@ -311,7 +310,7 @@ export function AppCardActionBar({ app, onRefresh }: AppCardActionBarProps) {
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
const { mutateAsync: mutateDeleteApp, isPending: isDeleting } = useDeleteAppMutation()
|
||||
const { mutateAsync: mutateToggleAppStar, isPending: isTogglingStar } = useToggleAppStarMutation()
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
const resourceMaintainer = getAppResourceMaintainer(app)
|
||||
const maintainerPermissionOptions = useMemo(() => ({
|
||||
currentUserId,
|
||||
@ -738,7 +737,7 @@ export function AppCard({ app, onlineUsers = [], onRefresh, onOpenTagManagement
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
const { mutateAsync: mutateDeleteApp, isPending: isDeleting } = useDeleteAppMutation()
|
||||
const { mutateAsync: mutateToggleAppStar, isPending: isTogglingStar } = useToggleAppStarMutation()
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
const resourceMaintainer = getAppResourceMaintainer(app)
|
||||
const maintainerPermissionOptions = useMemo(() => ({
|
||||
currentUserId,
|
||||
|
||||
@ -4,10 +4,9 @@ import type { AppListQuery, AppListSortBy } from '@/contract/console/apps'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { keepPreviousData, useInfiniteQuery, useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
@ -63,7 +62,7 @@ function List({
|
||||
const [showNewAppModal, setShowNewAppModal] = useState(false)
|
||||
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
||||
const [droppedDSLFile, setDroppedDSLFile] = useState<File | undefined>()
|
||||
const [needsRefreshAppList, setNeedsRefreshAppList] = useLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, '0', { raw: true })
|
||||
const [needsRefreshAppList, setNeedsRefreshAppList] = useNeedRefreshAppList()
|
||||
const canCreateApp = hasPermission(workspacePermissionKeys, 'app.create_and_management')
|
||||
|
||||
const handleDSLFileDropped = useCallback((file: File) => {
|
||||
|
||||
14
web/app/components/apps/storage.ts
Normal file
14
web/app/components/apps/storage.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList'
|
||||
|
||||
const [
|
||||
useNeedRefreshAppList,
|
||||
_useNeedRefreshAppListValue,
|
||||
useSetNeedRefreshAppList,
|
||||
] = createLocalStorageState<string>(NEED_REFRESH_APP_LIST_KEY, '0', { raw: true })
|
||||
|
||||
export {
|
||||
useNeedRefreshAppList,
|
||||
useSetNeedRefreshAppList,
|
||||
}
|
||||
@ -5,10 +5,10 @@ import type { AppData, ConversationItem } from '@/models/share'
|
||||
import type { HumanInputFilledFormData, HumanInputFormData } from '@/types/workflow'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useConversationIdInfo, useWebAppSidebarCollapseState } from '@/app/components/base/chat/storage'
|
||||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
@ -19,12 +19,8 @@ import { useInvalidateShareConversations, useShareChatList, useShareConversation
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
|
||||
import { enrichSubmittedHumanInputFormData } from '../chat/answer/human-input-content/submitted-utils'
|
||||
import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams, getRawInputsFromUrlParams, getRawUserVariablesFromUrlParams } from '../utils'
|
||||
|
||||
const WEBAPP_SIDEBAR_COLLAPSE_STORAGE_KEY = 'webappSidebarCollapse'
|
||||
const rawStorageOptions = { raw: true } as const
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
const newChatList: ChatItem[] = []
|
||||
messages.forEach((item) => {
|
||||
@ -129,17 +125,13 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
}
|
||||
setLocaleFromProps()
|
||||
}, [appData])
|
||||
const [storedSidebarCollapseState, setStoredSidebarCollapseState] = useLocalStorage<string>(
|
||||
WEBAPP_SIDEBAR_COLLAPSE_STORAGE_KEY,
|
||||
undefined,
|
||||
rawStorageOptions,
|
||||
)
|
||||
const [storedSidebarCollapseState, setStoredSidebarCollapseState] = useWebAppSidebarCollapseState()
|
||||
const sidebarCollapseState = storedSidebarCollapseState === 'collapsed'
|
||||
const handleSidebarCollapse = useCallback((state: boolean) => {
|
||||
if (appId)
|
||||
setStoredSidebarCollapseState(state ? 'collapsed' : 'expanded')
|
||||
}, [appId, setStoredSidebarCollapseState])
|
||||
const [conversationIdInfo, setConversationIdInfo] = useLocalStorage<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {})
|
||||
const [conversationIdInfo, setConversationIdInfo] = useConversationIdInfo()
|
||||
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId])
|
||||
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
|
||||
if (appId) {
|
||||
|
||||
@ -28,7 +28,6 @@ describe('Log', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useAppStore).mockImplementation(selector => selector({
|
||||
// State properties
|
||||
appSidebarExpand: 'expand',
|
||||
currentLogModalActiveTab: 'question',
|
||||
showPromptLogModal: false,
|
||||
showAgentLogModal: false,
|
||||
|
||||
@ -5,10 +5,10 @@ import type { Locale } from '@/i18n-config'
|
||||
import type { AppData, ConversationItem } from '@/models/share'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useConversationIdInfo } from '@/app/components/base/chat/storage'
|
||||
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
@ -18,7 +18,6 @@ import { useInvalidateShareConversations, useShareChatList, useShareConversation
|
||||
import { useGetTryAppInfo, useGetTryAppParams } from '@/service/use-try-app'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { getProcessedFilesFromResponse } from '../../file-uploader/utils'
|
||||
import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { buildChatItemTree, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams, getProcessedUserVariablesFromUrlParams } from '../utils'
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
@ -102,7 +101,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
|
||||
}
|
||||
setLanguageFromParams()
|
||||
}, [appInfo])
|
||||
const [conversationIdInfo, setConversationIdInfo] = useLocalStorage<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {})
|
||||
const [conversationIdInfo, setConversationIdInfo] = useConversationIdInfo()
|
||||
const removeConversationIdInfo = useCallback((appId: string) => {
|
||||
setConversationIdInfo((prev) => {
|
||||
const newInfo = { ...prev }
|
||||
|
||||
19
web/app/components/base/chat/storage.ts
Normal file
19
web/app/components/base/chat/storage.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
import { CONVERSATION_ID_INFO } from './constants'
|
||||
|
||||
const [
|
||||
useConversationIdInfo,
|
||||
_useConversationIdInfoValue,
|
||||
_useSetConversationIdInfo,
|
||||
] = createLocalStorageState<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {})
|
||||
|
||||
const [
|
||||
useWebAppSidebarCollapseState,
|
||||
_useWebAppSidebarCollapseStateValue,
|
||||
_useSetWebAppSidebarCollapseState,
|
||||
] = createLocalStorageState<string>('webappSidebarCollapse', undefined, { raw: true })
|
||||
|
||||
export {
|
||||
useConversationIdInfo,
|
||||
useWebAppSidebarCollapseState,
|
||||
}
|
||||
@ -47,8 +47,8 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('foxact/use-local-storage', () => ({
|
||||
useSetLocalStorage: () => setEducationVerifyingMock,
|
||||
vi.mock('@/app/education-apply/storage', () => ({
|
||||
useSetEducationVerifying: () => setEducationVerifyingMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/billing', () => ({
|
||||
|
||||
@ -7,13 +7,12 @@ import {
|
||||
RiGroupLine,
|
||||
} from '@remixicon/react'
|
||||
import { useUnmountedRef } from 'ahooks'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ApiAggregate, TriggerAll } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import UsageInfo from '@/app/components/billing/usage-info'
|
||||
import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants'
|
||||
import { useSetEducationVerifying } from '@/app/education-apply/storage'
|
||||
import VerifyStateModal from '@/app/education-apply/verify-state-modal'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
@ -73,7 +72,7 @@ const PlanComp: FC<Props> = ({
|
||||
const canManageBilling = hasPermission(workspacePermissionKeys, BillingPermission.Manage)
|
||||
const { mutateAsync, isPending } = useEducationVerify()
|
||||
const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal)
|
||||
const setEducationVerifying = useSetLocalStorage<string>(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, { raw: true })
|
||||
const setEducationVerifying = useSetEducationVerifying()
|
||||
const unmountedRef = useUnmountedRef()
|
||||
const handleVerify = () => {
|
||||
if (isPending)
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useEffect } from 'react'
|
||||
import {
|
||||
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
|
||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||
} from '@/app/education-apply/constants'
|
||||
import { EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION } from '@/app/education-apply/constants'
|
||||
import { useSetEducationVerifying } from '@/app/education-apply/storage'
|
||||
import { useSearchParams } from '@/next/navigation'
|
||||
|
||||
export function EducationVerifyActionRecorder() {
|
||||
const searchParams = useSearchParams()
|
||||
const setEducationVerifying = useSetLocalStorage<string>(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, { raw: true })
|
||||
const setEducationVerifying = useSetEducationVerifying()
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get('action') === EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION)
|
||||
|
||||
@ -12,7 +12,7 @@ import { fetchAppDetail, fetchAppList, fetchBanners } from '@/service/explore'
|
||||
import { renderWithNuqs } from '@/test/nuqs-testing'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { AppACLPermission } from '@/utils/permission'
|
||||
import { LEARN_DIFY_HIDDEN_STORAGE_KEY } from '../../learn-dify/atoms'
|
||||
import { LEARN_DIFY_HIDDEN_STORAGE_KEY } from '../../learn-dify/storage'
|
||||
import AppList from '../index'
|
||||
|
||||
type MockAppContext = {
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import { atomWithStorage } from 'jotai/utils'
|
||||
|
||||
export const LEARN_DIFY_HIDDEN_STORAGE_KEY = 'explore-learn-dify-hidden'
|
||||
|
||||
const learnDifyHiddenAtom = atomWithStorage<boolean>(
|
||||
LEARN_DIFY_HIDDEN_STORAGE_KEY,
|
||||
false,
|
||||
undefined,
|
||||
{ getOnInit: true },
|
||||
)
|
||||
|
||||
const learnDifyVisibleAtom = atom(get => !get(learnDifyHiddenAtom))
|
||||
|
||||
export function useLearnDifyVisibleValue() {
|
||||
return useAtomValue(learnDifyVisibleAtom)
|
||||
}
|
||||
|
||||
export function useSetLearnDifyHidden() {
|
||||
return useSetAtom(learnDifyHiddenAtom)
|
||||
}
|
||||
@ -7,8 +7,8 @@ import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLearnDifyAppList } from '@/service/use-explore'
|
||||
import { useLearnDifyVisibleValue, useSetLearnDifyHidden } from './atoms'
|
||||
import LearnDifyItem from './item'
|
||||
import { useLearnDifyHiddenValue, useSetLearnDifyHidden } from './storage'
|
||||
|
||||
type LearnDifyProps = {
|
||||
canCreate?: boolean
|
||||
@ -132,10 +132,10 @@ const LearnDifyContent = ({
|
||||
}
|
||||
|
||||
const DismissibleLearnDify = (props: LearnDifyProps) => {
|
||||
const visible = useLearnDifyVisibleValue()
|
||||
const hidden = useLearnDifyHiddenValue()
|
||||
const setHidden = useSetLearnDifyHidden()
|
||||
|
||||
if (!visible)
|
||||
if (hidden)
|
||||
return null
|
||||
|
||||
return <LearnDifyContent {...props} onHide={() => setHidden(true)} />
|
||||
|
||||
16
web/app/components/explore/learn-dify/storage.ts
Normal file
16
web/app/components/explore/learn-dify/storage.ts
Normal file
@ -0,0 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
export const LEARN_DIFY_HIDDEN_STORAGE_KEY = 'explore-learn-dify-hidden'
|
||||
|
||||
const [
|
||||
_useLearnDifyHidden,
|
||||
useLearnDifyHiddenValue,
|
||||
useSetLearnDifyHidden,
|
||||
] = createLocalStorageState<boolean>(LEARN_DIFY_HIDDEN_STORAGE_KEY, false)
|
||||
|
||||
export {
|
||||
useLearnDifyHiddenValue,
|
||||
useSetLearnDifyHidden,
|
||||
}
|
||||
@ -8,10 +8,10 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { resetUser } from '@/app/components/base/amplitude/utils'
|
||||
import { useSetEducationExpiredHasNoticed, useSetEducationReverifyHasNoticed, useSetEducationReverifyPrevExpireAt } from '@/app/education-apply/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useLogout } from '@/service/use-common'
|
||||
@ -27,9 +27,6 @@ type AccountDropdownProps = {
|
||||
variant?: 'default' | 'mainNav'
|
||||
}
|
||||
|
||||
const EDUCATION_REVERIFY_PREV_EXPIRE_AT_KEY = 'education-reverify-prev-expire-at'
|
||||
const EDUCATION_REVERIFY_HAS_NOTICED_KEY = 'education-reverify-has-noticed'
|
||||
const EDUCATION_EXPIRED_HAS_NOTICED_KEY = 'education-expired-has-noticed'
|
||||
const mainNavMenuPopupClassName = 'w-60 max-w-80 overflow-hidden bg-components-panel-bg-blur! p-0! backdrop-blur-[5px]'
|
||||
|
||||
export default function AppSelector({
|
||||
@ -41,9 +38,9 @@ export default function AppSelector({
|
||||
const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const { userProfile, langGeniusVersionInfo } = useAppContext()
|
||||
const clearEducationReverifyPrevExpireAt = useSetLocalStorage<number>(EDUCATION_REVERIFY_PREV_EXPIRE_AT_KEY)
|
||||
const clearEducationReverifyHasNoticed = useSetLocalStorage<boolean>(EDUCATION_REVERIFY_HAS_NOTICED_KEY)
|
||||
const clearEducationExpiredHasNoticed = useSetLocalStorage<boolean>(EDUCATION_EXPIRED_HAS_NOTICED_KEY)
|
||||
const clearEducationReverifyPrevExpireAt = useSetEducationReverifyPrevExpireAt()
|
||||
const clearEducationReverifyHasNoticed = useSetEducationReverifyHasNoticed()
|
||||
const clearEducationExpiredHasNoticed = useSetEducationExpiredHasNoticed()
|
||||
|
||||
const { mutateAsync: logout } = useLogout()
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
import type { EventEmitterValue } from '@/context/event-emitter'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { usePathname } from '@/next/navigation'
|
||||
import s from './index.module.css'
|
||||
import { useWorkflowCanvasMaximizeValue } from './storage'
|
||||
|
||||
type HeaderWrapperProps = {
|
||||
children: React.ReactNode
|
||||
@ -19,7 +19,7 @@ const HeaderWrapper = ({
|
||||
const isBordered = ['/apps', '/snippets', '/datasets/create', '/tools'].includes(pathname)
|
||||
const inWorkflowCanvas = pathname.endsWith('/workflow')
|
||||
const isPipelineCanvas = pathname.endsWith('/pipeline')
|
||||
const [storedHideHeader] = useLocalStorage<boolean>('workflow-canvas-maximize', false)
|
||||
const storedHideHeader = useWorkflowCanvasMaximizeValue()
|
||||
const [eventHideHeader, setEventHideHeader] = useState<boolean | null>(null)
|
||||
const hideHeader = eventHideHeader ?? storedHideHeader
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
@ -97,7 +97,7 @@ export function Header() {
|
||||
|
||||
return (
|
||||
<div className="flex h-14 items-center">
|
||||
<div className="flex min-w-0 flex-1 items-center overflow-hidden overflow-hidden pr-2 pl-3 min-[1280px]:pr-3">
|
||||
<div className="flex min-w-0 flex-1 items-center overflow-hidden pr-2 pl-3 min-[1280px]:pr-3">
|
||||
{renderLogo()}
|
||||
<div className="mx-1.5 shrink-0 font-light text-divider-deep">/</div>
|
||||
<WorkplaceSelector />
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { X } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { NOTICE_I18N } from '@/i18n-config/language'
|
||||
import { useHideMaintenanceNotice } from './storage'
|
||||
|
||||
const MaintenanceNotice = () => {
|
||||
const { t } = useTranslation()
|
||||
const locale = useLanguage()
|
||||
|
||||
const [hiddenNoticeValue, setHiddenNoticeValue] = useLocalStorage<string>('hide-maintenance-notice', '0', { raw: true })
|
||||
const [hiddenNoticeValue, setHiddenNoticeValue] = useHideMaintenanceNotice()
|
||||
const hiddenNotice = hiddenNoticeValue === '1'
|
||||
const [closedInSession, setClosedInSession] = useState(false)
|
||||
const showNotice = !hiddenNotice && !closedInSession
|
||||
|
||||
18
web/app/components/header/storage.ts
Normal file
18
web/app/components/header/storage.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
const [
|
||||
useHideMaintenanceNotice,
|
||||
_useHideMaintenanceNoticeValue,
|
||||
_useSetHideMaintenanceNotice,
|
||||
] = createLocalStorageState<string>('hide-maintenance-notice', '0', { raw: true })
|
||||
|
||||
const [
|
||||
_useWorkflowCanvasMaximize,
|
||||
useWorkflowCanvasMaximizeValue,
|
||||
_useSetWorkflowCanvasMaximize,
|
||||
] = createLocalStorageState<boolean>('workflow-canvas-maximize', false)
|
||||
|
||||
export {
|
||||
useHideMaintenanceNotice,
|
||||
useWorkflowCanvasMaximizeValue,
|
||||
}
|
||||
@ -10,7 +10,7 @@ import { createStore, Provider as JotaiProvider } from 'jotai'
|
||||
import { createTestQueryClient, renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { LEARN_DIFY_HIDDEN_STORAGE_KEY } from '@/app/components/explore/learn-dify/atoms'
|
||||
import { LEARN_DIFY_HIDDEN_STORAGE_KEY } from '@/app/components/explore/learn-dify/storage'
|
||||
import { useGotoAnythingOpen } from '@/app/components/goto-anything/atoms'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useAppContext, useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
@ -21,6 +21,7 @@ import { consoleQuery } from '@/service/client'
|
||||
import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import MainNav from '../index'
|
||||
import { DETAIL_SIDEBAR_STORAGE_KEY } from '../storage'
|
||||
|
||||
const activeEdgeClassName = 'before:pointer-events-none'
|
||||
|
||||
@ -356,7 +357,6 @@ describe('MainNav', () => {
|
||||
})
|
||||
mockSwitchWorkspace.mockReturnValue(new Promise(() => {}))
|
||||
hotkeyRegistrations.clear()
|
||||
useAppStore.getState().setAppSidebarExpand('')
|
||||
useAppStore.getState().setAppDetail()
|
||||
})
|
||||
|
||||
@ -418,7 +418,7 @@ describe('MainNav', () => {
|
||||
})
|
||||
|
||||
it('keeps the global navigation account section expanded on home routes', () => {
|
||||
localStorage.setItem('app-detail-collapse-or-expand', 'collapse')
|
||||
localStorage.setItem(DETAIL_SIDEBAR_STORAGE_KEY, 'collapse')
|
||||
mockPathname = '/'
|
||||
|
||||
renderMainNav()
|
||||
@ -626,7 +626,7 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('app-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.getByTestId('app-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('collapse')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it('shows app detail navigation as a floating preview when hovering the collapsed top toggle', () => {
|
||||
@ -637,7 +637,7 @@ describe('MainNav', () => {
|
||||
fireEvent.mouseEnter(screen.getByTestId('app-detail-top').parentElement!)
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16', 'overflow-visible')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('collapse')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
expect(screen.getAllByTestId('app-detail-top')).toHaveLength(1)
|
||||
expect(screen.getByTestId('app-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('app-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
@ -655,7 +655,7 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('complementary')).not.toHaveClass('overflow-visible')
|
||||
expect(screen.getByTestId('app-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('app-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('expand')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('expand')
|
||||
})
|
||||
|
||||
it('replaces global navigation with dataset detail navigation on dataset routes', () => {
|
||||
@ -685,7 +685,7 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('dataset-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.getByTestId('dataset-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('collapse')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it('shows dataset detail navigation as a floating preview when hovering the collapsed top toggle', () => {
|
||||
@ -696,7 +696,7 @@ describe('MainNav', () => {
|
||||
fireEvent.mouseEnter(screen.getByTestId('dataset-detail-top').parentElement!)
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16', 'overflow-visible')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('collapse')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
expect(screen.getAllByTestId('dataset-detail-top')).toHaveLength(1)
|
||||
expect(screen.getByTestId('dataset-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('dataset-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
@ -756,7 +756,7 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('agent-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.getByTestId('agent-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('collapse')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it('collapses deployment detail navigation from the top-right toggle', () => {
|
||||
@ -769,7 +769,7 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('deployment-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.getByTestId('deployment-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('collapse')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it.each([
|
||||
@ -804,7 +804,7 @@ describe('MainNav', () => {
|
||||
fireEvent.mouseEnter(screen.getByTestId('agent-detail-top').parentElement!)
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16', 'overflow-visible')
|
||||
expect(localStorage.getItem('app-detail-collapse-or-expand')).toBe('collapse')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
expect(screen.getAllByTestId('agent-detail-top')).toHaveLength(1)
|
||||
expect(screen.getByTestId('agent-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('agent-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLearnDifyVisibleValue, useSetLearnDifyHidden } from '@/app/components/explore/learn-dify/atoms'
|
||||
import { useLearnDifyHiddenValue, useSetLearnDifyHidden } from '@/app/components/explore/learn-dify/storage'
|
||||
import AccountAbout from '@/app/components/header/account-about'
|
||||
import Compliance from '@/app/components/header/account-dropdown/compliance'
|
||||
import { ExternalLinkIndicator, MenuItemContent } from '@/app/components/header/account-dropdown/menu-item-content'
|
||||
@ -52,7 +52,7 @@ const HelpMenu = ({
|
||||
const docLink = useDocLink()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { langGeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
|
||||
const learnDifyVisible = useLearnDifyVisibleValue()
|
||||
const learnDifyHidden = useLearnDifyHiddenValue()
|
||||
const setLearnDifyHidden = useSetLearnDifyHidden()
|
||||
const [aboutVisible, setAboutVisible] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
@ -96,7 +96,7 @@ const HelpMenu = ({
|
||||
/>
|
||||
</DropdownMenuLinkItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={learnDifyVisible}
|
||||
checked={!learnDifyHidden}
|
||||
closeOnClick={false}
|
||||
className="mx-0 h-8 gap-1 px-0 py-1 pr-2 pl-3"
|
||||
onCheckedChange={checked => setLearnDifyHidden(!checked)}
|
||||
@ -109,13 +109,13 @@ const HelpMenu = ({
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'relative inline-flex h-4 w-7 shrink-0 items-center rounded-[5px] p-0.5 transition-colors',
|
||||
learnDifyVisible ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked',
|
||||
!learnDifyHidden ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'block h-3 w-2.5 rounded-[3px] bg-components-toggle-knob shadow-sm transition-transform',
|
||||
learnDifyVisible && 'translate-x-3.5',
|
||||
!learnDifyHidden && 'translate-x-3.5',
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@ -4,7 +4,6 @@ import type { MainNavItem, MainNavProps } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useHotkey } from '@tanstack/react-hotkeys'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
@ -29,11 +28,11 @@ import { MainNavSearchButton } from './components/search-button'
|
||||
import WebAppsSection from './components/web-apps-section'
|
||||
import { WorkspaceCard } from './components/workspace-card'
|
||||
import { isMainNavRouteVisible, MAIN_NAV_ROUTES } from './routes'
|
||||
import { useDetailSidebarMode } from './storage'
|
||||
|
||||
const DATASET_COLLECTION_ROUTES = new Set(['create', 'create-from-pipeline', 'connect'])
|
||||
const DATASET_DOCUMENT_CREATION_ROUTES = new Set(['create', 'create-from-pipeline'])
|
||||
const DEPLOYMENT_COLLECTION_ROUTES = new Set(['create'])
|
||||
const DETAIL_SIDEBAR_STORAGE_KEY = 'app-detail-collapse-or-expand'
|
||||
const secondarySidebarHelpTriggerIcon = <span aria-hidden className="i-ri-question-line size-4 shrink-0" />
|
||||
|
||||
function SecondarySidebarHelpMenu({
|
||||
@ -98,14 +97,12 @@ const MainNav = ({
|
||||
const showDeploymentDetailNavigation = canUseAppDeploy && !isCurrentWorkspaceDatasetOperator && isDeploymentDetailPathname(pathname)
|
||||
const showSnippetDetailBottomNavigation = isSnippetDetailPathname(pathname)
|
||||
const showDetailNavigation = showAppDetailNavigation || showDatasetDetailNavigation || showAgentDetailNavigation || showDeploymentDetailNavigation
|
||||
const { hasAppDetail, appSidebarExpand, setAppDetail, setAppSidebarExpand } = useAppStore(useShallow(state => ({
|
||||
const { hasAppDetail, setAppDetail } = useAppStore(useShallow(state => ({
|
||||
hasAppDetail: !!state.appDetail,
|
||||
appSidebarExpand: state.appSidebarExpand,
|
||||
setAppDetail: state.setAppDetail,
|
||||
setAppSidebarExpand: state.setAppSidebarExpand,
|
||||
})))
|
||||
const [storedDetailSidebarExpand, setStoredDetailSidebarExpand] = useLocalStorage<string>(DETAIL_SIDEBAR_STORAGE_KEY, 'expand', { raw: true })
|
||||
const detailNavigationMode = appSidebarExpand === 'collapse' || (!appSidebarExpand && storedDetailSidebarExpand === 'collapse') ? 'collapse' : 'expand'
|
||||
const [storedDetailSidebarExpand, setStoredDetailSidebarExpand] = useDetailSidebarMode()
|
||||
const detailNavigationMode = storedDetailSidebarExpand === 'collapse' ? 'collapse' : 'expand'
|
||||
const detailNavigationExpanded = detailNavigationMode === 'expand'
|
||||
const isCollapsedDetailNavigation = showDetailNavigation && !detailNavigationExpanded
|
||||
const [detailNavigationHoverPreviewOpen, setDetailNavigationHoverPreviewOpen] = useState(false)
|
||||
@ -124,16 +121,17 @@ const MainNav = ({
|
||||
|
||||
setDetailNavigationTransitionDisabled(true)
|
||||
setDetailNavigationHoverPreviewOpen(false)
|
||||
setAppSidebarExpand('expand')
|
||||
setStoredDetailSidebarExpand('expand')
|
||||
detailNavigationTransitionTimerRef.current = setTimeout(() => {
|
||||
setDetailNavigationTransitionDisabled(false)
|
||||
}, 200)
|
||||
return
|
||||
}
|
||||
|
||||
const nextMode = detailNavigationExpanded ? 'collapse' : 'expand'
|
||||
setDetailNavigationHoverPreviewOpen(false)
|
||||
setAppSidebarExpand(detailNavigationExpanded ? 'collapse' : 'expand')
|
||||
}, [detailNavigationExpanded, isDetailNavigationHoverPreviewOpen, setAppSidebarExpand])
|
||||
setStoredDetailSidebarExpand(nextMode)
|
||||
}, [detailNavigationExpanded, isDetailNavigationHoverPreviewOpen, setStoredDetailSidebarExpand])
|
||||
const openDetailNavigationHoverPreview = useCallback(() => {
|
||||
if (!isCollapsedDetailNavigation)
|
||||
return
|
||||
@ -161,13 +159,6 @@ const MainNav = ({
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!showDetailNavigation)
|
||||
return
|
||||
|
||||
setStoredDetailSidebarExpand(detailNavigationMode)
|
||||
}, [detailNavigationMode, setStoredDetailSidebarExpand, showDetailNavigation])
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname.startsWith('/app/') || !hasAppDetail)
|
||||
return
|
||||
|
||||
16
web/app/components/main-nav/storage.ts
Normal file
16
web/app/components/main-nav/storage.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
type DetailSidebarMode = 'expand' | 'collapse'
|
||||
|
||||
export const DETAIL_SIDEBAR_STORAGE_KEY = 'app-detail-collapse-or-expand'
|
||||
|
||||
const [
|
||||
useDetailSidebarMode,
|
||||
_useDetailSidebarModeValue,
|
||||
useSetDetailSidebarMode,
|
||||
] = createLocalStorageState<DetailSidebarMode>(DETAIL_SIDEBAR_STORAGE_KEY, 'expand', { raw: true })
|
||||
|
||||
export {
|
||||
useDetailSidebarMode,
|
||||
useSetDetailSidebarMode,
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Countdown, { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '../countdown'
|
||||
import Countdown from '../countdown'
|
||||
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '../storage'
|
||||
|
||||
describe('Countdown', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@ -2,9 +2,7 @@
|
||||
import { useCountDown } from 'ahooks'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const COUNT_DOWN_TIME_MS = 59000
|
||||
export const COUNT_DOWN_KEY = 'leftTime'
|
||||
import { COUNT_DOWN_TIME_MS, useCountdownLeftTimeValue, useSetCountdownLeftTime } from './storage'
|
||||
|
||||
type CountdownProps = {
|
||||
onResend?: () => void
|
||||
@ -12,24 +10,26 @@ type CountdownProps = {
|
||||
|
||||
export default function Countdown({ onResend }: CountdownProps) {
|
||||
const { t } = useTranslation()
|
||||
const [leftTime, setLeftTime] = useState(() => Number(localStorage.getItem(COUNT_DOWN_KEY) || COUNT_DOWN_TIME_MS))
|
||||
const storedLeftTime = useCountdownLeftTimeValue()
|
||||
const setStoredLeftTime = useSetCountdownLeftTime()
|
||||
const [leftTime, setLeftTime] = useState(() => Number(storedLeftTime || COUNT_DOWN_TIME_MS))
|
||||
const [time] = useCountDown({
|
||||
leftTime,
|
||||
onEnd: () => {
|
||||
setLeftTime(0)
|
||||
localStorage.removeItem(COUNT_DOWN_KEY)
|
||||
setStoredLeftTime(null)
|
||||
},
|
||||
})
|
||||
|
||||
const resend = async function () {
|
||||
setLeftTime(COUNT_DOWN_TIME_MS)
|
||||
localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`)
|
||||
setStoredLeftTime(`${COUNT_DOWN_TIME_MS}`)
|
||||
onResend?.()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(COUNT_DOWN_KEY, `${time}`)
|
||||
}, [time])
|
||||
setStoredLeftTime(`${time}`)
|
||||
}, [setStoredLeftTime, time])
|
||||
|
||||
return (
|
||||
<p className="system-xs-regular text-text-tertiary">
|
||||
|
||||
15
web/app/components/signin/storage.ts
Normal file
15
web/app/components/signin/storage.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
export const COUNT_DOWN_TIME_MS = 59000
|
||||
export const COUNT_DOWN_KEY = 'leftTime'
|
||||
|
||||
const [
|
||||
_useCountdownLeftTime,
|
||||
useCountdownLeftTimeValue,
|
||||
useSetCountdownLeftTime,
|
||||
] = createLocalStorageState<string>(COUNT_DOWN_KEY, undefined, { raw: true })
|
||||
|
||||
export {
|
||||
useCountdownLeftTimeValue,
|
||||
useSetCountdownLeftTime,
|
||||
}
|
||||
@ -3,7 +3,6 @@ import { render, screen } from '@testing-library/react'
|
||||
import SnippetPage from '..'
|
||||
|
||||
const mockUseSnippetInit = vi.fn()
|
||||
const mockSetAppSidebarExpand = vi.fn()
|
||||
let capturedWorkflowDefaultContextProps: {
|
||||
nodes: unknown[]
|
||||
edges: unknown[]
|
||||
@ -39,12 +38,6 @@ vi.mock('@/hooks/use-document-title', () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/app/store', () => ({
|
||||
useStore: (selector: (state: { setAppSidebarExpand: typeof mockSetAppSidebarExpand }) => unknown) => selector({
|
||||
setAppSidebarExpand: mockSetAppSidebarExpand,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow', () => ({
|
||||
default: ({
|
||||
children,
|
||||
|
||||
@ -6,7 +6,6 @@ import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -15,6 +14,7 @@ import useWorkspacePluginInstallPermission from '@/app/components/plugins/instal
|
||||
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
|
||||
import { getMarketplaceCategoryUrl } from '@/app/components/plugins/marketplace/utils'
|
||||
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
|
||||
import { useFeaturedToolsCollapsed } from '@/app/components/workflow/block-selector/storage'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import Link from '@/next/link'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
@ -42,8 +42,6 @@ type FeaturedToolPreviewPayload = {
|
||||
description: string
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'workflow_tools_featured_collapsed'
|
||||
|
||||
const FeaturedTools = ({
|
||||
plugins,
|
||||
providerMap,
|
||||
@ -57,7 +55,7 @@ const FeaturedTools = ({
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<FeaturedToolPreviewPayload>(), [])
|
||||
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
|
||||
const [visibleCountPlugins, setVisibleCountPlugins] = useState(plugins)
|
||||
const [isCollapsed, setIsCollapsed] = useLocalStorage<boolean>(STORAGE_KEY, false)
|
||||
const [isCollapsed, setIsCollapsed] = useFeaturedToolsCollapsed()
|
||||
|
||||
if (visibleCountPlugins !== plugins) {
|
||||
setVisibleCountPlugins(plugins)
|
||||
|
||||
@ -6,7 +6,6 @@ import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -15,6 +14,7 @@ import useWorkspacePluginInstallPermission from '@/app/components/plugins/instal
|
||||
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
|
||||
import { getMarketplaceCategoryUrl } from '@/app/components/plugins/marketplace/utils'
|
||||
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
|
||||
import { useFeaturedTriggersCollapsed } from '@/app/components/workflow/block-selector/storage'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import Link from '@/next/link'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
@ -41,8 +41,6 @@ type FeaturedTriggerPreviewPayload = {
|
||||
description: string
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'workflow_triggers_featured_collapsed'
|
||||
|
||||
const FeaturedTriggers = ({
|
||||
plugins,
|
||||
providerMap,
|
||||
@ -56,7 +54,7 @@ const FeaturedTriggers = ({
|
||||
const triggerActionPreviewCardHandle = useMemo(() => createPreviewCardHandle<TriggerPluginActionPreviewPayload>(), [])
|
||||
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
|
||||
const [visibleCountPlugins, setVisibleCountPlugins] = useState(plugins)
|
||||
const [isCollapsed, setIsCollapsed] = useLocalStorage<boolean>(STORAGE_KEY, false)
|
||||
const [isCollapsed, setIsCollapsed] = useFeaturedTriggersCollapsed()
|
||||
|
||||
if (visibleCountPlugins !== plugins) {
|
||||
setVisibleCountPlugins(plugins)
|
||||
|
||||
@ -3,13 +3,13 @@ import type { Dispatch, SetStateAction } from 'react'
|
||||
import type { ViewType } from '@/app/components/workflow/block-selector/view-type-select'
|
||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
||||
import { RiMoreLine } from '@remixicon/react'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/arrows'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
|
||||
import { useRAGRecommendationsCollapsed } from '@/app/components/workflow/block-selector/storage'
|
||||
import Link from '@/next/link'
|
||||
import { useRAGRecommendedPlugins } from '@/service/use-tools'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
@ -21,15 +21,13 @@ type RAGToolRecommendationsProps = {
|
||||
onTagsChange: Dispatch<SetStateAction<string[]>>
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'workflow_rag_recommendations_collapsed'
|
||||
|
||||
const RAGToolRecommendations = ({
|
||||
viewType,
|
||||
onSelect,
|
||||
onTagsChange,
|
||||
}: RAGToolRecommendationsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [isCollapsed, setIsCollapsed] = useLocalStorage<boolean>(STORAGE_KEY, false)
|
||||
const [isCollapsed, setIsCollapsed] = useRAGRecommendationsCollapsed()
|
||||
|
||||
const {
|
||||
data: ragRecommendedPlugins,
|
||||
|
||||
25
web/app/components/workflow/block-selector/storage.ts
Normal file
25
web/app/components/workflow/block-selector/storage.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
const [
|
||||
useFeaturedToolsCollapsed,
|
||||
_useFeaturedToolsCollapsedValue,
|
||||
_useSetFeaturedToolsCollapsed,
|
||||
] = createLocalStorageState<boolean>('workflow_tools_featured_collapsed', false)
|
||||
|
||||
const [
|
||||
useFeaturedTriggersCollapsed,
|
||||
_useFeaturedTriggersCollapsedValue,
|
||||
_useSetFeaturedTriggersCollapsed,
|
||||
] = createLocalStorageState<boolean>('workflow_triggers_featured_collapsed', false)
|
||||
|
||||
const [
|
||||
useRAGRecommendationsCollapsed,
|
||||
_useRAGRecommendationsCollapsedValue,
|
||||
_useSetRAGRecommendationsCollapsed,
|
||||
] = createLocalStorageState<boolean>('workflow_rag_recommendations_collapsed', false)
|
||||
|
||||
export {
|
||||
useFeaturedToolsCollapsed,
|
||||
useFeaturedTriggersCollapsed,
|
||||
useRAGRecommendationsCollapsed,
|
||||
}
|
||||
@ -13,7 +13,6 @@ import {
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import * as React from 'react'
|
||||
import {
|
||||
cloneElement,
|
||||
@ -58,6 +57,7 @@ import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
|
||||
import { NodeActionsDropdown } from '@/app/components/workflow/node-actions-menu'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import { useSetWorkflowNodePanelWidth } from '@/app/components/workflow/persistence/local-storage-options'
|
||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
||||
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
@ -165,7 +165,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
const setNodePanelWidth = useStore(s => s.setNodePanelWidth)
|
||||
const pendingSingleRun = useStore(s => s.pendingSingleRun)
|
||||
const setPendingSingleRun = useStore(s => s.setPendingSingleRun)
|
||||
const setNodePanelWidthStorage = useSetLocalStorage<string>('workflow-node-panel-width', { raw: true })
|
||||
const setNodePanelWidthStorage = useSetWorkflowNodePanelWidth()
|
||||
|
||||
const reservedCanvasWidth = 400 // Reserve the minimum visible width for the canvas
|
||||
|
||||
@ -178,7 +178,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
const newValue = clampNodePanelWidth(width, maxNodePanelWidth)
|
||||
|
||||
if (source === 'user')
|
||||
setNodePanelWidthStorage(`${newValue}`)
|
||||
setNodePanelWidthStorage(newValue)
|
||||
|
||||
setNodePanelWidth(newValue)
|
||||
}, [maxNodePanelWidth, setNodePanelWidth, setNodePanelWidthStorage])
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useAutoGenModel } from '@/app/components/app/configuration/config/auto-gen-model-storage'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
@ -41,25 +42,13 @@ const createEmptyModel = (): Model => ({
|
||||
completion_params: {} as CompletionParams,
|
||||
})
|
||||
|
||||
const getStoredModel = (): Model | null => {
|
||||
if (typeof window === 'undefined')
|
||||
return null
|
||||
|
||||
const savedModel = window.localStorage.getItem('auto-gen-model')
|
||||
|
||||
if (!savedModel)
|
||||
return null
|
||||
|
||||
return JSON.parse(savedModel) as Model
|
||||
}
|
||||
|
||||
const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
|
||||
onApply,
|
||||
crossAxisOffset,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [view, setView] = useState<GeneratorView>(GENERATOR_VIEWS.promptEditor)
|
||||
const [model, setModel] = useState<Model | null>(() => getStoredModel())
|
||||
const [model, setModel] = useAutoGenModel()
|
||||
const [instruction, setInstruction] = useState('')
|
||||
const [schema, setSchema] = useState<SchemaRoot | null>(null)
|
||||
const { theme } = useTheme()
|
||||
@ -102,8 +91,7 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
|
||||
mode: newValue.mode as ModelModeType,
|
||||
}
|
||||
setModel(newModel)
|
||||
window.localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||
}, [resolvedModel])
|
||||
}, [resolvedModel, setModel])
|
||||
|
||||
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
|
||||
const newModel = {
|
||||
@ -111,8 +99,7 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
|
||||
completion_params: newParams as CompletionParams,
|
||||
}
|
||||
setModel(newModel)
|
||||
window.localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||
}, [resolvedModel])
|
||||
}, [resolvedModel, setModel])
|
||||
|
||||
const { mutateAsync: generateStructuredOutputRules, isPending: isGenerating } = useGenerateStructuredOutputRules()
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiDraggable } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { produce } from 'immer'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
@ -14,12 +13,11 @@ import { ReactSortable } from 'react-sortablejs'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { useEdgesInteractions } from '../../../hooks'
|
||||
import AddButton from '../../_base/components/add-button'
|
||||
import { useInlineLabelHintDismissed } from '../storage'
|
||||
import Item from './class-item'
|
||||
import { getDefaultClassLabel, isDefaultClassLabel } from './class-label-utils'
|
||||
|
||||
const i18nPrefix = 'nodes.questionClassifiers'
|
||||
const INLINE_LABEL_HINT_STORAGE_KEY = 'question-classifier-inline-label-hint-dismissed'
|
||||
|
||||
type Props = Readonly<{
|
||||
nodeId: string
|
||||
list: Topic[]
|
||||
@ -43,7 +41,7 @@ const ClassList: FC<Props> = ({
|
||||
const [shouldScrollToEnd, setShouldScrollToEnd] = useState(false)
|
||||
const prevListLength = useRef(list.length)
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [storedRenameHintDismissed, setIsRenameHintDismissed] = useLocalStorage<boolean>(INLINE_LABEL_HINT_STORAGE_KEY)
|
||||
const [storedRenameHintDismissed, setIsRenameHintDismissed] = useInlineLabelHintDismissed()
|
||||
const isRenameHintDismissed = storedRenameHintDismissed ?? false
|
||||
|
||||
const handleClassChange = useCallback((index: number) => {
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
const [
|
||||
useInlineLabelHintDismissed,
|
||||
_useInlineLabelHintDismissedValue,
|
||||
_useSetInlineLabelHintDismissed,
|
||||
] = createLocalStorageState<boolean>('question-classifier-inline-label-hint-dismissed')
|
||||
|
||||
export {
|
||||
useInlineLabelHintDismissed,
|
||||
}
|
||||
@ -1,14 +1,13 @@
|
||||
import type { EditorState } from 'lexical'
|
||||
import type { NoteTheme } from './types'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback } from 'react'
|
||||
import { useNodeDataUpdate, useWorkflowHistory, WorkflowHistoryEvent } from '../hooks'
|
||||
import { NOTE_SHOW_AUTHOR_STORAGE_KEY } from './constants'
|
||||
import { useSetWorkflowNoteShowAuthor } from '../persistence/local-storage-options'
|
||||
|
||||
export const useNote = (id: string) => {
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
const { saveStateToHistory } = useWorkflowHistory()
|
||||
const setShowAuthorStorage = useSetLocalStorage<string>(NOTE_SHOW_AUTHOR_STORAGE_KEY, { raw: true })
|
||||
const setShowAuthorStorage = useSetWorkflowNoteShowAuthor()
|
||||
|
||||
const handleThemeChange = useCallback((theme: NoteTheme) => {
|
||||
handleNodeDataUpdateWithSyncDraft({ id, data: { theme } })
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
import type { NoteNodeType } from '../note-node/types'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback } from 'react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import {
|
||||
CUSTOM_NOTE_NODE,
|
||||
NOTE_SHOW_AUTHOR_STORAGE_KEY,
|
||||
} from '../note-node/constants'
|
||||
import { NoteTheme } from '../note-node/types'
|
||||
import { useWorkflowNoteShowAuthorValue } from '../persistence/local-storage-options'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import { generateNewNode } from '../utils'
|
||||
|
||||
export const useOperator = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { userProfile } = useAppContext()
|
||||
const [showAuthorStorage] = useLocalStorage<string>(NOTE_SHOW_AUTHOR_STORAGE_KEY, 'true', { raw: true })
|
||||
const showAuthorStorage = useWorkflowNoteShowAuthorValue()
|
||||
|
||||
const handleAddNote = useCallback(() => {
|
||||
const { newNode } = generateNewNode({
|
||||
|
||||
@ -5,7 +5,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/too
|
||||
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
@ -24,6 +23,7 @@ import {
|
||||
useWorkflowInteractions,
|
||||
} from '../../hooks'
|
||||
import { useResizePanel } from '../../nodes/_base/hooks/use-resize-panel'
|
||||
import { useSetDebugPreviewPanelWidth } from '../../persistence/local-storage-options'
|
||||
import { BlockEnum } from '../../types'
|
||||
import ChatWrapper from './chat-wrapper'
|
||||
|
||||
@ -55,10 +55,10 @@ const DebugAndPreview = () => {
|
||||
const nodePanelWidth = useStore(s => s.nodePanelWidth)
|
||||
const panelWidth = useStore(s => s.previewPanelWidth)
|
||||
const setPanelWidth = useStore(s => s.setPreviewPanelWidth)
|
||||
const setPanelWidthStorage = useSetLocalStorage<string>('debug-and-preview-panel-width', { raw: true })
|
||||
const setPanelWidthStorage = useSetDebugPreviewPanelWidth()
|
||||
const handleResize = useCallback((width: number, source: 'user' | 'system' = 'user') => {
|
||||
if (source === 'user')
|
||||
setPanelWidthStorage(`${width}`)
|
||||
setPanelWidthStorage(width)
|
||||
setPanelWidth(width)
|
||||
}, [setPanelWidth, setPanelWidthStorage])
|
||||
const maxPanelWidth = useMemo(() => {
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { useLocalStorage, useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useEffect, useLayoutEffect as useLayoutEffectFromReact } from 'react'
|
||||
import { useStore, useWorkflowStore } from '../store'
|
||||
import {
|
||||
isControlMode,
|
||||
isFiniteNumber,
|
||||
numberStorageOptions,
|
||||
rawStorageOptions,
|
||||
WORKFLOW_NODE_PANEL_WIDTH_KEY,
|
||||
WORKFLOW_OPERATION_MODE_KEY,
|
||||
WORKFLOW_PREVIEW_PANEL_WIDTH_KEY,
|
||||
WORKFLOW_VARIABLE_INSPECT_PANEL_HEIGHT_KEY,
|
||||
useDebugPreviewPanelWidthValue,
|
||||
useWorkflowNodePanelWidthValue,
|
||||
useWorkflowOperationMode,
|
||||
useWorkflowVariableInspectPanelHeightValue,
|
||||
} from './local-storage-options'
|
||||
|
||||
const useIsoLayoutEffect = typeof document !== 'undefined'
|
||||
@ -17,10 +14,10 @@ const useIsoLayoutEffect = typeof document !== 'undefined'
|
||||
: useEffect
|
||||
|
||||
export const WorkflowLocalStorageBridge = () => {
|
||||
const [storedNodePanelWidth] = useLocalStorage<number>(WORKFLOW_NODE_PANEL_WIDTH_KEY, undefined, numberStorageOptions)
|
||||
const [storedPreviewPanelWidth] = useLocalStorage<number>(WORKFLOW_PREVIEW_PANEL_WIDTH_KEY, undefined, numberStorageOptions)
|
||||
const [storedVariableInspectPanelHeight] = useLocalStorage<number>(WORKFLOW_VARIABLE_INSPECT_PANEL_HEIGHT_KEY, undefined, numberStorageOptions)
|
||||
const [storedControlMode] = useLocalStorage<string>(WORKFLOW_OPERATION_MODE_KEY, undefined, rawStorageOptions)
|
||||
const storedNodePanelWidth = useWorkflowNodePanelWidthValue()
|
||||
const storedPreviewPanelWidth = useDebugPreviewPanelWidthValue()
|
||||
const storedVariableInspectPanelHeight = useWorkflowVariableInspectPanelHeightValue()
|
||||
const [storedControlMode, setControlModeStorage] = useWorkflowOperationMode()
|
||||
|
||||
const workflowStore = useWorkflowStore()
|
||||
const setNodePanelWidth = useStore(state => state.setNodePanelWidth)
|
||||
@ -29,8 +26,6 @@ export const WorkflowLocalStorageBridge = () => {
|
||||
const setVariableInspectPanelHeight = useStore(state => state.setVariableInspectPanelHeight)
|
||||
const setControlMode = useStore(state => state.setControlMode)
|
||||
|
||||
const setControlModeStorage = useSetLocalStorage<string>(WORKFLOW_OPERATION_MODE_KEY, rawStorageOptions)
|
||||
|
||||
useIsoLayoutEffect(() => {
|
||||
if (!isFiniteNumber(storedNodePanelWidth))
|
||||
return
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
import { NOTE_SHOW_AUTHOR_STORAGE_KEY } from '../note-node/constants'
|
||||
import { ControlMode } from '../types'
|
||||
|
||||
export const WORKFLOW_NODE_PANEL_WIDTH_KEY = 'workflow-node-panel-width'
|
||||
export const WORKFLOW_PREVIEW_PANEL_WIDTH_KEY = 'debug-and-preview-panel-width'
|
||||
export const WORKFLOW_VARIABLE_INSPECT_PANEL_HEIGHT_KEY = 'workflow-variable-inpsect-panel-height'
|
||||
export const WORKFLOW_OPERATION_MODE_KEY = 'workflow-operation-mode'
|
||||
const WORKFLOW_NODE_PANEL_WIDTH_KEY = 'workflow-node-panel-width'
|
||||
const WORKFLOW_PREVIEW_PANEL_WIDTH_KEY = 'debug-and-preview-panel-width'
|
||||
const WORKFLOW_VARIABLE_INSPECT_PANEL_HEIGHT_KEY = 'workflow-variable-inpsect-panel-height'
|
||||
const WORKFLOW_OPERATION_MODE_KEY = 'workflow-operation-mode'
|
||||
|
||||
export const rawStorageOptions = { raw: true } as const
|
||||
export const numberStorageOptions = {
|
||||
const rawStorageOptions = { raw: true } as const
|
||||
const numberStorageOptions = {
|
||||
serializer: String,
|
||||
deserializer: Number,
|
||||
} as const
|
||||
@ -18,3 +20,45 @@ export const isControlMode = (value: string | null): value is ControlMode => {
|
||||
export const isFiniteNumber = (value: number | null): value is number => {
|
||||
return value !== null && Number.isFinite(value)
|
||||
}
|
||||
|
||||
const [
|
||||
_useWorkflowNodePanelWidth,
|
||||
useWorkflowNodePanelWidthValue,
|
||||
useSetWorkflowNodePanelWidth,
|
||||
] = createLocalStorageState<number>(WORKFLOW_NODE_PANEL_WIDTH_KEY, undefined, numberStorageOptions)
|
||||
|
||||
const [
|
||||
_useDebugPreviewPanelWidth,
|
||||
useDebugPreviewPanelWidthValue,
|
||||
useSetDebugPreviewPanelWidth,
|
||||
] = createLocalStorageState<number>(WORKFLOW_PREVIEW_PANEL_WIDTH_KEY, undefined, numberStorageOptions)
|
||||
|
||||
const [
|
||||
_useWorkflowVariableInspectPanelHeight,
|
||||
useWorkflowVariableInspectPanelHeightValue,
|
||||
useSetWorkflowVariableInspectPanelHeight,
|
||||
] = createLocalStorageState<number>(WORKFLOW_VARIABLE_INSPECT_PANEL_HEIGHT_KEY, undefined, numberStorageOptions)
|
||||
|
||||
const [
|
||||
useWorkflowOperationMode,
|
||||
_useWorkflowOperationModeValue,
|
||||
_useSetWorkflowOperationMode,
|
||||
] = createLocalStorageState<string>(WORKFLOW_OPERATION_MODE_KEY, undefined, rawStorageOptions)
|
||||
|
||||
const [
|
||||
_useWorkflowNoteShowAuthor,
|
||||
useWorkflowNoteShowAuthorValue,
|
||||
useSetWorkflowNoteShowAuthor,
|
||||
] = createLocalStorageState<string>(NOTE_SHOW_AUTHOR_STORAGE_KEY, 'true', rawStorageOptions)
|
||||
|
||||
export {
|
||||
useDebugPreviewPanelWidthValue,
|
||||
useSetDebugPreviewPanelWidth,
|
||||
useSetWorkflowNodePanelWidth,
|
||||
useSetWorkflowNoteShowAuthor,
|
||||
useSetWorkflowVariableInspectPanelHeight,
|
||||
useWorkflowNodePanelWidthValue,
|
||||
useWorkflowNoteShowAuthorValue,
|
||||
useWorkflowOperationMode,
|
||||
useWorkflowVariableInspectPanelHeightValue,
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useResizePanel } from '../nodes/_base/hooks/use-resize-panel'
|
||||
import { useSetWorkflowVariableInspectPanelHeight } from '../persistence/local-storage-options'
|
||||
import { useStore } from '../store'
|
||||
import Panel from './panel'
|
||||
|
||||
@ -22,10 +22,10 @@ const VariableInspectPanel: FC = () => {
|
||||
return workflowCanvasHeight - 60
|
||||
}, [workflowCanvasHeight])
|
||||
|
||||
const setPanelHeightStorage = useSetLocalStorage<string>('workflow-variable-inpsect-panel-height', { raw: true })
|
||||
const setPanelHeightStorage = useSetWorkflowVariableInspectPanelHeight()
|
||||
|
||||
const handleResize = useCallback((width: number, height: number) => {
|
||||
setPanelHeightStorage(`${height}`)
|
||||
setPanelHeightStorage(height)
|
||||
setVariableInspectPanelHeight(height)
|
||||
}, [setVariableInspectPanelHeight, setPanelHeightStorage])
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import type { GeneratedGraph } from './types'
|
||||
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { CompletionParams, Model } from '@/types/app'
|
||||
import type { CompletionParams, ModelModeType } from '@/types/app'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
@ -16,7 +16,6 @@ import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -29,15 +28,14 @@ import WorkflowPreview from '@/app/components/workflow/workflow-preview'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { generateWorkflow } from '@/service/debug'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
import { ModelModeType } from '@/types/app'
|
||||
import { getRedirectionPath } from '@/utils/app-redirection'
|
||||
import { applyToCurrentApp, applyToNewApp, WorkflowApplyHashCollisionError, WorkflowApplyOrphanError } from './apply'
|
||||
import ExamplePrompts from './example-prompts'
|
||||
import GenerationPhases from './generation-phases'
|
||||
import { EMPTY_WORKFLOW_GENERATOR_MODEL, useWorkflowGeneratorModel } from './storage'
|
||||
import { useWorkflowGeneratorStore } from './store'
|
||||
import useGenGraph from './use-gen-graph'
|
||||
|
||||
const STORAGE_MODEL_KEY = 'workflow-gen-model'
|
||||
// Hard ceiling before we abort a hung request. Generous on purpose: the
|
||||
// backend runs two sequential LLM calls and may retry a transient provider
|
||||
// error (bounded backoff) or an unparseable response (one extra call), so a
|
||||
@ -48,17 +46,6 @@ const FE_TIMEOUT_MS = 90_000
|
||||
// keeping the limit client-side turns an opaque 400 into a visible input stop.
|
||||
const MAX_INSTRUCTION_LENGTH = 10_000
|
||||
|
||||
// Stable default used both as the SSR/empty-storage seed for the persisted
|
||||
// model and as the merge base when patching a partial update. Module-level so
|
||||
// the reference stays identical across renders (useLocalStorage uses it as the
|
||||
// server value, which must not change identity each render).
|
||||
const EMPTY_MODEL: Model = {
|
||||
name: '',
|
||||
provider: '',
|
||||
mode: ModelModeType.chat,
|
||||
completion_params: {} as CompletionParams,
|
||||
}
|
||||
|
||||
const renderPlaceholder = (label: string) => (
|
||||
<div className="flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8">
|
||||
<span className="i-custom-vender-other-generator size-8 text-text-quaternary" />
|
||||
@ -120,10 +107,7 @@ const WorkflowGeneratorModal: React.FC = () => {
|
||||
|
||||
const isRefine = intent === 'refine' && !!currentAppId
|
||||
|
||||
// Persisted model selection. ``useLocalStorage`` is the storage boundary
|
||||
// mandated for client-only preferences — the empty model is the SSR/seed
|
||||
// value so ``model`` is always a concrete ``Model`` (never null) here.
|
||||
const [model, setModel] = useLocalStorage<Model>(STORAGE_MODEL_KEY, EMPTY_MODEL)
|
||||
const [model, setModel] = useWorkflowGeneratorModel()
|
||||
|
||||
const { defaultModel } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
||||
|
||||
@ -133,7 +117,7 @@ const WorkflowGeneratorModal: React.FC = () => {
|
||||
useEffect(() => {
|
||||
if (defaultModel && !model.name) {
|
||||
setModel(prev => ({
|
||||
...(prev ?? EMPTY_MODEL),
|
||||
...(prev ?? EMPTY_WORKFLOW_GENERATOR_MODEL),
|
||||
name: defaultModel.model,
|
||||
provider: defaultModel.provider.provider,
|
||||
}))
|
||||
@ -142,7 +126,7 @@ const WorkflowGeneratorModal: React.FC = () => {
|
||||
|
||||
const handleModelChange = useCallback((newValue: { modelId: string, provider: string, mode?: string, features?: string[] }) => {
|
||||
setModel(prev => ({
|
||||
...(prev ?? EMPTY_MODEL),
|
||||
...(prev ?? EMPTY_WORKFLOW_GENERATOR_MODEL),
|
||||
provider: newValue.provider,
|
||||
name: newValue.modelId,
|
||||
mode: newValue.mode as ModelModeType,
|
||||
@ -151,7 +135,7 @@ const WorkflowGeneratorModal: React.FC = () => {
|
||||
|
||||
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
|
||||
setModel(prev => ({
|
||||
...(prev ?? EMPTY_MODEL),
|
||||
...(prev ?? EMPTY_WORKFLOW_GENERATOR_MODEL),
|
||||
completion_params: newParams as CompletionParams,
|
||||
}))
|
||||
}, [setModel])
|
||||
|
||||
20
web/app/components/workflow/workflow-generator/storage.ts
Normal file
20
web/app/components/workflow/workflow-generator/storage.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { CompletionParams, Model } from '@/types/app'
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
import { ModelModeType } from '@/types/app'
|
||||
|
||||
export const EMPTY_WORKFLOW_GENERATOR_MODEL: Model = {
|
||||
name: '',
|
||||
provider: '',
|
||||
mode: ModelModeType.chat,
|
||||
completion_params: {} as CompletionParams,
|
||||
}
|
||||
|
||||
const [
|
||||
useWorkflowGeneratorModel,
|
||||
_useWorkflowGeneratorModelValue,
|
||||
_useSetWorkflowGeneratorModel,
|
||||
] = createLocalStorageState<Model>('workflow-gen-model', EMPTY_WORKFLOW_GENERATOR_MODEL)
|
||||
|
||||
export {
|
||||
useWorkflowGeneratorModel,
|
||||
}
|
||||
@ -1,3 +1,2 @@
|
||||
export const EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION = 'getEducationVerify'
|
||||
export const EDUCATION_VERIFYING_LOCALSTORAGE_ITEM = 'educationVerifying'
|
||||
export const EDUCATION_RE_VERIFY_ACTION = 'educationReVerify'
|
||||
|
||||
@ -8,12 +8,11 @@ import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useEducationDiscount } from '@/app/components/billing/hooks/use-education-discount'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants'
|
||||
import { useSetEducationVerifying } from '@/app/education-apply/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
@ -61,7 +60,7 @@ const EducationApplyAgeContent = () => {
|
||||
const openAsyncWindow = useAsyncWindowOpen()
|
||||
const queryClient = useQueryClient()
|
||||
const switchWorkspaceMutation = useMutation(consoleQuery.workspaces.switch.post.mutationOptions())
|
||||
const setEducationVerifying = useSetLocalStorage<string>(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, { raw: true })
|
||||
const setEducationVerifying = useSetEducationVerifying()
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const token = searchParams.get('token')
|
||||
|
||||
@ -4,13 +4,18 @@ import { useDebounceFn } from 'ahooks'
|
||||
import dayjs from 'dayjs'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import {
|
||||
useEducationExpiredHasNoticed,
|
||||
useEducationReverifyHasNoticed,
|
||||
useEducationReverifyPrevExpireAt,
|
||||
useEducationVerifying,
|
||||
} from '@/app/education-apply/storage'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
@ -19,7 +24,6 @@ import { useEducationAutocomplete, useEducationVerify } from '@/service/use-educ
|
||||
import {
|
||||
EDUCATION_RE_VERIFY_ACTION,
|
||||
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
|
||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||
} from './constants'
|
||||
|
||||
dayjs.extend(utc)
|
||||
@ -89,9 +93,9 @@ const useEducationReverifyNotice = ({
|
||||
// const [educationInfo, setEducationInfo] = useState<{ is_student: boolean, allow_refresh: boolean, expire_at: number | null } | null>(null)
|
||||
// const isLoading = !educationInfo
|
||||
const { educationAccountExpireAt, allowRefreshEducationVerify, isLoadingEducationAccountInfo: isLoading } = useProviderContext()
|
||||
const [prevExpireAt, setPrevExpireAt] = useLocalStorage<number>('education-reverify-prev-expire-at', 0)
|
||||
const [reverifyHasNoticed, setReverifyHasNoticed] = useLocalStorage<boolean>('education-reverify-has-noticed', false)
|
||||
const [expiredHasNoticed, setExpiredHasNoticed] = useLocalStorage<boolean>('education-expired-has-noticed', false)
|
||||
const [prevExpireAt, setPrevExpireAt] = useEducationReverifyPrevExpireAt()
|
||||
const [reverifyHasNoticed, setReverifyHasNoticed] = useEducationReverifyHasNoticed()
|
||||
const [expiredHasNoticed, setExpiredHasNoticed] = useEducationExpiredHasNoticed()
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || !timezone)
|
||||
@ -132,7 +136,7 @@ const useEducationReverifyNotice = ({
|
||||
export const useEducationInit = () => {
|
||||
const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal)
|
||||
const setShowEducationExpireNoticeModal = useModalContextSelector(s => s.setShowEducationExpireNoticeModal)
|
||||
const [educationVerifying, setEducationVerifying] = useLocalStorage<string>(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, 'no', { raw: true })
|
||||
const [educationVerifying, setEducationVerifying] = useEducationVerifying()
|
||||
const searchParams = useSearchParams()
|
||||
const educationVerifyAction = searchParams.get('action')
|
||||
|
||||
|
||||
38
web/app/education-apply/storage.ts
Normal file
38
web/app/education-apply/storage.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
const EDUCATION_VERIFYING_LOCALSTORAGE_ITEM = 'educationVerifying'
|
||||
|
||||
const [
|
||||
useEducationVerifying,
|
||||
_useEducationVerifyingValue,
|
||||
useSetEducationVerifying,
|
||||
] = createLocalStorageState<string>(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, 'no', { raw: true })
|
||||
|
||||
const [
|
||||
useEducationReverifyPrevExpireAt,
|
||||
_useEducationReverifyPrevExpireAtValue,
|
||||
useSetEducationReverifyPrevExpireAt,
|
||||
] = createLocalStorageState<number>('education-reverify-prev-expire-at', 0)
|
||||
|
||||
const [
|
||||
useEducationReverifyHasNoticed,
|
||||
_useEducationReverifyHasNoticedValue,
|
||||
useSetEducationReverifyHasNoticed,
|
||||
] = createLocalStorageState<boolean>('education-reverify-has-noticed', false)
|
||||
|
||||
const [
|
||||
useEducationExpiredHasNoticed,
|
||||
_useEducationExpiredHasNoticedValue,
|
||||
useSetEducationExpiredHasNoticed,
|
||||
] = createLocalStorageState<boolean>('education-expired-has-noticed', false)
|
||||
|
||||
export {
|
||||
useEducationExpiredHasNoticed,
|
||||
useEducationReverifyHasNoticed,
|
||||
useEducationReverifyPrevExpireAt,
|
||||
useEducationVerifying,
|
||||
useSetEducationExpiredHasNoticed,
|
||||
useSetEducationReverifyHasNoticed,
|
||||
useSetEducationReverifyPrevExpireAt,
|
||||
useSetEducationVerifying,
|
||||
}
|
||||
@ -12,7 +12,7 @@ import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import Link from '@/next/link'
|
||||
import { useRouter, useSearchParams } from '@/next/navigation'
|
||||
import { sendResetPasswordCode } from '@/service/common'
|
||||
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '../components/signin/countdown'
|
||||
import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '../components/signin/storage'
|
||||
|
||||
export default function CheckCode() {
|
||||
const { t } = useTranslation()
|
||||
@ -22,6 +22,7 @@ export default function CheckCode() {
|
||||
const [email, setEmail] = useState('')
|
||||
const [loading, setIsLoading] = useState(false)
|
||||
const locale = useLocale()
|
||||
const setCountdownLeftTime = useSetCountdownLeftTime()
|
||||
|
||||
const handleGetEMailVerificationCode = async () => {
|
||||
try {
|
||||
@ -37,7 +38,7 @@ export default function CheckCode() {
|
||||
setIsLoading(true)
|
||||
const res = await sendResetPasswordCode(email, locale)
|
||||
if (res.result === 'success') {
|
||||
localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`)
|
||||
setCountdownLeftTime(`${COUNT_DOWN_TIME_MS}`)
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.set('token', encodeURIComponent(res.data))
|
||||
params.set('email', encodeURIComponent(email))
|
||||
|
||||
@ -2,10 +2,9 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown'
|
||||
import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage'
|
||||
import { emailRegex } from '@/config'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { useRouter, useSearchParams } from '@/next/navigation'
|
||||
@ -23,7 +22,7 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
|
||||
const [email, setEmail] = useState(emailFromLink)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const locale = useLocale()
|
||||
const setCountdownLeftTime = useSetLocalStorage<string>(COUNT_DOWN_KEY, { raw: true })
|
||||
const setCountdownLeftTime = useSetCountdownLeftTime()
|
||||
|
||||
const handleGetEMailVerificationCode = async () => {
|
||||
try {
|
||||
|
||||
@ -180,8 +180,6 @@ export const VAR_ITEM_TEMPLATE_IN_PIPELINE = {
|
||||
|
||||
export const appDefaultIconBackground = '#D5F5F6'
|
||||
|
||||
export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList'
|
||||
|
||||
export const DATASET_DEFAULT = {
|
||||
top_k: 4,
|
||||
score_threshold: 0.8,
|
||||
|
||||
@ -11,7 +11,6 @@ import type { InputVar } from '@/app/components/workflow/types'
|
||||
import type { ExpireNoticeModalPayloadProps } from '@/app/education-apply/expire-notice-modal'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import type { ModerationConfig, PromptVariable } from '@/models/debug'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
DEFAULT_ACCOUNT_SETTING_TAB,
|
||||
@ -20,9 +19,7 @@ import {
|
||||
isValidSettingsTab,
|
||||
isWorkspaceSettingTab,
|
||||
} from '@/app/components/header/account-setting/constants'
|
||||
import {
|
||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||
} from '@/app/education-apply/constants'
|
||||
import { useSetEducationVerifying } from '@/app/education-apply/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
@ -107,7 +104,7 @@ export const ModalContextProvider = ({
|
||||
const [showUpdatePluginModal, setShowUpdatePluginModal] = useState<ModalState<UpdatePluginPayload> | null>(null)
|
||||
const [showEducationExpireNoticeModal, setShowEducationExpireNoticeModal] = useState<ModalState<ExpireNoticeModalPayloadProps> | null>(null)
|
||||
const { currentWorkspace } = useAppContext()
|
||||
const setEducationVerifying = useSetLocalStorage<string>(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, { raw: true })
|
||||
const setEducationVerifying = useSetEducationVerifying()
|
||||
|
||||
const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false)
|
||||
const handleCancelAccountSettingModal = () => {
|
||||
|
||||
@ -38,8 +38,8 @@ vi.mock('@/app/components/header/account-setting', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('foxact/use-local-storage', () => ({
|
||||
useSetLocalStorage: () => mockSetEducationVerifying,
|
||||
vi.mock('@/app/education-apply/storage', () => ({
|
||||
useSetEducationVerifying: () => mockSetEducationVerifying,
|
||||
}))
|
||||
|
||||
const mockUseProviderContext = vi.fn()
|
||||
|
||||
@ -5,7 +5,6 @@ import type { ProviderContextState } from './provider-context'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
|
||||
@ -25,6 +24,7 @@ import {
|
||||
} from '@/service/use-common'
|
||||
import { useEducationStatus } from '@/service/use-education'
|
||||
import { ProviderContext } from './provider-context'
|
||||
import { useAnthropicQuotaNotice } from './provider-storage'
|
||||
|
||||
type ProviderContextProviderProps = {
|
||||
children: ReactNode
|
||||
@ -40,8 +40,6 @@ const unlimitedMemberInviteLimit: MemberInviteLimit = {
|
||||
limit: 0,
|
||||
}
|
||||
|
||||
const ANTHROPIC_QUOTA_NOTICE_STORAGE_KEY = 'anthropic_quota_notice'
|
||||
|
||||
const resolveMemberInviteLimit = (data: Awaited<ReturnType<typeof fetchCurrentPlanInfo>>): MemberInviteLimit => {
|
||||
if (!data)
|
||||
return unlimitedMemberInviteLimit
|
||||
@ -158,11 +156,7 @@ export const ProviderContextProvider = ({
|
||||
// #endregion Zendesk conversation fields
|
||||
|
||||
const { t } = useTranslation()
|
||||
const [anthropicQuotaNotice, setAnthropicQuotaNotice] = useLocalStorage<string>(
|
||||
ANTHROPIC_QUOTA_NOTICE_STORAGE_KEY,
|
||||
'false',
|
||||
{ raw: true },
|
||||
)
|
||||
const [anthropicQuotaNotice, setAnthropicQuotaNotice] = useAnthropicQuotaNotice()
|
||||
|
||||
useEffect(() => {
|
||||
if (anthropicQuotaNotice === 'true')
|
||||
|
||||
13
web/context/provider-storage.ts
Normal file
13
web/context/provider-storage.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createLocalStorageState } from 'foxact/create-local-storage-state'
|
||||
|
||||
const ANTHROPIC_QUOTA_NOTICE_STORAGE_KEY = 'anthropic_quota_notice'
|
||||
|
||||
const [
|
||||
useAnthropicQuotaNotice,
|
||||
_useAnthropicQuotaNoticeValue,
|
||||
_useSetAnthropicQuotaNotice,
|
||||
] = createLocalStorageState<string>(ANTHROPIC_QUOTA_NOTICE_STORAGE_KEY, 'false', { raw: true })
|
||||
|
||||
export {
|
||||
useAnthropicQuotaNotice,
|
||||
}
|
||||
@ -197,7 +197,7 @@ export default antfu(
|
||||
'error',
|
||||
{
|
||||
name: 'localStorage',
|
||||
message: 'Do not use localStorage directly. Use foxact/use-local-storage instead.',
|
||||
message: 'Do not use localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
|
||||
},
|
||||
],
|
||||
'no-restricted-properties': [
|
||||
@ -205,19 +205,19 @@ export default antfu(
|
||||
{
|
||||
object: 'window',
|
||||
property: 'localStorage',
|
||||
message: 'Do not use window.localStorage directly. Use foxact/use-local-storage instead.',
|
||||
message: 'Do not use window.localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
|
||||
},
|
||||
{
|
||||
object: 'globalThis',
|
||||
property: 'localStorage',
|
||||
message: 'Do not use globalThis.localStorage directly. Use foxact/use-local-storage instead.',
|
||||
message: 'Do not use globalThis.localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'ImportDeclaration[source.value="ahooks"] ImportSpecifier[imported.name="useLocalStorageState"]',
|
||||
message: 'Do not use ahooks useLocalStorageState. Use foxact/use-local-storage instead.',
|
||||
message: 'Do not use ahooks useLocalStorageState. Use foxact storage hooks instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -4,15 +4,14 @@ import type {
|
||||
} from '@/models/app'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useSetLocalStorage } from 'foxact/use-local-storage'
|
||||
import {
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { DSLImportStatus } from '@/models/app'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import {
|
||||
@ -45,7 +44,7 @@ export const useImportDSL = () => {
|
||||
const invalidateAppList = useInvalidateAppList()
|
||||
const [versions, setVersions] = useState<{ importedVersion: string, systemVersion: string }>()
|
||||
const importIdRef = useRef<string>('')
|
||||
const setNeedRefresh = useSetLocalStorage<string>(NEED_REFRESH_APP_LIST_KEY, { raw: true })
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
|
||||
const handleImportDSL = useCallback(async (
|
||||
payload: DSLPayload,
|
||||
@ -114,7 +113,7 @@ export const useImportDSL = () => {
|
||||
finally {
|
||||
setIsFetching(false)
|
||||
}
|
||||
}, [isFetching, t, setNeedRefresh, handleCheckPluginDependencies, push, setNeedRefresh, invalidateAppList])
|
||||
}, [isFetching, t, handleCheckPluginDependencies, push, setNeedRefresh, invalidateAppList])
|
||||
|
||||
const handleImportDSLConfirm = useCallback(async (
|
||||
{
|
||||
@ -157,7 +156,7 @@ export const useImportDSL = () => {
|
||||
finally {
|
||||
setIsFetching(false)
|
||||
}
|
||||
}, [isFetching, t, handleCheckPluginDependencies, setNeedRefresh, push, setNeedRefresh, invalidateAppList])
|
||||
}, [isFetching, t, handleCheckPluginDependencies, setNeedRefresh, push, invalidateAppList])
|
||||
|
||||
return {
|
||||
handleImportDSL,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user