| parser.add_argument("icon", type=str, location="json") | parser.add_argument("icon", type=str, location="json") | ||||
| parser.add_argument("icon_background", 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("use_icon_as_answer_icon", type=bool, location="json") | ||||
| parser.add_argument("max_active_requests", type=int, location="json") | |||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| app_service = AppService() | app_service = AppService() |
| "site": fields.Nested(site_fields), | "site": fields.Nested(site_fields), | ||||
| "api_base_url": fields.String, | "api_base_url": fields.String, | ||||
| "use_icon_as_answer_icon": fields.Boolean, | "use_icon_as_answer_icon": fields.Boolean, | ||||
| "max_active_requests": fields.Integer, | |||||
| "created_by": fields.String, | "created_by": fields.String, | ||||
| "created_at": TimestampField, | "created_at": TimestampField, | ||||
| "updated_by": fields.String, | "updated_by": fields.String, |
| app.icon = args.get("icon") | app.icon = args.get("icon") | ||||
| app.icon_background = args.get("icon_background") | app.icon_background = args.get("icon_background") | ||||
| app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False) | 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_by = current_user.id | ||||
| app.updated_at = datetime.now(UTC).replace(tzinfo=None) | app.updated_at = datetime.now(UTC).replace(tzinfo=None) | ||||
| db.session.commit() | db.session.commit() |
| icon_background, | icon_background, | ||||
| description, | description, | ||||
| use_icon_as_answer_icon, | use_icon_as_answer_icon, | ||||
| max_active_requests, | |||||
| }) => { | }) => { | ||||
| try { | try { | ||||
| await updateAppInfo({ | await updateAppInfo({ | ||||
| icon_background, | icon_background, | ||||
| description, | description, | ||||
| use_icon_as_answer_icon, | use_icon_as_answer_icon, | ||||
| max_active_requests, | |||||
| }) | }) | ||||
| setShowEditModal(false) | setShowEditModal(false) | ||||
| notify({ | notify({ | ||||
| appDescription={app.description} | appDescription={app.description} | ||||
| appMode={app.mode} | appMode={app.mode} | ||||
| appUseIconAsAnswerIcon={app.use_icon_as_answer_icon} | appUseIconAsAnswerIcon={app.use_icon_as_answer_icon} | ||||
| max_active_requests={app.max_active_requests ?? null} | |||||
| show={showEditModal} | show={showEditModal} | ||||
| onConfirm={onEdit} | onConfirm={onEdit} | ||||
| onHide={() => setShowEditModal(false)} | onHide={() => setShowEditModal(false)} |
| icon_background, | icon_background, | ||||
| description, | description, | ||||
| use_icon_as_answer_icon, | use_icon_as_answer_icon, | ||||
| max_active_requests, | |||||
| }) => { | }) => { | ||||
| if (!appDetail) | if (!appDetail) | ||||
| return | return | ||||
| icon_background, | icon_background, | ||||
| description, | description, | ||||
| use_icon_as_answer_icon, | use_icon_as_answer_icon, | ||||
| max_active_requests, | |||||
| }) | }) | ||||
| setShowEditModal(false) | setShowEditModal(false) | ||||
| notify({ | notify({ | ||||
| appDescription={appDetail.description} | appDescription={appDetail.description} | ||||
| appMode={appDetail.mode} | appMode={appDetail.mode} | ||||
| appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon} | appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon} | ||||
| max_active_requests={appDetail.max_active_requests ?? null} | |||||
| show={showEditModal} | show={showEditModal} | ||||
| onConfirm={onEdit} | onConfirm={onEdit} | ||||
| onHide={() => setShowEditModal(false)} | onHide={() => setShowEditModal(false)} |
| appIconUrl?: string | null | appIconUrl?: string | null | ||||
| appMode?: string | appMode?: string | ||||
| appUseIconAsAnswerIcon?: boolean | appUseIconAsAnswerIcon?: boolean | ||||
| max_active_requests: number | null | |||||
| onConfirm: (info: { | onConfirm: (info: { | ||||
| name: string | name: string | ||||
| icon_type: AppIconType | icon_type: AppIconType | ||||
| icon_background?: string | icon_background?: string | ||||
| description: string | description: string | ||||
| use_icon_as_answer_icon?: boolean | use_icon_as_answer_icon?: boolean | ||||
| max_active_requests?: number | null | |||||
| }) => Promise<void> | }) => Promise<void> | ||||
| confirmDisabled?: boolean | confirmDisabled?: boolean | ||||
| onHide: () => void | onHide: () => void | ||||
| appDescription, | appDescription, | ||||
| appMode, | appMode, | ||||
| appUseIconAsAnswerIcon, | appUseIconAsAnswerIcon, | ||||
| max_active_requests, | |||||
| onConfirm, | onConfirm, | ||||
| confirmDisabled, | confirmDisabled, | ||||
| onHide, | onHide, | ||||
| const [description, setDescription] = useState(appDescription || '') | const [description, setDescription] = useState(appDescription || '') | ||||
| const [useIconAsAnswerIcon, setUseIconAsAnswerIcon] = useState(appUseIconAsAnswerIcon || false) | 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 { plan, enableBilling } = useProviderContext() | ||||
| const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) | const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) | ||||
| Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') }) | Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') }) | ||||
| return | return | ||||
| } | } | ||||
| onConfirm({ | |||||
| const isValid = maxActiveRequestsInput.trim() !== '' && !isNaN(Number(maxActiveRequestsInput)) | |||||
| const payload: any = { | |||||
| name, | name, | ||||
| icon_type: appIcon.type, | icon_type: appIcon.type, | ||||
| icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, | icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, | ||||
| icon_background: appIcon.type === 'emoji' ? appIcon.background! : undefined, | icon_background: appIcon.type === 'emoji' ? appIcon.background! : undefined, | ||||
| description, | description, | ||||
| use_icon_as_answer_icon: useIconAsAnswerIcon, | use_icon_as_answer_icon: useIconAsAnswerIcon, | ||||
| }) | |||||
| } | |||||
| if (isValid) | |||||
| payload.max_active_requests = Number(maxActiveRequestsInput) | |||||
| onConfirm(payload) | |||||
| onHide() | onHide() | ||||
| }, [name, appIcon, description, useIconAsAnswerIcon, onConfirm, onHide, t]) | |||||
| }, [name, appIcon, description, useIconAsAnswerIcon, onConfirm, onHide, t, maxActiveRequestsInput]) | |||||
| const { run: handleSubmit } = useDebounceFn(submit, { wait: 300 }) | const { run: handleSubmit } = useDebounceFn(submit, { wait: 300 }) | ||||
| <p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p> | <p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p> | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {isEditModal && ( | |||||
| <div className='pt-2'> | |||||
| <div className='mb-2 mt-2 text-sm font-medium leading-[20px] text-text-primary'>{t('app.maxActiveRequests')}</div> | |||||
| <Input | |||||
| type='number' | |||||
| min={1} | |||||
| placeholder={t('app.maxActiveRequestsPlaceholder')} | |||||
| value={maxActiveRequestsInput} | |||||
| onChange={(e) => { | |||||
| setMaxActiveRequestsInput(e.target.value) | |||||
| }} | |||||
| className='h-10 w-full' | |||||
| /> | |||||
| <p className='body-xs-regular mb-0 mt-2 text-text-tertiary'>{t('app.maxActiveRequestsTip')}</p> | |||||
| </div> | |||||
| )} | |||||
| {!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />} | {!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />} | ||||
| </div> | </div> | ||||
| <div className='flex flex-row-reverse'> | <div className='flex flex-row-reverse'> |
| notSetDesc: 'Currently nobody can access the web app. Please set permissions.', | notSetDesc: 'Currently nobody can access the web app. Please set permissions.', | ||||
| }, | }, | ||||
| noAccessPermission: 'No permission to access web app', | 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 | export default translation |
| notSetDesc: '当前任何人都无法访问 Web 应用。请设置访问权限。', | notSetDesc: '当前任何人都无法访问 Web 应用。请设置访问权限。', | ||||
| }, | }, | ||||
| noAccessPermission: '没有权限访问 web 应用', | noAccessPermission: '没有权限访问 web 应用', | ||||
| maxActiveRequests: '最大活跃请求数', | |||||
| maxActiveRequestsPlaceholder: '0 表示不限制', | |||||
| maxActiveRequestsTip: '当前应用的最大活跃请求数(0 表示不限制)', | |||||
| } | } | ||||
| export default translation | export default translation |
| return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } }) | return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } }) | ||||
| } | } | ||||
| export const updateAppInfo: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string; description: string; use_icon_as_answer_icon?: boolean }> = ({ appID, name, icon_type, icon, icon_background, description, use_icon_as_answer_icon }) => { | |||||
| return put<AppDetailResponse>(`apps/${appID}`, { body: { name, icon_type, icon, icon_background, description, use_icon_as_answer_icon } }) | |||||
| export const updateAppInfo: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string; description: string; use_icon_as_answer_icon?: boolean; max_active_requests?: number | null }> = ({ 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<AppDetailResponse>(`apps/${appID}`, { body }) | |||||
| } | } | ||||
| export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppMode; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => { | export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppMode; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => { |
| } | } | ||||
| /** access control */ | /** access control */ | ||||
| access_mode: AccessMode | access_mode: AccessMode | ||||
| max_active_requests?: number | null | |||||
| } | } | ||||
| export type AppSSO = { | export type AppSSO = { |