mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-17 23:05:53 +08:00
Merge branch 'hotfix/create-from-template-category' into deploy/dev
This commit is contained in:
commit
e1149bd26d
@ -10,6 +10,7 @@ from configs import dify_config
|
|||||||
from controllers.console.workspace.error import AccountNotInitializedError
|
from controllers.console.workspace.error import AccountNotInitializedError
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from extensions.ext_redis import redis_client
|
from extensions.ext_redis import redis_client
|
||||||
|
from models.account import AccountStatus
|
||||||
from models.dataset import RateLimitLog
|
from models.dataset import RateLimitLog
|
||||||
from models.model import DifySetup
|
from models.model import DifySetup
|
||||||
from services.feature_service import FeatureService, LicenseStatus
|
from services.feature_service import FeatureService, LicenseStatus
|
||||||
@ -24,7 +25,7 @@ def account_initialization_required(view):
|
|||||||
# check account initialization
|
# check account initialization
|
||||||
account = current_user
|
account = current_user
|
||||||
|
|
||||||
if account.status == "uninitialized":
|
if account.status == AccountStatus.UNINITIALIZED:
|
||||||
raise AccountNotInitializedError()
|
raise AccountNotInitializedError()
|
||||||
|
|
||||||
return view(*args, **kwargs)
|
return view(*args, **kwargs)
|
||||||
|
@ -59,7 +59,7 @@ class WorkflowRunDetailApi(Resource):
|
|||||||
Get a workflow task running detail
|
Get a workflow task running detail
|
||||||
"""
|
"""
|
||||||
app_mode = AppMode.value_of(app_model.mode)
|
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()
|
raise NotWorkflowAppError()
|
||||||
|
|
||||||
workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_run_id).first()
|
workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_run_id).first()
|
||||||
|
@ -798,7 +798,25 @@ class ProviderConfiguration(BaseModel):
|
|||||||
provider_models = [m for m in provider_models if m.status == ModelStatus.ACTIVE]
|
provider_models = [m for m in provider_models if m.status == ModelStatus.ACTIVE]
|
||||||
|
|
||||||
# resort provider_models
|
# 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(
|
def _get_system_provider_models(
|
||||||
self,
|
self,
|
||||||
|
@ -134,6 +134,9 @@ class ProviderEntity(BaseModel):
|
|||||||
# pydantic configs
|
# pydantic configs
|
||||||
model_config = ConfigDict(protected_namespaces=())
|
model_config = ConfigDict(protected_namespaces=())
|
||||||
|
|
||||||
|
# position from plugin _position.yaml
|
||||||
|
position: Optional[dict[str, list[str]]] = {}
|
||||||
|
|
||||||
@field_validator("models", mode="before")
|
@field_validator("models", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_models(cls, v):
|
def validate_models(cls, v):
|
||||||
|
@ -191,7 +191,7 @@ const Apps = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='relative flex flex-1 overflow-y-auto'>
|
<div className='relative flex flex-1 overflow-y-auto'>
|
||||||
{!searchKeywords && <div className='h-full w-[200px] p-4'>
|
{!searchKeywords && <div className='h-full w-[200px] p-4'>
|
||||||
<Sidebar current={currCategory as AppCategories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
|
<Sidebar current={currCategory as AppCategories} categories={categories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
|
||||||
</div>}
|
</div>}
|
||||||
<div className='h-full flex-1 shrink-0 grow overflow-auto border-l border-divider-burn p-6 pt-2'>
|
<div className='h-full flex-1 shrink-0 grow overflow-auto border-l border-divider-burn p-6 pt-2'>
|
||||||
{searchFilteredList && searchFilteredList.length > 0 && <>
|
{searchFilteredList && searchFilteredList.length > 0 && <>
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
'use client'
|
'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 { useTranslation } from 'react-i18next'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
|
|
||||||
export enum AppCategories {
|
export enum AppCategories {
|
||||||
RECOMMENDED = 'Recommended',
|
RECOMMENDED = 'Recommended',
|
||||||
ASSISTANT = 'Assistant',
|
|
||||||
AGENT = 'Agent',
|
|
||||||
HR = 'HR',
|
|
||||||
PROGRAMMING = 'Programming',
|
|
||||||
WORKFLOW = 'Workflow',
|
|
||||||
WRITING = 'Writing',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SidebarProps = {
|
type SidebarProps = {
|
||||||
current: AppCategories
|
current: AppCategories | string
|
||||||
onClick?: (category: AppCategories) => void
|
categories: string[]
|
||||||
|
onClick?: (category: AppCategories | string) => void
|
||||||
onCreateFromBlank?: () => void
|
onCreateFromBlank?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) {
|
export default function Sidebar({ current, categories, onClick, onCreateFromBlank }: SidebarProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return <div className="flex h-full w-full flex-col">
|
return <div className="flex h-full w-full flex-col">
|
||||||
<ul>
|
<ul>
|
||||||
@ -28,12 +23,7 @@ export default function Sidebar({ current, onClick, onCreateFromBlank }: Sidebar
|
|||||||
</ul>
|
</ul>
|
||||||
<div className='system-xs-medium-uppercase px-3 pb-1 pt-2 text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div>
|
<div className='system-xs-medium-uppercase px-3 pb-1 pt-2 text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div>
|
||||||
<ul className='flex grow flex-col gap-0.5'>
|
<ul className='flex grow flex-col gap-0.5'>
|
||||||
<CategoryItem category={AppCategories.ASSISTANT} active={current === AppCategories.ASSISTANT} onClick={onClick} />
|
{categories.map(category => (<CategoryItem key={category} category={category} active={current === category} onClick={onClick} />))}
|
||||||
<CategoryItem category={AppCategories.AGENT} active={current === AppCategories.AGENT} onClick={onClick} />
|
|
||||||
<CategoryItem category={AppCategories.HR} active={current === AppCategories.HR} onClick={onClick} />
|
|
||||||
<CategoryItem category={AppCategories.PROGRAMMING} active={current === AppCategories.PROGRAMMING} onClick={onClick} />
|
|
||||||
<CategoryItem category={AppCategories.WORKFLOW} active={current === AppCategories.WORKFLOW} onClick={onClick} />
|
|
||||||
<CategoryItem category={AppCategories.WRITING} active={current === AppCategories.WRITING} onClick={onClick} />
|
|
||||||
</ul>
|
</ul>
|
||||||
<Divider bgStyle='gradient' />
|
<Divider bgStyle='gradient' />
|
||||||
<div className='flex cursor-pointer items-center gap-1 px-3 py-1 text-text-tertiary' onClick={onCreateFromBlank}>
|
<div className='flex cursor-pointer items-center gap-1 px-3 py-1 text-text-tertiary' onClick={onCreateFromBlank}>
|
||||||
@ -45,47 +35,25 @@ export default function Sidebar({ current, onClick, onCreateFromBlank }: Sidebar
|
|||||||
|
|
||||||
type CategoryItemProps = {
|
type CategoryItemProps = {
|
||||||
active: boolean
|
active: boolean
|
||||||
category: AppCategories
|
category: AppCategories | string
|
||||||
onClick?: (category: AppCategories) => void
|
onClick?: (category: AppCategories | string) => void
|
||||||
}
|
}
|
||||||
function CategoryItem({ category, active, onClick }: CategoryItemProps) {
|
function CategoryItem({ category, active, onClick }: CategoryItemProps) {
|
||||||
return <li
|
return <li
|
||||||
className={classNames('p-1 pl-3 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')}
|
className={classNames('p-1 pl-3 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')}
|
||||||
onClick={() => { onClick?.(category) }}>
|
onClick={() => { onClick?.(category) }}>
|
||||||
<div className='inline-flex h-5 w-5 items-center justify-center rounded-md border border-divider-regular bg-components-icon-bg-midnight-solid group-[.active]:bg-components-icon-bg-blue-solid'>
|
{category === AppCategories.RECOMMENDED && <div className='inline-flex h-5 w-5 items-center justify-center rounded-md border border-divider-regular bg-components-icon-bg-midnight-solid group-[.active]:bg-components-icon-bg-blue-solid'>
|
||||||
<AppCategoryIcon category={category} />
|
<RiThumbUpFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||||
</div>
|
</div>}
|
||||||
<AppCategoryLabel category={category}
|
<AppCategoryLabel category={category}
|
||||||
className={classNames('system-sm-medium text-components-menu-item-text group-[.active]:text-components-menu-item-text-active group-hover:text-components-menu-item-text-hover', active && 'system-sm-semibold')} />
|
className={classNames('system-sm-medium text-components-menu-item-text group-[.active]:text-components-menu-item-text-active group-hover:text-components-menu-item-text-hover', active && 'system-sm-semibold')} />
|
||||||
</li >
|
</li >
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppCategoryLabelProps = {
|
type AppCategoryLabelProps = {
|
||||||
category: AppCategories
|
category: AppCategories | string
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) {
|
export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) {
|
||||||
const { t } = useTranslation()
|
return <span className={className}>{category}</span>
|
||||||
return <span className={className}>{t(`app.newAppFromTemplate.sidebar.${category}`)}</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppCategoryIconProps = {
|
|
||||||
category: AppCategories
|
|
||||||
}
|
|
||||||
function AppCategoryIcon({ category }: AppCategoryIconProps) {
|
|
||||||
if (category === AppCategories.AGENT)
|
|
||||||
return <RiSpeakAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
if (category === AppCategories.ASSISTANT)
|
|
||||||
return <RiChatSmileAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
if (category === AppCategories.HR)
|
|
||||||
return <RiPassPendingFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
if (category === AppCategories.PROGRAMMING)
|
|
||||||
return <RiTerminalBoxFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
if (category === AppCategories.RECOMMENDED)
|
|
||||||
return <RiThumbUpFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
if (category === AppCategories.WRITING)
|
|
||||||
return <RiQuillPenAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
if (category === AppCategories.WORKFLOW)
|
|
||||||
return <RiExchange2Fill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
return <RiAppsFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' />
|
|
||||||
}
|
}
|
||||||
|
@ -316,6 +316,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
nodesConnectable={!nodesReadOnly}
|
nodesConnectable={!nodesReadOnly}
|
||||||
nodesFocusable={!nodesReadOnly}
|
nodesFocusable={!nodesReadOnly}
|
||||||
edgesFocusable={!nodesReadOnly}
|
edgesFocusable={!nodesReadOnly}
|
||||||
|
panOnScroll
|
||||||
panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly}
|
panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly}
|
||||||
zoomOnPinch={!workflowReadOnly}
|
zoomOnPinch={!workflowReadOnly}
|
||||||
zoomOnScroll={!workflowReadOnly}
|
zoomOnScroll={!workflowReadOnly}
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
3
web/pnpm-lock.yaml
generated
3
web/pnpm-lock.yaml
generated
@ -115,6 +115,9 @@ importers:
|
|||||||
classnames:
|
classnames:
|
||||||
specifier: ^2.5.1
|
specifier: ^2.5.1
|
||||||
version: 2.5.1
|
version: 2.5.1
|
||||||
|
clsx:
|
||||||
|
specifier: ^2.1.1
|
||||||
|
version: 2.1.1
|
||||||
copy-to-clipboard:
|
copy-to-clipboard:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
// 3) APPEND it to the document body right away:
|
// 3) APPEND it to the document body right away:
|
||||||
document.body.appendChild(preloadedIframe);
|
document.body.appendChild(preloadedIframe);
|
||||||
// ─── End Fix Snippet
|
// ─── 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");
|
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 {
|
} else {
|
||||||
startX = e.clientX - element.offsetLeft;
|
startX = e.clientX - element.offsetLeft;
|
||||||
startY = e.clientY - element.offsetTop;
|
startY = e.clientY - element.offsetTop;
|
||||||
|
startClientX = e.clientX;
|
||||||
|
startClientY = e.clientY;
|
||||||
}
|
}
|
||||||
document.addEventListener("mousemove", drag);
|
document.addEventListener("mousemove", drag);
|
||||||
document.addEventListener("touchmove", drag, { passive: false });
|
document.addEventListener("touchmove", drag, { passive: false });
|
||||||
|
2
web/public/embed.min.js
vendored
2
web/public/embed.min.js
vendored
@ -35,4 +35,4 @@
|
|||||||
<svg id="closeIcon" style="display:none" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg id="closeIcon" style="display:none" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M18 18L6 6M6 18L18 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M18 18L6 6M6 18L18 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
`,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<Math.abs(i)||8<Math.abs(o)?!0:u){r.style.transition="none",r.style.cursor="grabbing";i=document.getElementById(h);i&&(i.style.display="none",y("open"));let e,t;t="touchmove"===n.type?(e=n.touches[0].clientX-s,window.innerHeight-n.touches[0].clientY-d):(e=n.clientX-s,window.innerHeight-n.clientY-d);o=r.getBoundingClientRect(),i=window.innerWidth-o.width,n=window.innerHeight-o.height;"x"!==a&&"both"!==a||r.style.setProperty(`--${m}-left`,Math.max(0,Math.min(e,i))+"px"),"y"!==a&&"both"!==a||r.style.setProperty(`--${m}-bottom`,Math.max(0,Math.min(t,n))+"px")}}function c(){setTimeout(()=>{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<t.length&&console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"),document.getElementById(m)||n()}else console.error(t+" is empty or token is not provided")}function y(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function l(e){"Escape"===e.key&&(e=document.getElementById(h))&&"none"!==e.style.display&&(e.style.display="none",y("open"))}document.addEventListener("keydown",l),p?.dynamicScript?e():document.body.onload=e})();
|
`,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,l=("touchstart"===e.type?(s=e.touches[0].clientX-r.offsetLeft,d=e.touches[0].clientY-r.offsetTop,t=e.touches[0].clientX,e.touches[0]):(s=e.clientX-r.offsetLeft,d=e.clientY-r.offsetTop,t=e.clientX,e)).clientY,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<Math.abs(i)||8<Math.abs(o)?!0:u){r.style.transition="none",r.style.cursor="grabbing";i=document.getElementById(h);i&&(i.style.display="none",y("open"));let e,t;t="touchmove"===n.type?(e=n.touches[0].clientX-s,window.innerHeight-n.touches[0].clientY-d):(e=n.clientX-s,window.innerHeight-n.clientY-d);o=r.getBoundingClientRect(),i=window.innerWidth-o.width,n=window.innerHeight-o.height;"x"!==a&&"both"!==a||r.style.setProperty(`--${m}-left`,Math.max(0,Math.min(e,i))+"px"),"y"!==a&&"both"!==a||r.style.setProperty(`--${m}-bottom`,Math.max(0,Math.min(t,n))+"px")}}function c(){setTimeout(()=>{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<t.length&&console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"),document.getElementById(m)||n()}else console.error(t+" is empty or token is not provided")}function y(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function l(e){"Escape"===e.key&&(e=document.getElementById(h))&&"none"!==e.style.display&&(e.style.display="none",y("open"))}document.addEventListener("keydown",l),p?.dynamicScript?e():document.body.onload=e})();
|
Loading…
x
Reference in New Issue
Block a user