diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 860166a61a..9fe32dde6d 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -151,6 +151,7 @@ class AppApi(Resource): parser.add_argument("icon", type=str, location="json") parser.add_argument("icon_background", type=str, location="json") parser.add_argument("use_icon_as_answer_icon", type=bool, location="json") + parser.add_argument("max_active_requests", type=int, location="json") args = parser.parse_args() app_service = AppService() diff --git a/api/fields/app_fields.py b/api/fields/app_fields.py index 73c224542a..b6d85e0e24 100644 --- a/api/fields/app_fields.py +++ b/api/fields/app_fields.py @@ -188,6 +188,7 @@ app_detail_fields_with_site = { "site": fields.Nested(site_fields), "api_base_url": fields.String, "use_icon_as_answer_icon": fields.Boolean, + "max_active_requests": fields.Integer, "created_by": fields.String, "created_at": TimestampField, "updated_by": fields.String, diff --git a/api/services/app_service.py b/api/services/app_service.py index db0f8cd414..0a08f345df 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -233,6 +233,7 @@ class AppService: app.icon = args.get("icon") app.icon_background = args.get("icon_background") app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False) + app.max_active_requests = args.get("max_active_requests") app.updated_by = current_user.id app.updated_at = datetime.now(UTC).replace(tzinfo=None) db.session.commit() diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index f50cc10520..e04c3fdea6 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -88,6 +88,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { icon_background, description, use_icon_as_answer_icon, + max_active_requests, }) => { try { await updateAppInfo({ @@ -98,6 +99,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { icon_background, description, use_icon_as_answer_icon, + max_active_requests, }) setShowEditModal(false) notify({ @@ -432,6 +434,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { appDescription={app.description} appMode={app.mode} appUseIconAsAnswerIcon={app.use_icon_as_answer_icon} + max_active_requests={app.max_active_requests ?? null} show={showEditModal} onConfirm={onEdit} onHide={() => setShowEditModal(false)} diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 3817ebf5a4..d5a04ec420 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -71,6 +71,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx icon_background, description, use_icon_as_answer_icon, + max_active_requests, }) => { if (!appDetail) return @@ -83,6 +84,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx icon_background, description, use_icon_as_answer_icon, + max_active_requests, }) setShowEditModal(false) notify({ @@ -350,6 +352,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx appDescription={appDetail.description} appMode={appDetail.mode} appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon} + max_active_requests={appDetail.max_active_requests ?? null} show={showEditModal} onConfirm={onEdit} onHide={() => setShowEditModal(false)} diff --git a/web/app/components/explore/create-app-modal/index.tsx b/web/app/components/explore/create-app-modal/index.tsx index f30b286786..7e1e59b51b 100644 --- a/web/app/components/explore/create-app-modal/index.tsx +++ b/web/app/components/explore/create-app-modal/index.tsx @@ -27,6 +27,7 @@ export type CreateAppModalProps = { appIconUrl?: string | null appMode?: string appUseIconAsAnswerIcon?: boolean + max_active_requests: number | null onConfirm: (info: { name: string icon_type: AppIconType @@ -34,6 +35,7 @@ export type CreateAppModalProps = { icon_background?: string description: string use_icon_as_answer_icon?: boolean + max_active_requests?: number | null }) => Promise confirmDisabled?: boolean onHide: () => void @@ -50,6 +52,7 @@ const CreateAppModal = ({ appDescription, appMode, appUseIconAsAnswerIcon, + max_active_requests, onConfirm, confirmDisabled, onHide, @@ -66,6 +69,10 @@ const CreateAppModal = ({ const [description, setDescription] = useState(appDescription || '') const [useIconAsAnswerIcon, setUseIconAsAnswerIcon] = useState(appUseIconAsAnswerIcon || false) + const [maxActiveRequestsInput, setMaxActiveRequestsInput] = useState( + max_active_requests !== null && max_active_requests !== undefined ? String(max_active_requests) : '', + ) + const { plan, enableBilling } = useProviderContext() const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) @@ -74,16 +81,21 @@ const CreateAppModal = ({ Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') }) return } - onConfirm({ + const isValid = maxActiveRequestsInput.trim() !== '' && !isNaN(Number(maxActiveRequestsInput)) + const payload: any = { name, icon_type: appIcon.type, icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, icon_background: appIcon.type === 'emoji' ? appIcon.background! : undefined, description, use_icon_as_answer_icon: useIconAsAnswerIcon, - }) + } + if (isValid) + payload.max_active_requests = Number(maxActiveRequestsInput) + + onConfirm(payload) onHide() - }, [name, appIcon, description, useIconAsAnswerIcon, onConfirm, onHide, t]) + }, [name, appIcon, description, useIconAsAnswerIcon, onConfirm, onHide, t, maxActiveRequestsInput]) const { run: handleSubmit } = useDebounceFn(submit, { wait: 300 }) @@ -158,6 +170,22 @@ const CreateAppModal = ({

{t('app.answerIcon.descriptionInExplore')}

)} + {isEditModal && ( +
+
{t('app.maxActiveRequests')}
+ { + setMaxActiveRequestsInput(e.target.value) + }} + className='h-10 w-full' + /> +

{t('app.maxActiveRequestsTip')}

+
+ )} {!isEditModal && isAppsFull && }
diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 8ddb0f1bfe..06b5e8ded1 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -245,6 +245,9 @@ const translation = { notSetDesc: 'Currently nobody can access the web app. Please set permissions.', }, noAccessPermission: 'No permission to access web app', + maxActiveRequests: 'Max concurrent requests', + maxActiveRequestsPlaceholder: 'Enter 0 for unlimited', + maxActiveRequestsTip: 'Maximum number of concurrent active requests per app (0 for unlimited)', } export default translation diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index c616c088b1..9e577a360e 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -246,6 +246,9 @@ const translation = { notSetDesc: '当前任何人都无法访问 Web 应用。请设置访问权限。', }, noAccessPermission: '没有权限访问 web 应用', + maxActiveRequests: '最大活跃请求数', + maxActiveRequestsPlaceholder: '0 表示不限制', + maxActiveRequestsTip: '当前应用的最大活跃请求数(0 表示不限制)', } export default translation diff --git a/web/service/apps.ts b/web/service/apps.ts index d87a98412e..8e506a0987 100644 --- a/web/service/apps.ts +++ b/web/service/apps.ts @@ -21,8 +21,9 @@ export const createApp: Fetcher('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } }) } -export const updateAppInfo: Fetcher = ({ appID, name, icon_type, icon, icon_background, description, use_icon_as_answer_icon }) => { - return put(`apps/${appID}`, { body: { name, icon_type, icon, icon_background, description, use_icon_as_answer_icon } }) +export const updateAppInfo: Fetcher = ({ appID, name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests }) => { + const body = { name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests } + return put(`apps/${appID}`, { body }) } export const copyApp: Fetcher = ({ appID, name, icon_type, icon, icon_background, mode, description }) => { diff --git a/web/types/app.ts b/web/types/app.ts index 3de5c446ec..ba58e0defe 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -369,6 +369,7 @@ export type App = { } /** access control */ access_mode: AccessMode + max_active_requests?: number | null } export type AppSSO = {