diff --git a/api/controllers/console/wraps.py b/api/controllers/console/wraps.py index e5e8038ad7..6911181d82 100644 --- a/api/controllers/console/wraps.py +++ b/api/controllers/console/wraps.py @@ -10,6 +10,7 @@ from configs import dify_config from controllers.console.workspace.error import AccountNotInitializedError from extensions.ext_database import db from extensions.ext_redis import redis_client +from models.account import AccountStatus from models.dataset import RateLimitLog from models.model import DifySetup from services.feature_service import FeatureService, LicenseStatus @@ -24,7 +25,7 @@ def account_initialization_required(view): # check account initialization account = current_user - if account.status == "uninitialized": + if account.status == AccountStatus.UNINITIALIZED: raise AccountNotInitializedError() return view(*args, **kwargs) diff --git a/api/controllers/service_api/app/workflow.py b/api/controllers/service_api/app/workflow.py index 8b10a028f3..ca3e35aab8 100644 --- a/api/controllers/service_api/app/workflow.py +++ b/api/controllers/service_api/app/workflow.py @@ -59,7 +59,7 @@ class WorkflowRunDetailApi(Resource): Get a workflow task running detail """ app_mode = AppMode.value_of(app_model.mode) - if app_mode != AppMode.WORKFLOW: + if app_mode not in [AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]: raise NotWorkflowAppError() workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_run_id).first() diff --git a/api/core/entities/provider_configuration.py b/api/core/entities/provider_configuration.py index b3affc91a6..86887c9b4a 100644 --- a/api/core/entities/provider_configuration.py +++ b/api/core/entities/provider_configuration.py @@ -798,7 +798,25 @@ class ProviderConfiguration(BaseModel): provider_models = [m for m in provider_models if m.status == ModelStatus.ACTIVE] # resort provider_models - return sorted(provider_models, key=lambda x: x.model_type.value) + # Optimize sorting logic: first sort by provider.position order, then by model_type.value + # Get the position list for model types (retrieve only once for better performance) + model_type_positions = {} + if hasattr(self.provider, "position") and self.provider.position: + model_type_positions = self.provider.position + + def get_sort_key(model: ModelWithProviderEntity): + # Get the position list for the current model type + positions = model_type_positions.get(model.model_type.value, []) + + # If the model name is in the position list, use its index for sorting + # Otherwise use a large value (list length) to place undefined models at the end + position_index = positions.index(model.model) if model.model in positions else len(positions) + + # Return composite sort key: (model_type value, model position index) + return (model.model_type.value, position_index) + + # Sort using the composite sort key + return sorted(provider_models, key=get_sort_key) def _get_system_provider_models( self, diff --git a/api/core/model_runtime/entities/provider_entities.py b/api/core/model_runtime/entities/provider_entities.py index 85321bed94..d0f9ee13e5 100644 --- a/api/core/model_runtime/entities/provider_entities.py +++ b/api/core/model_runtime/entities/provider_entities.py @@ -134,6 +134,9 @@ class ProviderEntity(BaseModel): # pydantic configs model_config = ConfigDict(protected_namespaces=()) + # position from plugin _position.yaml + position: Optional[dict[str, list[str]]] = {} + @field_validator("models", mode="before") @classmethod def validate_models(cls, v): diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index 702a07397d..8d88e8c361 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -191,7 +191,7 @@ const Apps = ({
{!searchKeywords &&
- { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> + { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
}
{searchFilteredList && searchFilteredList.length > 0 && <> diff --git a/web/app/components/app/create-app-dialog/app-list/sidebar.tsx b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx index ee843675a5..86048316ab 100644 --- a/web/app/components/app/create-app-dialog/app-list/sidebar.tsx +++ b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx @@ -1,26 +1,21 @@ 'use client' -import { RiAppsFill, RiChatSmileAiFill, RiExchange2Fill, RiPassPendingFill, RiQuillPenAiFill, RiSpeakAiFill, RiStickyNoteAddLine, RiTerminalBoxFill, RiThumbUpFill } from '@remixicon/react' +import { RiStickyNoteAddLine, RiThumbUpFill } from '@remixicon/react' import { useTranslation } from 'react-i18next' import classNames from '@/utils/classnames' import Divider from '@/app/components/base/divider' export enum AppCategories { RECOMMENDED = 'Recommended', - ASSISTANT = 'Assistant', - AGENT = 'Agent', - HR = 'HR', - PROGRAMMING = 'Programming', - WORKFLOW = 'Workflow', - WRITING = 'Writing', } type SidebarProps = { - current: AppCategories - onClick?: (category: AppCategories) => void + current: AppCategories | string + categories: string[] + onClick?: (category: AppCategories | string) => void onCreateFromBlank?: () => void } -export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) { +export default function Sidebar({ current, categories, onClick, onCreateFromBlank }: SidebarProps) { const { t } = useTranslation() return
    @@ -28,12 +23,7 @@ export default function Sidebar({ current, onClick, onCreateFromBlank }: Sidebar
{t('app.newAppFromTemplate.byCategories')}
    - - - - - - + {categories.map(category => ())}
@@ -45,47 +35,25 @@ export default function Sidebar({ current, onClick, onCreateFromBlank }: Sidebar type CategoryItemProps = { active: boolean - category: AppCategories - onClick?: (category: AppCategories) => void + category: AppCategories | string + onClick?: (category: AppCategories | string) => void } function CategoryItem({ category, active, onClick }: CategoryItemProps) { return
  • { onClick?.(category) }}> -
    - -
    + {category === AppCategories.RECOMMENDED &&
    + +
    }
  • } type AppCategoryLabelProps = { - category: AppCategories + category: AppCategories | string className?: string } export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) { - const { t } = useTranslation() - return {t(`app.newAppFromTemplate.sidebar.${category}`)} -} - -type AppCategoryIconProps = { - category: AppCategories -} -function AppCategoryIcon({ category }: AppCategoryIconProps) { - if (category === AppCategories.AGENT) - return - if (category === AppCategories.ASSISTANT) - return - if (category === AppCategories.HR) - return - if (category === AppCategories.PROGRAMMING) - return - if (category === AppCategories.RECOMMENDED) - return - if (category === AppCategories.WRITING) - return - if (category === AppCategories.WORKFLOW) - return - return + return {category} } diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 9a3e13822a..64aefc4dd3 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -316,6 +316,7 @@ export const Workflow: FC = memo(({ nodesConnectable={!nodesReadOnly} nodesFocusable={!nodesReadOnly} edgesFocusable={!nodesReadOnly} + panOnScroll panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly} zoomOnPinch={!workflowReadOnly} zoomOnScroll={!workflowReadOnly} diff --git a/web/package.json b/web/package.json index b232f908a3..960812f5a3 100644 --- a/web/package.json +++ b/web/package.json @@ -61,6 +61,7 @@ "ahooks": "^3.8.4", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", + "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index bf39194eaa..4d48af8742 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -115,6 +115,9 @@ importers: classnames: specifier: ^2.5.1 version: 2.5.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 diff --git a/web/public/embed.js b/web/public/embed.js index 8f13a7178c..8aaf94f955 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -25,7 +25,7 @@ // Main function to embed the chatbot async function embedChatbot() { let isDragging = false - + if (!config || !config.token) { console.error(`${configKey} is empty or token is not provided`); return; @@ -81,7 +81,7 @@ // 3) APPEND it to the document body right away: document.body.appendChild(preloadedIframe); // ─── End Fix Snippet - if(iframeUrl.length > 2048) { + if (iframeUrl.length > 2048) { console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"); } @@ -252,6 +252,8 @@ } else { startX = e.clientX - element.offsetLeft; startY = e.clientY - element.offsetTop; + startClientX = e.clientX; + startClientY = e.clientY; } document.addEventListener("mousemove", drag); document.addEventListener("touchmove", drag, { passive: false }); @@ -264,7 +266,7 @@ const touch = e.type === "touchmove" ? e.touches[0] : e; const deltaX = touch.clientX - startClientX; const deltaY = touch.clientY - startClientY; - + // Determine whether it is a drag operation if (Math.abs(deltaX) > 8 || Math.abs(deltaY) > 8) { isDragging = true; diff --git a/web/public/embed.min.js b/web/public/embed.min.js index 13837183c0..c0aeac4487 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -35,4 +35,4 @@ - `,n.appendChild(e),document.body.appendChild(n),n.addEventListener("click",t),n.addEventListener("touchend",t),p.draggable){var r=n;var a=p.dragAxis||"both";let s,d,t,l;function o(e){u=!1,"touchstart"===e.type?(s=e.touches[0].clientX-r.offsetLeft,d=e.touches[0].clientY-r.offsetTop,t=e.touches[0].clientX,l=e.touches[0].clientY):(s=e.clientX-r.offsetLeft,d=e.clientY-r.offsetTop),document.addEventListener("mousemove",i),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("mouseup",c),document.addEventListener("touchend",c),e.preventDefault()}function i(n){var o="touchmove"===n.type?n.touches[0]:n,i=o.clientX-t,o=o.clientY-l;if(u=8{u=!1},0),r.style.transition="",r.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}r.addEventListener("mousedown",o),r.addEventListener("touchstart",o)}}e.style.display="none",document.body.appendChild(e),2048{u=!1},0),r.style.transition="",r.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}r.addEventListener("mousedown",o),r.addEventListener("touchstart",o)}}e.style.display="none",document.body.appendChild(e),2048