diff --git a/web/app/account/account-page/index.module.css b/web/app/account/account-page/index.module.css
new file mode 100644
index 0000000000..949d1257e9
--- /dev/null
+++ b/web/app/account/account-page/index.module.css
@@ -0,0 +1,9 @@
+.modal {
+ padding: 24px 32px !important;
+ width: 400px !important;
+}
+
+.bg {
+ background: linear-gradient(180deg, rgba(217, 45, 32, 0.05) 0%, rgba(217, 45, 32, 0.00) 24.02%), #F9FAFB;
+}
+
diff --git a/web/app/account/account-page/index.tsx b/web/app/account/account-page/index.tsx
new file mode 100644
index 0000000000..53f7692e6c
--- /dev/null
+++ b/web/app/account/account-page/index.tsx
@@ -0,0 +1,304 @@
+'use client'
+import { useState } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { useContext } from 'use-context-selector'
+import s from './index.module.css'
+import Collapse from '@/app/components/header/account-setting/collapse'
+import type { IItem } from '@/app/components/header/account-setting/collapse'
+import Modal from '@/app/components/base/modal'
+import Confirm from '@/app/components/base/confirm'
+import Button from '@/app/components/base/button'
+import { updateUserProfile } from '@/service/common'
+import { useAppContext } from '@/context/app-context'
+import { ToastContext } from '@/app/components/base/toast'
+import AppIcon from '@/app/components/base/app-icon'
+import Avatar from '@/app/components/base/avatar'
+import { IS_CE_EDITION } from '@/config'
+
+const titleClassName = `
+ text-sm font-medium text-gray-900
+`
+const descriptionClassName = `
+ mt-1 text-xs font-normal text-gray-500
+`
+const inputClassName = `
+ mt-2 w-full px-3 py-2 bg-gray-100 rounded
+ text-sm font-normal text-gray-800
+`
+
+const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
+
+export default function AccountPage() {
+ const { t } = useTranslation()
+ const { mutateUserProfile, userProfile, apps } = useAppContext()
+ const { notify } = useContext(ToastContext)
+ const [editNameModalVisible, setEditNameModalVisible] = useState(false)
+ const [editName, setEditName] = useState('')
+ const [editing, setEditing] = useState(false)
+ const [editPasswordModalVisible, setEditPasswordModalVisible] = useState(false)
+ const [currentPassword, setCurrentPassword] = useState('')
+ const [password, setPassword] = useState('')
+ const [confirmPassword, setConfirmPassword] = useState('')
+ const [showDeleteAccountModal, setShowDeleteAccountModal] = useState(false)
+
+ const handleEditName = () => {
+ setEditNameModalVisible(true)
+ setEditName(userProfile.name)
+ }
+ const handleSaveName = async () => {
+ try {
+ setEditing(true)
+ await updateUserProfile({ url: 'account/name', body: { name: editName } })
+ notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
+ mutateUserProfile()
+ setEditNameModalVisible(false)
+ setEditing(false)
+ }
+ catch (e) {
+ notify({ type: 'error', message: (e as Error).message })
+ setEditNameModalVisible(false)
+ setEditing(false)
+ }
+ }
+
+ const showErrorMessage = (message: string) => {
+ notify({
+ type: 'error',
+ message,
+ })
+ }
+ const valid = () => {
+ if (!password.trim()) {
+ showErrorMessage(t('login.error.passwordEmpty'))
+ return false
+ }
+ if (!validPassword.test(password)) {
+ showErrorMessage(t('login.error.passwordInvalid'))
+ return false
+ }
+ if (password !== confirmPassword) {
+ showErrorMessage(t('common.account.notEqual'))
+ return false
+ }
+
+ return true
+ }
+ const resetPasswordForm = () => {
+ setCurrentPassword('')
+ setPassword('')
+ setConfirmPassword('')
+ }
+ const handleSavePassword = async () => {
+ if (!valid())
+ return
+ try {
+ setEditing(true)
+ await updateUserProfile({
+ url: 'account/password',
+ body: {
+ password: currentPassword,
+ new_password: password,
+ repeat_new_password: confirmPassword,
+ },
+ })
+ notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
+ mutateUserProfile()
+ setEditPasswordModalVisible(false)
+ resetPasswordForm()
+ setEditing(false)
+ }
+ catch (e) {
+ notify({ type: 'error', message: (e as Error).message })
+ setEditPasswordModalVisible(false)
+ setEditing(false)
+ }
+ }
+
+ const renderAppItem = (item: IItem) => {
+ return (
+
+ )
+ }
+
+ return (
+ <>
+
+
{t('common.account.myAccount')}
+
+
+
+
+
{userProfile.name}
+
{userProfile.email}
+
+
+
+
{t('common.account.name')}
+
+
+ {userProfile.name}
+
+
+ {t('common.operation.edit')}
+
+
+
+
+
{t('common.account.email')}
+
+
+ {userProfile.email}
+
+
+
+ {
+ IS_CE_EDITION && (
+
+
+
{t('common.account.password')}
+
{t('common.account.passwordTip')}
+
+
+
+ )
+ }
+
+
+
{t('common.account.langGeniusAccount')}
+
{t('common.account.langGeniusAccountTip')}
+ {!!apps.length && (
+
({ key: app.id, name: app.name }))}
+ renderItem={renderAppItem}
+ wrapperClassName='mt-2'
+ />
+ )}
+ {!IS_CE_EDITION && }
+
+ {
+ editNameModalVisible && (
+ setEditNameModalVisible(false)}
+ className={s.modal}
+ >
+ {t('common.account.editName')}
+ {t('common.account.name')}
+ setEditName(e.target.value)}
+ />
+
+
+
+
+
+ )
+ }
+ {
+ editPasswordModalVisible && (
+ {
+ setEditPasswordModalVisible(false)
+ resetPasswordForm()
+ }}
+ className={s.modal}
+ >
+ {userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}
+ {userProfile.is_password_set && (
+ <>
+ {t('common.account.currentPassword')}
+ setCurrentPassword(e.target.value)}
+ />
+ >
+ )}
+
+ {userProfile.is_password_set ? t('common.account.newPassword') : t('common.account.password')}
+
+ setPassword(e.target.value)}
+ />
+ {t('common.account.confirmPassword')}
+ setConfirmPassword(e.target.value)}
+ />
+
+
+
+
+
+ )
+ }
+ {
+ showDeleteAccountModal && (
+ setShowDeleteAccountModal(false)}
+ onConfirm={() => setShowDeleteAccountModal(false)}
+ showCancel={false}
+ type='warning'
+ title={t('common.account.delete')}
+ content={
+ <>
+
+ {t('common.account.deleteTip')}
+
+
+ {`${t('common.account.delete')}: ${userProfile.email}`}
+ >
+ }
+ confirmText={t('common.operation.ok') as string}
+ />
+ )
+ }
+ >
+ )
+}
diff --git a/web/app/account/avatar.tsx b/web/app/account/avatar.tsx
new file mode 100644
index 0000000000..29bd0cb5a5
--- /dev/null
+++ b/web/app/account/avatar.tsx
@@ -0,0 +1,94 @@
+'use client'
+import { useTranslation } from 'react-i18next'
+import { Fragment } from 'react'
+import { useRouter } from 'next/navigation'
+import { Menu, Transition } from '@headlessui/react'
+import Avatar from '@/app/components/base/avatar'
+import { logout } from '@/service/common'
+import { useAppContext } from '@/context/app-context'
+import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
+
+export type IAppSelector = {
+ isMobile: boolean
+}
+
+export default function AppSelector() {
+ const router = useRouter()
+ const { t } = useTranslation()
+ const { userProfile } = useAppContext()
+
+ const handleLogout = async () => {
+ await logout({
+ url: '/logout',
+ params: {},
+ })
+
+ if (localStorage?.getItem('console_token'))
+ localStorage.removeItem('console_token')
+
+ router.push('/signin')
+ }
+
+ return (
+
+ )
+}
diff --git a/web/app/account/header.tsx b/web/app/account/header.tsx
new file mode 100644
index 0000000000..694533e5ab
--- /dev/null
+++ b/web/app/account/header.tsx
@@ -0,0 +1,37 @@
+'use client'
+import { useTranslation } from 'react-i18next'
+import { RiArrowRightUpLine, RiRobot2Line } from '@remixicon/react'
+import { useRouter } from 'next/navigation'
+import Button from '../components/base/button'
+import Avatar from './avatar'
+import LogoSite from '@/app/components/base/logo/logo-site'
+
+const Header = () => {
+ const { t } = useTranslation()
+ const router = useRouter()
+
+ const back = () => {
+ router.back()
+ }
+ return (
+
+
+
+
+
+
+
{t('common.account.account')}
+
+
+
+
+
+
+
+ )
+}
+export default Header
diff --git a/web/app/account/layout.tsx b/web/app/account/layout.tsx
new file mode 100644
index 0000000000..5aa8b05cbf
--- /dev/null
+++ b/web/app/account/layout.tsx
@@ -0,0 +1,40 @@
+import React from 'react'
+import type { ReactNode } from 'react'
+import Header from './header'
+import SwrInitor from '@/app/components/swr-initor'
+import { AppContextProvider } from '@/context/app-context'
+import GA, { GaType } from '@/app/components/base/ga'
+import HeaderWrapper from '@/app/components/header/header-wrapper'
+import { EventEmitterContextProvider } from '@/context/event-emitter'
+import { ProviderContextProvider } from '@/context/provider-context'
+import { ModalContextProvider } from '@/context/modal-context'
+
+const Layout = ({ children }: { children: ReactNode }) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+ >
+ )
+}
+
+export const metadata = {
+ title: 'Dify',
+}
+
+export default Layout
diff --git a/web/app/account/page.tsx b/web/app/account/page.tsx
new file mode 100644
index 0000000000..bb7e7f7feb
--- /dev/null
+++ b/web/app/account/page.tsx
@@ -0,0 +1,7 @@
+import AccountPage from './account-page'
+
+export default function Account() {
+ return
+}
diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx
index 03157ed7cb..712906ebae 100644
--- a/web/app/components/header/account-dropdown/index.tsx
+++ b/web/app/components/header/account-dropdown/index.tsx
@@ -107,7 +107,16 @@ export default function AppSelector({ isMobile }: IAppSelector) {
- setShowAccountSettingModal({ payload: 'account' })}>
+
+
{t('common.account.account')}
+
+
+
+
+ setShowAccountSettingModal({ payload: 'members' })}>
{t('common.userProfile.settings')}
diff --git a/web/app/components/header/account-setting/index.tsx b/web/app/components/header/account-setting/index.tsx
index 253b9f1b4c..d829f6b77b 100644
--- a/web/app/components/header/account-setting/index.tsx
+++ b/web/app/components/header/account-setting/index.tsx
@@ -2,10 +2,6 @@
import { useTranslation } from 'react-i18next'
import { useEffect, useRef, useState } from 'react'
import {
- RiAccountCircleFill,
- RiAccountCircleLine,
- RiApps2AddFill,
- RiApps2AddLine,
RiBox3Fill,
RiBox3Line,
RiCloseLine,
@@ -21,9 +17,7 @@ import {
RiPuzzle2Line,
RiTranslate2,
} from '@remixicon/react'
-import AccountPage from './account-page'
import MembersPage from './members-page'
-import IntegrationsPage from './Integrations-page'
import LanguagePage from './language-page'
import ApiBasedExtensionPage from './api-based-extension-page'
import DataSourcePage from './data-source-page'
@@ -60,7 +54,7 @@ type GroupItem = {
export default function AccountSetting({
onCancel,
- activeTab = 'account',
+ activeTab = 'members',
}: IAccountSettingProps) {
const [activeMenu, setActiveMenu] = useState(activeTab)
const { t } = useTranslation()
@@ -125,18 +119,6 @@ export default function AccountSetting({
key: 'account-group',
name: t('common.settings.accountGroup'),
items: [
- {
- key: 'account',
- name: t('common.settings.account'),
- icon:
,
- activeIcon:
,
- },
- {
- key: 'integrations',
- name: t('common.settings.integrations'),
- icon:
,
- activeIcon:
,
- },
{
key: 'language',
name: t('common.settings.language'),
@@ -217,10 +199,8 @@ export default function AccountSetting({
- {activeMenu === 'account' &&
}
{activeMenu === 'members' &&
}
{activeMenu === 'billing' &&
}
- {activeMenu === 'integrations' &&
}
{activeMenu === 'language' &&
}
{activeMenu === 'provider' &&
}
{activeMenu === 'data-source' &&
}
diff --git a/web/app/components/header/header-wrapper.tsx b/web/app/components/header/header-wrapper.tsx
index 205a379a90..360cf8e560 100644
--- a/web/app/components/header/header-wrapper.tsx
+++ b/web/app/components/header/header-wrapper.tsx
@@ -11,7 +11,7 @@ const HeaderWrapper = ({
children,
}: HeaderWrapperProps) => {
const pathname = usePathname()
- const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools'].includes(pathname)
+ const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools', '/account'].includes(pathname)
return (