mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
feat: model load balancing (#4926)
This commit is contained in:
@ -3,10 +3,10 @@
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply inline-flex justify-center items-center content-center h-9 leading-5 rounded-lg px-4 py-2 text-base cursor-pointer whitespace-nowrap;
|
||||
}
|
||||
};
|
||||
|
||||
.btn-default {
|
||||
@apply border-solid border border-gray-200 cursor-pointer text-gray-500 hover:bg-white hover:shadow-sm hover:border-gray-300;
|
||||
@apply border-solid border border-gray-200 cursor-pointer text-gray-700 hover:bg-white hover:shadow-sm hover:border-gray-300;
|
||||
}
|
||||
|
||||
.btn-default-disabled {
|
||||
@ -28,4 +28,4 @@
|
||||
.btn-warning-disabled {
|
||||
@apply bg-red-600/75 cursor-not-allowed text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import type { FC, MouseEventHandler } from 'react'
|
||||
import React from 'react'
|
||||
import type { FC, MouseEventHandler, PropsWithChildren } from 'react'
|
||||
import React, { memo } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import Spinner from '../spinner'
|
||||
|
||||
export type IButtonProps = {
|
||||
export type IButtonProps = PropsWithChildren<{
|
||||
type?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
loading?: boolean
|
||||
tabIndex?: number
|
||||
children: React.ReactNode
|
||||
onClick?: MouseEventHandler<HTMLDivElement>
|
||||
}
|
||||
}>
|
||||
|
||||
const Button: FC<IButtonProps> = ({
|
||||
type,
|
||||
@ -21,22 +21,22 @@ const Button: FC<IButtonProps> = ({
|
||||
loading = false,
|
||||
tabIndex,
|
||||
}) => {
|
||||
let style = 'cursor-pointer'
|
||||
let typeClassNames = 'cursor-pointer'
|
||||
switch (type) {
|
||||
case 'primary':
|
||||
style = (disabled || loading) ? 'btn-primary-disabled' : 'btn-primary'
|
||||
typeClassNames = (disabled || loading) ? 'btn-primary-disabled' : 'btn-primary'
|
||||
break
|
||||
case 'warning':
|
||||
style = (disabled || loading) ? 'btn-warning-disabled' : 'btn-warning'
|
||||
typeClassNames = (disabled || loading) ? 'btn-warning-disabled' : 'btn-warning'
|
||||
break
|
||||
default:
|
||||
style = disabled ? 'btn-default-disabled' : 'btn-default'
|
||||
typeClassNames = disabled ? 'btn-default-disabled' : 'btn-default'
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`btn ${style} ${className && className}`}
|
||||
className={classNames('btn', typeClassNames, className)}
|
||||
tabIndex={tabIndex}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
>
|
||||
@ -47,4 +47,4 @@ const Button: FC<IButtonProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Button)
|
||||
export default memo(Button)
|
||||
|
||||
@ -65,7 +65,7 @@ const WorkflowProcessItem = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'mb-2 rounded-xl border-[0.5px] border-black/[0.08]',
|
||||
'mb-2 rounded-xl border-[0.5px] border-black/8',
|
||||
collapse ? 'py-[7px]' : hideInfo ? 'pt-2 pb-1' : 'py-2',
|
||||
collapse && (!grayBg ? 'bg-white' : 'bg-gray-50'),
|
||||
hideInfo ? 'mx-[-8px] px-1' : 'w-full px-3',
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 3V20M12 20H6.99999M12 20H17M2.99999 6H7.52785C7.83834 6 8.14457 5.92771 8.42228 5.78885L9.5777 5.21115C9.85541 5.07229 10.1616 5 10.4721 5H13.5279C13.8384 5 14.1446 5.07229 14.4223 5.21115L15.5777 5.78885C15.8554 5.92771 16.1616 6 16.4721 6H21M5.49999 6L3.02043 13.4387C2.71807 14.3458 3.08918 15.3834 4.0053 15.657C5.0117 15.9577 5.98828 15.9577 6.99468 15.657C7.9108 15.3834 8.28191 14.3457 7.97955 13.4387L5.49999 6ZM18.5 6L16.0204 13.4387C15.7181 14.3458 16.0892 15.3834 17.0053 15.657C18.0117 15.9577 18.9883 15.9577 19.9947 15.657C20.9108 15.3834 21.2819 14.3457 20.9796 13.4387L18.5 6Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 791 B |
@ -0,0 +1,29 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12 3V20M12 20H6.99999M12 20H17M2.99999 6H7.52785C7.83834 6 8.14457 5.92771 8.42228 5.78885L9.5777 5.21115C9.85541 5.07229 10.1616 5 10.4721 5H13.5279C13.8384 5 14.1446 5.07229 14.4223 5.21115L15.5777 5.78885C15.8554 5.92771 16.1616 6 16.4721 6H21M5.49999 6L3.02043 13.4387C2.71807 14.3458 3.08918 15.3834 4.0053 15.657C5.0117 15.9577 5.98828 15.9577 6.99468 15.657C7.9108 15.3834 8.28191 14.3457 7.97955 13.4387L5.49999 6ZM18.5 6L16.0204 13.4387C15.7181 14.3458 16.0892 15.3834 17.0053 15.657C18.0117 15.9577 18.9883 15.9577 19.9947 15.657C20.9108 15.3834 21.2819 14.3457 20.9796 13.4387L18.5 6Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Balance"
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Balance.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Balance'
|
||||
|
||||
export default Icon
|
||||
@ -1,3 +1,4 @@
|
||||
export { default as Balance } from './Balance'
|
||||
export { default as CoinsStacked01 } from './CoinsStacked01'
|
||||
export { default as GoldCoin } from './GoldCoin'
|
||||
export { default as ReceiptList } from './ReceiptList'
|
||||
|
||||
@ -77,8 +77,7 @@ const ImageList: FC<ImageListProps> = ({
|
||||
<div
|
||||
className={`
|
||||
absolute inset-0 flex items-center justify-center rounded-lg z-[1] border
|
||||
${
|
||||
item.progress === -1
|
||||
${item.progress === -1
|
||||
? 'bg-[#FEF0C7] border-[#DC6803]'
|
||||
: 'bg-black/[0.16] border-transparent'
|
||||
}
|
||||
@ -120,7 +119,7 @@ const ImageList: FC<ImageListProps> = ({
|
||||
type="button"
|
||||
className={cn(
|
||||
'absolute z-10 -top-[9px] -right-[9px] items-center justify-center w-[18px] h-[18px]',
|
||||
'bg-white hover:bg-gray-50 border-[0.5px] border-black/[0.02] rounded-2xl shadow-lg',
|
||||
'bg-white hover:bg-gray-50 border-[0.5px] border-black/2 rounded-2xl shadow-lg',
|
||||
item.progress === -1 ? 'flex' : 'hidden group-hover:flex',
|
||||
)}
|
||||
onClick={() => onRemove && onRemove(item._id)}
|
||||
|
||||
@ -18,7 +18,7 @@ const ImagePreview: FC<ImagePreviewProps> = ({
|
||||
className='max-w-full max-h-full'
|
||||
/>
|
||||
<div
|
||||
className='absolute top-6 right-6 flex items-center justify-center w-8 h-8 bg-white/[0.08] rounded-lg backdrop-blur-[2px] cursor-pointer'
|
||||
className='absolute top-6 right-6 flex items-center justify-center w-8 h-8 bg-white/8 rounded-lg backdrop-blur-[2px] cursor-pointer'
|
||||
onClick={onCancel}
|
||||
>
|
||||
<XClose className='w-4 h-4 text-white' />
|
||||
|
||||
7
web/app/components/base/modal/index.css
Normal file
7
web/app/components/base/modal/index.css
Normal file
@ -0,0 +1,7 @@
|
||||
.modal-dialog {
|
||||
@apply relative z-10;
|
||||
}
|
||||
|
||||
.modal-panel {
|
||||
@apply w-full max-w-md transform rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all;
|
||||
}
|
||||
@ -1,16 +1,17 @@
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { Fragment } from 'react'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import classNames from 'classnames'
|
||||
// https://headlessui.com/react/dialog
|
||||
|
||||
type IModal = {
|
||||
className?: string
|
||||
wrapperClassName?: string
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
onClose?: () => void
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
children: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
closable?: boolean
|
||||
overflowVisible?: boolean
|
||||
}
|
||||
@ -19,7 +20,7 @@ export default function Modal({
|
||||
className,
|
||||
wrapperClassName,
|
||||
isShow,
|
||||
onClose,
|
||||
onClose = () => { },
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
@ -28,7 +29,7 @@ export default function Modal({
|
||||
}: IModal) {
|
||||
return (
|
||||
<Transition appear show={isShow} as={Fragment}>
|
||||
<Dialog as="div" className={`relative z-30 ${wrapperClassName}`} onClose={onClose}>
|
||||
<Dialog as="div" className={classNames('modal-dialog', wrapperClassName)} onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
@ -58,7 +59,11 @@ export default function Modal({
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className={`w-full max-w-md transform ${overflowVisible ? 'overflow-visible' : 'overflow-hidden'} rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all ${className}`}>
|
||||
<Dialog.Panel className={classNames(
|
||||
'modal-panel',
|
||||
overflowVisible ? 'overflow-visible' : 'overflow-hidden',
|
||||
className,
|
||||
)}>
|
||||
{title && <Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
.simplePieChart {
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 5px -3px rgb(from var(--simple-pie-chart-color) r g b / 0.1), 0.5px 0.5px 3px 0 rgb(from var(--simple-pie-chart-color) r g b / 0.3);
|
||||
}
|
||||
66
web/app/components/base/simple-pie-chart/index.tsx
Normal file
66
web/app/components/base/simple-pie-chart/index.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import type { CSSProperties } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import ReactECharts from 'echarts-for-react'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import classNames from 'classnames'
|
||||
import style from './index.module.css'
|
||||
|
||||
export type SimplePieChartProps = {
|
||||
percentage?: number
|
||||
fill?: string
|
||||
stroke?: string
|
||||
size?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SimplePieChart = ({ percentage = 80, fill = '#fdb022', stroke = '#f79009', size = 12, className }: SimplePieChartProps) => {
|
||||
const option: EChartsOption = useMemo(() => ({
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['83%', '100%'],
|
||||
animation: false,
|
||||
data: [
|
||||
{ value: 100, itemStyle: { color: stroke } },
|
||||
],
|
||||
emphasis: {
|
||||
disabled: true,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
cursor: 'default',
|
||||
},
|
||||
{
|
||||
type: 'pie',
|
||||
radius: '83%',
|
||||
animationDuration: 600,
|
||||
data: [
|
||||
{ value: percentage, itemStyle: { color: fill } },
|
||||
{ value: 100 - percentage, itemStyle: { color: '#fff' } },
|
||||
],
|
||||
emphasis: {
|
||||
disabled: true,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
cursor: 'default',
|
||||
},
|
||||
],
|
||||
}), [stroke, fill, percentage])
|
||||
|
||||
return (
|
||||
<ReactECharts
|
||||
option={option}
|
||||
className={classNames(style.simplePieChart, className)}
|
||||
style={{
|
||||
'--simple-pie-chart-color': fill,
|
||||
'width': size,
|
||||
'height': size,
|
||||
} as CSSProperties}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(SimplePieChart)
|
||||
@ -4,13 +4,14 @@ import classNames from 'classnames'
|
||||
import { Switch as OriginalSwitch } from '@headlessui/react'
|
||||
|
||||
type SwitchProps = {
|
||||
onChange: (value: boolean) => void
|
||||
onChange?: (value: boolean) => void
|
||||
size?: 'sm' | 'md' | 'lg' | 'l'
|
||||
defaultValue?: boolean
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Switch = ({ onChange, size = 'lg', defaultValue = false, disabled = false }: SwitchProps) => {
|
||||
const Switch = ({ onChange, size = 'lg', defaultValue = false, disabled = false, className }: SwitchProps) => {
|
||||
const [enabled, setEnabled] = useState(defaultValue)
|
||||
useEffect(() => {
|
||||
setEnabled(defaultValue)
|
||||
@ -42,13 +43,14 @@ const Switch = ({ onChange, size = 'lg', defaultValue = false, disabled = false
|
||||
if (disabled)
|
||||
return
|
||||
setEnabled(checked)
|
||||
onChange(checked)
|
||||
onChange?.(checked)
|
||||
}}
|
||||
className={classNames(
|
||||
wrapStyle[size],
|
||||
enabled ? 'bg-blue-600' : 'bg-gray-200',
|
||||
'relative inline-flex flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out',
|
||||
disabled ? '!opacity-50 !cursor-not-allowed' : '',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user