| from functools import wraps | from functools import wraps | ||||
| from typing import Optional | from typing import Optional | ||||
| from flask import request | |||||
| from flask import current_app, request | |||||
| from flask_login import user_logged_in | |||||
| from flask_restful import reqparse | from flask_restful import reqparse | ||||
| from pydantic import BaseModel | from pydantic import BaseModel | ||||
| from sqlalchemy.orm import Session | from sqlalchemy.orm import Session | ||||
| from extensions.ext_database import db | from extensions.ext_database import db | ||||
| from libs.login import _get_user | |||||
| from models.account import Account, Tenant | from models.account import Account, Tenant | ||||
| from models.model import EndUser | from models.model import EndUser | ||||
| from services.account_service import AccountService | from services.account_service import AccountService | ||||
| raise ValueError("tenant not found") | raise ValueError("tenant not found") | ||||
| kwargs["tenant_model"] = tenant_model | kwargs["tenant_model"] = tenant_model | ||||
| kwargs["user_model"] = get_user(tenant_id, user_id) | |||||
| user = get_user(tenant_id, user_id) | |||||
| kwargs["user_model"] = user | |||||
| current_app.login_manager._update_request_context_with_user(user) # type: ignore | |||||
| user_logged_in.send(current_app._get_current_object(), user=_get_user()) # type: ignore | |||||
| return view_func(*args, **kwargs) | return view_func(*args, **kwargs) | ||||
| agent_thought: Optional[MessageAgentThought] = ( | agent_thought: Optional[MessageAgentThought] = ( | ||||
| db.session.query(MessageAgentThought).filter(MessageAgentThought.id == event.agent_thought_id).first() | db.session.query(MessageAgentThought).filter(MessageAgentThought.id == event.agent_thought_id).first() | ||||
| ) | ) | ||||
| db.session.refresh(agent_thought) | |||||
| db.session.close() | |||||
| if agent_thought: | if agent_thought: | ||||
| return AgentThoughtStreamResponse( | return AgentThoughtStreamResponse( | 
| def _remove_unsupported_model_features_for_old_version(self, model_schema: AIModelEntity) -> AIModelEntity: | def _remove_unsupported_model_features_for_old_version(self, model_schema: AIModelEntity) -> AIModelEntity: | ||||
| if model_schema.features: | if model_schema.features: | ||||
| for feature in model_schema.features: | |||||
| if feature.value not in AgentOldVersionModelFeatures: | |||||
| for feature in model_schema.features[:]: # Create a copy to safely modify during iteration | |||||
| try: | |||||
| AgentOldVersionModelFeatures(feature.value) # Try to create enum member from value | |||||
| except ValueError: | |||||
| model_schema.features.remove(feature) | model_schema.features.remove(feature) | ||||
| return model_schema | return model_schema | 
| from enum import Enum | |||||
| from enum import Enum, StrEnum | |||||
| from typing import Any, Literal, Union | from typing import Any, Literal, Union | ||||
| from pydantic import BaseModel | from pydantic import BaseModel | ||||
| OPEN = 1 | OPEN = 1 | ||||
| class AgentOldVersionModelFeatures(Enum): | |||||
| class AgentOldVersionModelFeatures(StrEnum): | |||||
| """ | """ | ||||
| Enum class for old SDK version llm feature. | Enum class for old SDK version llm feature. | ||||
| """ | """ | 
| files[key].append(file_tuple) | files[key].append(file_tuple) | ||||
| # convert files to list for httpx request | # convert files to list for httpx request | ||||
| # If there are no actual files, we still need to force httpx to use `multipart/form-data`. | |||||
| # This is achieved by inserting a harmless placeholder file that will be ignored by the server. | |||||
| if not files: | |||||
| self.files = [("__multipart_placeholder__", ("", b"", "application/octet-stream"))] | |||||
| if files: | if files: | ||||
| self.files = [] | self.files = [] | ||||
| for key, file_tuples in files.items(): | for key, file_tuples in files.items(): | ||||
| raw += f"{k}: {v}\r\n" | raw += f"{k}: {v}\r\n" | ||||
| body_string = "" | body_string = "" | ||||
| if self.files: | |||||
| # Only log actual files if present. | |||||
| # '__multipart_placeholder__' is inserted to force multipart encoding but is not a real file. | |||||
| # This prevents logging meaningless placeholder entries. | |||||
| if self.files and not all(f[0] == "__multipart_placeholder__" for f in self.files): | |||||
| for key, (filename, content, mime_type) in self.files: | for key, (filename, content, mime_type) in self.files: | ||||
| body_string += f"--{boundary}\r\n" | body_string += f"--{boundary}\r\n" | ||||
| body_string += f'Content-Disposition: form-data; name="{key}"\r\n\r\n' | body_string += f'Content-Disposition: form-data; name="{key}"\r\n\r\n' | 
| "types-tqdm~=4.67.0", | "types-tqdm~=4.67.0", | ||||
| "types-ujson~=5.10.0", | "types-ujson~=5.10.0", | ||||
| "boto3-stubs>=1.38.20", | "boto3-stubs>=1.38.20", | ||||
| "types-jmespath>=1.0.2.20240106", | |||||
| ] | ] | ||||
| ############################################################ | ############################################################ | 
| assert "multipart/form-data" in executor.headers["Content-Type"] | assert "multipart/form-data" in executor.headers["Content-Type"] | ||||
| assert executor.params == [] | assert executor.params == [] | ||||
| assert executor.json is None | assert executor.json is None | ||||
| assert executor.files is None | |||||
| # '__multipart_placeholder__' is expected when no file inputs exist, | |||||
| # to ensure the request is treated as multipart/form-data by the backend. | |||||
| assert executor.files == [("__multipart_placeholder__", ("", b"", "application/octet-stream"))] | |||||
| assert executor.content is None | assert executor.content is None | ||||
| # Check that the form data is correctly loaded in executor.data | # Check that the form data is correctly loaded in executor.data | 
| { name = "types-gevent" }, | { name = "types-gevent" }, | ||||
| { name = "types-greenlet" }, | { name = "types-greenlet" }, | ||||
| { name = "types-html5lib" }, | { name = "types-html5lib" }, | ||||
| { name = "types-jmespath" }, | |||||
| { name = "types-jsonschema" }, | { name = "types-jsonschema" }, | ||||
| { name = "types-markdown" }, | { name = "types-markdown" }, | ||||
| { name = "types-oauthlib" }, | { name = "types-oauthlib" }, | ||||
| { name = "types-gevent", specifier = "~=24.11.0" }, | { name = "types-gevent", specifier = "~=24.11.0" }, | ||||
| { name = "types-greenlet", specifier = "~=3.1.0" }, | { name = "types-greenlet", specifier = "~=3.1.0" }, | ||||
| { name = "types-html5lib", specifier = "~=1.1.11" }, | { name = "types-html5lib", specifier = "~=1.1.11" }, | ||||
| { name = "types-jmespath", specifier = ">=1.0.2.20240106" }, | |||||
| { name = "types-jsonschema", specifier = "~=4.23.0" }, | { name = "types-jsonschema", specifier = "~=4.23.0" }, | ||||
| { name = "types-markdown", specifier = "~=3.7.0" }, | { name = "types-markdown", specifier = "~=3.7.0" }, | ||||
| { name = "types-oauthlib", specifier = "~=3.2.0" }, | { name = "types-oauthlib", specifier = "~=3.2.0" }, | ||||
| { url = "https://files.pythonhosted.org/packages/ba/7c/f862b1dc31268ef10fe95b43dcdf216ba21a592fafa2d124445cd6b92e93/types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403", size = 17292 }, | { url = "https://files.pythonhosted.org/packages/ba/7c/f862b1dc31268ef10fe95b43dcdf216ba21a592fafa2d124445cd6b92e93/types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403", size = 17292 }, | ||||
| ] | ] | ||||
| [[package]] | |||||
| name = "types-jmespath" | |||||
| version = "1.0.2.20240106" | |||||
| source = { registry = "https://pypi.org/simple" } | |||||
| sdist = { url = "https://files.pythonhosted.org/packages/1b/e4/1f7414dbca03975f66f1ab1b3f7b3deb7c19b104ef14dd3c99036bbc39b2/types-jmespath-1.0.2.20240106.tar.gz", hash = "sha256:b4a65a116bfc1c700a4fd9d24e2e397f4a431122e0320a77b7f1989a6b5d819e", size = 5071 } | |||||
| wheels = [ | |||||
| { url = "https://files.pythonhosted.org/packages/f5/30/3d6443f782601dd88820ba31e7668abfec7e19d685ac7f6fbcfd6ebba519/types_jmespath-1.0.2.20240106-py3-none-any.whl", hash = "sha256:c3e715fcaae9e5f8d74e14328fdedc4f2b3f0e18df17f3e457ae0a18e245bde0", size = 6087 }, | |||||
| ] | |||||
| [[package]] | [[package]] | ||||
| name = "types-jsonschema" | name = "types-jsonschema" | ||||
| version = "4.23.0.20241208" | version = "4.23.0.20241208" | 
| import Avatar from './avatar' | import Avatar from './avatar' | ||||
| import DifyLogo from '@/app/components/base/logo/dify-logo' | import DifyLogo from '@/app/components/base/logo/dify-logo' | ||||
| import { useCallback } from 'react' | import { useCallback } from 'react' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| const Header = () => { | const Header = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const router = useRouter() | const router = useRouter() | ||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| const back = useCallback(() => { | const back = useCallback(() => { | ||||
| router.back() | router.back() | ||||
| <div className='flex flex-1 items-center justify-between px-4'> | <div className='flex flex-1 items-center justify-between px-4'> | ||||
| <div className='flex items-center gap-3'> | <div className='flex items-center gap-3'> | ||||
| <div className='flex cursor-pointer items-center' onClick={back}> | <div className='flex cursor-pointer items-center' onClick={back}> | ||||
| <DifyLogo /> | |||||
| {systemFeatures.branding.enabled && systemFeatures.branding.login_page_logo | |||||
| ? <img | |||||
| src={systemFeatures.branding.login_page_logo} | |||||
| className='block h-[22px] w-auto object-contain' | |||||
| alt='Dify logo' | |||||
| /> | |||||
| : <DifyLogo />} | |||||
| </div> | </div> | ||||
| <div className='h-4 w-[1px] origin-center rotate-[11.31deg] bg-divider-regular' /> | <div className='h-4 w-[1px] origin-center rotate-[11.31deg] bg-divider-regular' /> | ||||
| <p className='title-3xl-semi-bold relative mt-[-2px] text-text-primary'>{t('common.account.account')}</p> | <p className='title-3xl-semi-bold relative mt-[-2px] text-text-primary'>{t('common.account.account')}</p> | 
| 'flex shrink-0 items-center gap-1.5 px-1', | 'flex shrink-0 items-center gap-1.5 px-1', | ||||
| )}> | )}> | ||||
| <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | ||||
| {systemFeatures.branding.enabled ? ( | |||||
| <img src={systemFeatures.branding.login_page_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| ) : ( | |||||
| <DifyLogo size='small' />) | |||||
| { | |||||
| systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| : appData?.custom_config?.replace_webapp_logo | |||||
| ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| } | } | ||||
| </div> | </div> | ||||
| )} | )} | 
| import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown' | import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown' | ||||
| import DifyLogo from '@/app/components/base/logo/dify-logo' | import DifyLogo from '@/app/components/base/logo/dify-logo' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| export type IHeaderProps = { | export type IHeaderProps = { | ||||
| isMobile?: boolean | isMobile?: boolean | ||||
| const [parentOrigin, setParentOrigin] = useState('') | const [parentOrigin, setParentOrigin] = useState('') | ||||
| const [showToggleExpandButton, setShowToggleExpandButton] = useState(false) | const [showToggleExpandButton, setShowToggleExpandButton] = useState(false) | ||||
| const [expanded, setExpanded] = useState(false) | const [expanded, setExpanded] = useState(false) | ||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| const handleMessageReceived = useCallback((event: MessageEvent) => { | const handleMessageReceived = useCallback((event: MessageEvent) => { | ||||
| let currentParentOrigin = parentOrigin | let currentParentOrigin = parentOrigin | ||||
| 'flex shrink-0 items-center gap-1.5 px-2', | 'flex shrink-0 items-center gap-1.5 px-2', | ||||
| )}> | )}> | ||||
| <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | ||||
| {appData?.custom_config?.replace_webapp_logo && ( | |||||
| <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| )} | |||||
| {!appData?.custom_config?.replace_webapp_logo && ( | |||||
| <DifyLogo size='small' /> | |||||
| )} | |||||
| { | |||||
| systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| : appData?.custom_config?.replace_webapp_logo | |||||
| ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| } | |||||
| </div> | </div> | ||||
| )} | )} | ||||
| </div> | </div> | 
| import DifyLogo from '@/app/components/base/logo/dify-logo' | import DifyLogo from '@/app/components/base/logo/dify-logo' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import useDocumentTitle from '@/hooks/use-document-title' | import useDocumentTitle from '@/hooks/use-document-title' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| const Chatbot = () => { | const Chatbot = () => { | ||||
| const { | const { | ||||
| themeBuilder, | themeBuilder, | ||||
| } = useEmbeddedChatbotContext() | } = useEmbeddedChatbotContext() | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| const customConfig = appData?.custom_config | const customConfig = appData?.custom_config | ||||
| const site = appData?.site | const site = appData?.site | ||||
| 'flex shrink-0 items-center gap-1.5 px-2', | 'flex shrink-0 items-center gap-1.5 px-2', | ||||
| )}> | )}> | ||||
| <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | ||||
| {appData?.custom_config?.replace_webapp_logo && ( | |||||
| <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| )} | |||||
| {!appData?.custom_config?.replace_webapp_logo && ( | |||||
| <DifyLogo size='small' /> | |||||
| )} | |||||
| { | |||||
| systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| : appData?.custom_config?.replace_webapp_logo | |||||
| ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| } | |||||
| </div> | </div> | ||||
| )} | )} | ||||
| </div> | </div> | 
| import classNames from '@/utils/classnames' | import classNames from '@/utils/classnames' | ||||
| import useTheme from '@/hooks/use-theme' | import useTheme from '@/hooks/use-theme' | ||||
| import { basePath } from '@/utils/var' | import { basePath } from '@/utils/var' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| export type LogoStyle = 'default' | 'monochromeWhite' | export type LogoStyle = 'default' | 'monochromeWhite' | ||||
| export const logoPathMap: Record<LogoStyle, string> = { | export const logoPathMap: Record<LogoStyle, string> = { | ||||
| }) => { | }) => { | ||||
| const { theme } = useTheme() | const { theme } = useTheme() | ||||
| const themedStyle = (theme === 'dark' && style === 'default') ? 'monochromeWhite' : style | const themedStyle = (theme === 'dark' && style === 'default') ? 'monochromeWhite' : style | ||||
| const { systemFeatures } = useGlobalPublicStore() | |||||
| const hasBrandingLogo = Boolean(systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo) | |||||
| let src = `${basePath}${logoPathMap[themedStyle]}` | |||||
| if (hasBrandingLogo) | |||||
| src = systemFeatures.branding.workspace_logo | |||||
| return ( | return ( | ||||
| <img | <img | ||||
| src={src} | |||||
| className={classNames('block object-contain', logoSizeMap[size], hasBrandingLogo && 'w-auto', className)} | |||||
| alt={hasBrandingLogo ? 'Logo' : 'Dify logo'} | |||||
| src={`${basePath}${logoPathMap[themedStyle]}`} | |||||
| className={classNames('block object-contain', logoSizeMap[size], className)} | |||||
| alt='Dify logo' | |||||
| /> | /> | ||||
| ) | ) | ||||
| } | } | 
| const newIndex = options.findIndex(option => option.value === value) | const newIndex = options.findIndex(option => option.value === value) | ||||
| setActiveIndex(newIndex) | setActiveIndex(newIndex) | ||||
| updateSliderStyle(newIndex) | updateSliderStyle(newIndex) | ||||
| }, [value, options, pluginList]) | |||||
| }, [value, options, pluginList?.total]) | |||||
| return ( | return ( | ||||
| <div className={cn(className, 'relative inline-flex items-center justify-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5')}> | <div className={cn(className, 'relative inline-flex items-center justify-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5')}> | ||||
| {option.text} | {option.text} | ||||
| {/* if no plugin installed, the badge won't show */} | {/* if no plugin installed, the badge won't show */} | ||||
| {option.value === 'plugins' | {option.value === 'plugins' | ||||
| && (pluginList?.plugins.length ?? 0) > 0 | |||||
| && (pluginList?.total ?? 0) > 0 | |||||
| && <Badge | && <Badge | ||||
| size='s' | size='s' | ||||
| uppercase={true} | uppercase={true} | ||||
| state={BadgeState.Default} | state={BadgeState.Default} | ||||
| > | > | ||||
| {pluginList?.plugins.length} | |||||
| {pluginList?.total} | |||||
| </Badge> | </Badge> | ||||
| } | } | ||||
| </div> | </div> | 
| } from '@/service/common' | } from '@/service/common' | ||||
| import { useAppContext } from '@/context/app-context' | import { useAppContext } from '@/context/app-context' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| const ALLOW_FILE_EXTENSIONS = ['svg', 'png'] | const ALLOW_FILE_EXTENSIONS = ['svg', 'png'] | ||||
| const [fileId, setFileId] = useState('') | const [fileId, setFileId] = useState('') | ||||
| const [imgKey, setImgKey] = useState(Date.now()) | const [imgKey, setImgKey] = useState(Date.now()) | ||||
| const [uploadProgress, setUploadProgress] = useState(0) | const [uploadProgress, setUploadProgress] = useState(0) | ||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| const isSandbox = enableBilling && plan.type === Plan.sandbox | const isSandbox = enableBilling && plan.type === Plan.sandbox | ||||
| const uploading = uploadProgress > 0 && uploadProgress < 100 | const uploading = uploadProgress > 0 && uploadProgress < 100 | ||||
| const webappLogo = currentWorkspace.custom_config?.replace_webapp_logo || '' | const webappLogo = currentWorkspace.custom_config?.replace_webapp_logo || '' | ||||
| {!webappBrandRemoved && ( | {!webappBrandRemoved && ( | ||||
| <> | <> | ||||
| <div className='system-2xs-medium-uppercase text-text-tertiary'>POWERED BY</div> | <div className='system-2xs-medium-uppercase text-text-tertiary'>POWERED BY</div> | ||||
| {webappLogo | |||||
| ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| { | |||||
| systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| : webappLogo | |||||
| ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| } | } | ||||
| </> | </> | ||||
| )} | )} | ||||
| {!webappBrandRemoved && ( | {!webappBrandRemoved && ( | ||||
| <> | <> | ||||
| <div className='system-2xs-medium-uppercase text-text-tertiary'>POWERED BY</div> | <div className='system-2xs-medium-uppercase text-text-tertiary'>POWERED BY</div> | ||||
| {webappLogo | |||||
| ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| { | |||||
| systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| : webappLogo | |||||
| ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| } | } | ||||
| </> | </> | ||||
| )} | )} | 
| import { IS_CE_EDITION } from '@/config' | import { IS_CE_EDITION } from '@/config' | ||||
| import DifyLogo from '@/app/components/base/logo/dify-logo' | import DifyLogo from '@/app/components/base/logo/dify-logo' | ||||
| import { noop } from 'lodash-es' | import { noop } from 'lodash-es' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| type IAccountSettingProps = { | type IAccountSettingProps = { | ||||
| langeniusVersionInfo: LangGeniusVersionResponse | langeniusVersionInfo: LangGeniusVersionResponse | ||||
| }: IAccountSettingProps) { | }: IAccountSettingProps) { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version | const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version | ||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| return ( | return ( | ||||
| <Modal | <Modal | ||||
| <RiCloseLine className='h-4 w-4 text-text-tertiary' /> | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> | ||||
| </div> | </div> | ||||
| <div className='flex flex-col items-center gap-4 py-8'> | <div className='flex flex-col items-center gap-4 py-8'> | ||||
| <DifyLogo size='large' className='mx-auto' /> | |||||
| {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img | |||||
| src={systemFeatures.branding.workspace_logo} | |||||
| className='block h-7 w-auto object-contain' | |||||
| alt='logo' | |||||
| /> | |||||
| : <DifyLogo size='large' className='mx-auto' />} | |||||
| <div className='text-center text-xs font-normal text-text-tertiary'>Version {langeniusVersionInfo?.current_version}</div> | <div className='text-center text-xs font-normal text-text-tertiary'>Version {langeniusVersionInfo?.current_version}</div> | ||||
| <div className='flex flex-col items-center gap-2 text-center text-xs font-normal text-text-secondary'> | <div className='flex flex-col items-center gap-2 text-center text-xs font-normal text-text-secondary'> | ||||
| <div>© {dayjs().year()} LangGenius, Inc., Contributors.</div> | <div>© {dayjs().year()} LangGenius, Inc., Contributors.</div> | 
| import PlanBadge from './plan-badge' | import PlanBadge from './plan-badge' | ||||
| import LicenseNav from './license-env' | import LicenseNav from './license-env' | ||||
| import { Plan } from '../billing/type' | import { Plan } from '../billing/type' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| const navClassName = ` | const navClassName = ` | ||||
| flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl | flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl | ||||
| const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false) | const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false) | ||||
| const { enableBilling, plan } = useProviderContext() | const { enableBilling, plan } = useProviderContext() | ||||
| const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() | const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() | ||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| const isFreePlan = plan.type === Plan.sandbox | const isFreePlan = plan.type === Plan.sandbox | ||||
| const handlePlanClick = useCallback(() => { | const handlePlanClick = useCallback(() => { | ||||
| if (isFreePlan) | if (isFreePlan) | ||||
| !isMobile | !isMobile | ||||
| && <div className='flex shrink-0 items-center gap-1.5 self-stretch pl-3'> | && <div className='flex shrink-0 items-center gap-1.5 self-stretch pl-3'> | ||||
| <Link href="/apps" className='flex h-8 shrink-0 items-center justify-center gap-2 px-0.5'> | <Link href="/apps" className='flex h-8 shrink-0 items-center justify-center gap-2 px-0.5'> | ||||
| <DifyLogo /> | |||||
| {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img | |||||
| src={systemFeatures.branding.workspace_logo} | |||||
| className='block h-[22px] w-auto object-contain' | |||||
| alt='logo' | |||||
| /> | |||||
| : <DifyLogo />} | |||||
| </Link> | </Link> | ||||
| <div className='font-light text-divider-deep'>/</div> | <div className='font-light text-divider-deep'>/</div> | ||||
| <div className='flex items-center gap-0.5'> | <div className='flex items-center gap-0.5'> | ||||
| {isMobile && ( | {isMobile && ( | ||||
| <div className='flex'> | <div className='flex'> | ||||
| <Link href="/apps" className='mr-4 flex items-center'> | <Link href="/apps" className='mr-4 flex items-center'> | ||||
| <DifyLogo /> | |||||
| {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img | |||||
| src={systemFeatures.branding.workspace_logo} | |||||
| className='block h-[22px] w-auto object-contain' | |||||
| alt='logo' | |||||
| /> | |||||
| : <DifyLogo />} | |||||
| </Link> | </Link> | ||||
| <div className='font-light text-divider-deep'>/</div> | <div className='font-light text-divider-deep'>/</div> | ||||
| {enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />} | {enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />} | 
| import type { FilterState } from './filter-management' | import type { FilterState } from './filter-management' | ||||
| import FilterManagement from './filter-management' | import FilterManagement from './filter-management' | ||||
| import List from './list' | import List from './list' | ||||
| import { useInstalledLatestVersion, useInstalledPluginListWithPagination, useInvalidateInstalledPluginList } from '@/service/use-plugins' | |||||
| import { useInstalledLatestVersion, useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' | |||||
| import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' | import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' | ||||
| import { usePluginPageContext } from './context' | import { usePluginPageContext } from './context' | ||||
| import { useDebounceFn } from 'ahooks' | import { useDebounceFn } from 'ahooks' | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const filters = usePluginPageContext(v => v.filters) as FilterState | const filters = usePluginPageContext(v => v.filters) as FilterState | ||||
| const setFilters = usePluginPageContext(v => v.setFilters) | const setFilters = usePluginPageContext(v => v.setFilters) | ||||
| const { data: pluginList, isLoading: isPluginListLoading, isFetching, isLastPage, loadNextPage } = useInstalledPluginListWithPagination() | |||||
| const { data: pluginList, isLoading: isPluginListLoading, isFetching, isLastPage, loadNextPage } = useInstalledPluginList() | |||||
| const { data: installedLatestVersion } = useInstalledLatestVersion( | const { data: installedLatestVersion } = useInstalledLatestVersion( | ||||
| pluginList?.plugins | pluginList?.plugins | ||||
| .filter(plugin => plugin.source === PluginSource.marketplace) | .filter(plugin => plugin.source === PluginSource.marketplace) | 
| !isPC && resultExisted && 'rounded-b-2xl border-b-[0.5px] border-divider-regular', | !isPC && resultExisted && 'rounded-b-2xl border-b-[0.5px] border-divider-regular', | ||||
| )}> | )}> | ||||
| <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> | ||||
| {systemFeatures.branding.enabled ? ( | |||||
| <img src={systemFeatures.branding.login_page_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| ) : ( | |||||
| <DifyLogo size='small' /> | |||||
| )} | |||||
| { | |||||
| systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo | |||||
| ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> | |||||
| : customConfig?.replace_webapp_logo | |||||
| ? <img src={`${customConfig?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> | |||||
| : <DifyLogo size='small' /> | |||||
| } | |||||
| </div> | </div> | ||||
| )} | )} | ||||
| </div> | </div> | 
| </div> | </div> | ||||
| <div className="mx-auto mt-6 w-full"> | <div className="mx-auto mt-6 w-full"> | ||||
| <div className="bg-white"> | |||||
| <div> | |||||
| {/* Password */} | {/* Password */} | ||||
| <div className='mb-5'> | <div className='mb-5'> | ||||
| <label htmlFor="password" className="system-md-semibold my-2 text-text-secondary"> | <label htmlFor="password" className="system-md-semibold my-2 text-text-secondary"> | 
| import type { Locale } from '@/i18n' | import type { Locale } from '@/i18n' | ||||
| import I18n from '@/context/i18n' | import I18n from '@/context/i18n' | ||||
| import dynamic from 'next/dynamic' | import dynamic from 'next/dynamic' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| // Avoid rendering the logo and theme selector on the server | // Avoid rendering the logo and theme selector on the server | ||||
| const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), { | const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), { | ||||
| const Header = () => { | const Header = () => { | ||||
| const { locale, setLocaleOnClient } = useContext(I18n) | const { locale, setLocaleOnClient } = useContext(I18n) | ||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| return ( | return ( | ||||
| <div className='flex w-full items-center justify-between p-6'> | <div className='flex w-full items-center justify-between p-6'> | ||||
| <DifyLogo size='large' /> | |||||
| {systemFeatures.branding.enabled && systemFeatures.branding.login_page_logo | |||||
| ? <img | |||||
| src={systemFeatures.branding.login_page_logo} | |||||
| className='block h-7 w-auto object-contain' | |||||
| alt='logo' | |||||
| /> | |||||
| : <DifyLogo size='large' />} | |||||
| <div className='flex items-center gap-1'> | <div className='flex items-center gap-1'> | ||||
| <Select | <Select | ||||
| value={locale} | value={locale} | 
| perPage: '페이지당 항목 수', | perPage: '페이지당 항목 수', | ||||
| }, | }, | ||||
| theme: { | theme: { | ||||
| theme: '주제', | |||||
| light: '빛', | |||||
| dark: '어둠', | |||||
| theme: '테마', | |||||
| light: '밝은', | |||||
| dark: '어두운', | |||||
| auto: '시스템', | auto: '시스템', | ||||
| }, | }, | ||||
| compliance: { | compliance: { | 
| designDocument: '디자인 문서', | designDocument: '디자인 문서', | ||||
| productSpecification: '제품 사양서', | productSpecification: '제품 사양서', | ||||
| financialReport: '재무 보고서', | financialReport: '재무 보고서', | ||||
| marketAnalysis: '시장 분석', | |||||
| marketAnalysis: '마켓 분석', | |||||
| projectPlan: '프로젝트 계획서', | projectPlan: '프로젝트 계획서', | ||||
| teamStructure: '팀 구조', | teamStructure: '팀 구조', | ||||
| policiesProcedures: '정책 및 절차', | policiesProcedures: '정책 및 절차', | 
| datasetMetadata: { | datasetMetadata: { | ||||
| name: '이름', | name: '이름', | ||||
| deleteTitle: '삭제 확인', | deleteTitle: '삭제 확인', | ||||
| disabled: '장애인', | |||||
| disabled: '사용안함', | |||||
| addMetaData: '메타데이터 추가', | addMetaData: '메타데이터 추가', | ||||
| values: '{{num}} 값들', | values: '{{num}} 값들', | ||||
| namePlaceholder: '메타데이터 이름', | namePlaceholder: '메타데이터 이름', | 
| }, | }, | ||||
| source: { | source: { | ||||
| local: '로컬 패키지 파일', | local: '로컬 패키지 파일', | ||||
| marketplace: '시장', | |||||
| marketplace: '마켓', | |||||
| github: '깃허브', | github: '깃허브', | ||||
| }, | }, | ||||
| detailPanel: { | detailPanel: { | ||||
| endpointsEnabled: '{{num}}개의 엔드포인트 집합이 활성화되었습니다.', | endpointsEnabled: '{{num}}개의 엔드포인트 집합이 활성화되었습니다.', | ||||
| installFrom: '에서 설치', | installFrom: '에서 설치', | ||||
| allCategories: '모든 카테고리', | allCategories: '모든 카테고리', | ||||
| submitPlugin: '제출 플러그인', | |||||
| submitPlugin: '플러그인 제출', | |||||
| findMoreInMarketplace: 'Marketplace에서 더 알아보기', | findMoreInMarketplace: 'Marketplace에서 더 알아보기', | ||||
| searchCategories: '검색 카테고리', | searchCategories: '검색 카테고리', | ||||
| search: '검색', | search: '검색', | 
| code: '코드', | code: '코드', | ||||
| model: '모델', | model: '모델', | ||||
| rerankModel: '재정렬 모델', | rerankModel: '재정렬 모델', | ||||
| visionVariable: '시력 변수', | |||||
| visionVariable: '비전 변수', | |||||
| }, | }, | ||||
| invalidVariable: '잘못된 변수', | invalidVariable: '잘못된 변수', | ||||
| rerankModelRequired: 'Rerank Model을 켜기 전에 설정에서 모델이 성공적으로 구성되었는지 확인하십시오.', | rerankModelRequired: 'Rerank Model을 켜기 전에 설정에서 모델이 성공적으로 구성되었는지 확인하십시오.', | ||||
| metadata: { | metadata: { | ||||
| options: { | options: { | ||||
| disabled: { | disabled: { | ||||
| title: '장애인', | |||||
| title: '사용안함', | |||||
| subTitle: '메타데이터 필터링을 활성화하지 않음', | subTitle: '메타데이터 필터링을 활성화하지 않음', | ||||
| }, | }, | ||||
| automatic: { | automatic: { | 
| GitHubItemAndMarketPlaceDependency, | GitHubItemAndMarketPlaceDependency, | ||||
| InstallPackageResponse, | InstallPackageResponse, | ||||
| InstalledLatestVersionResponse, | InstalledLatestVersionResponse, | ||||
| InstalledPluginListResponse, | |||||
| InstalledPluginListWithTotalResponse, | InstalledPluginListWithTotalResponse, | ||||
| PackageDependency, | PackageDependency, | ||||
| Permissions, | Permissions, | ||||
| }) | }) | ||||
| } | } | ||||
| export const useInstalledPluginList = (disable?: boolean) => { | |||||
| return useQuery<InstalledPluginListResponse>({ | |||||
| queryKey: useInstalledPluginListKey, | |||||
| queryFn: () => get<InstalledPluginListResponse>('/workspaces/current/plugin/list'), | |||||
| enabled: !disable, | |||||
| initialData: !disable ? undefined : { plugins: [] }, | |||||
| }) | |||||
| } | |||||
| export const useInstalledPluginListWithPagination = (pageSize = 100) => { | |||||
| export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => { | |||||
| const fetchPlugins = async ({ pageParam = 1 }) => { | const fetchPlugins = async ({ pageParam = 1 }) => { | ||||
| const response = await get<InstalledPluginListWithTotalResponse>( | const response = await get<InstalledPluginListWithTotalResponse>( | ||||
| `/workspaces/current/plugin/list?page=${pageParam}&page_size=${pageSize}`, | `/workspaces/current/plugin/list?page=${pageParam}&page_size=${pageSize}`, | ||||
| hasNextPage, | hasNextPage, | ||||
| isFetchingNextPage, | isFetchingNextPage, | ||||
| isLoading, | isLoading, | ||||
| isSuccess, | |||||
| } = useInfiniteQuery({ | } = useInfiniteQuery({ | ||||
| queryKey: ['installed-plugins', pageSize], | |||||
| enabled: !disable, | |||||
| queryKey: useInstalledPluginListKey, | |||||
| queryFn: fetchPlugins, | queryFn: fetchPlugins, | ||||
| getNextPageParam: (lastPage, pages) => { | getNextPageParam: (lastPage, pages) => { | ||||
| const totalItems = lastPage.total | const totalItems = lastPage.total | ||||
| }) | }) | ||||
| const plugins = data?.pages.flatMap(page => page.plugins) ?? [] | const plugins = data?.pages.flatMap(page => page.plugins) ?? [] | ||||
| const total = data?.pages[0].total ?? 0 | |||||
| return { | return { | ||||
| data: { | |||||
| data: disable ? undefined : { | |||||
| plugins, | plugins, | ||||
| total, | |||||
| }, | }, | ||||
| isLastPage: !hasNextPage, | isLastPage: !hasNextPage, | ||||
| loadNextPage: () => { | loadNextPage: () => { | ||||
| isLoading, | isLoading, | ||||
| isFetching: isFetchingNextPage, | isFetching: isFetchingNextPage, | ||||
| error, | error, | ||||
| isSuccess, | |||||
| } | } | ||||
| } | } | ||||