Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: crazywoola <427733928@qq.com>tags/0.6.11
| parser.add_argument('icon_background', type=str, location='json') | parser.add_argument('icon_background', type=str, location='json') | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| if 'mode' not in args or args['mode'] is None: | if 'mode' not in args or args['mode'] is None: | ||||
| @cloud_edition_billing_resource_check('apps') | @cloud_edition_billing_resource_check('apps') | ||||
| def post(self): | def post(self): | ||||
| """Import app""" | """Import app""" | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| @get_app_model | @get_app_model | ||||
| def delete(self, app_model): | def delete(self, app_model): | ||||
| """Delete app""" | """Delete app""" | ||||
| if not current_user.is_admin_or_owner: | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| app_service = AppService() | app_service = AppService() | ||||
| @marshal_with(app_detail_fields_with_site) | @marshal_with(app_detail_fields_with_site) | ||||
| def post(self, app_model): | def post(self, app_model): | ||||
| """Copy app""" | """Copy app""" | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| @get_app_model | @get_app_model | ||||
| @marshal_with(app_detail_fields) | @marshal_with(app_detail_fields) | ||||
| def post(self, app_model): | def post(self, app_model): | ||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | |||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| parser.add_argument('enable_site', type=bool, required=True, location='json') | parser.add_argument('enable_site', type=bool, required=True, location='json') | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| @get_app_model | @get_app_model | ||||
| @marshal_with(app_detail_fields) | @marshal_with(app_detail_fields) | ||||
| def post(self, app_model): | def post(self, app_model): | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| parser.add_argument('enable_api', type=bool, required=True, location='json') | parser.add_argument('enable_api', type=bool, required=True, location='json') | ||||
| args = parser.parse_args() | args = parser.parse_args() |
| from flask_restful.inputs import int_range | from flask_restful.inputs import int_range | ||||
| from sqlalchemy import func, or_ | from sqlalchemy import func, or_ | ||||
| from sqlalchemy.orm import joinedload | from sqlalchemy.orm import joinedload | ||||
| from werkzeug.exceptions import NotFound | |||||
| from werkzeug.exceptions import Forbidden, NotFound | |||||
| from controllers.console import api | from controllers.console import api | ||||
| from controllers.console.app.wraps import get_app_model | from controllers.console.app.wraps import get_app_model | ||||
| @get_app_model(mode=AppMode.COMPLETION) | @get_app_model(mode=AppMode.COMPLETION) | ||||
| @marshal_with(conversation_pagination_fields) | @marshal_with(conversation_pagination_fields) | ||||
| def get(self, app_model): | def get(self, app_model): | ||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| parser.add_argument('keyword', type=str, location='args') | parser.add_argument('keyword', type=str, location='args') | ||||
| parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args') | parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args') | ||||
| @get_app_model(mode=AppMode.COMPLETION) | @get_app_model(mode=AppMode.COMPLETION) | ||||
| @marshal_with(conversation_message_detail_fields) | @marshal_with(conversation_message_detail_fields) | ||||
| def get(self, app_model, conversation_id): | def get(self, app_model, conversation_id): | ||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| conversation_id = str(conversation_id) | conversation_id = str(conversation_id) | ||||
| return _get_conversation(app_model, conversation_id) | return _get_conversation(app_model, conversation_id) | ||||
| @account_initialization_required | @account_initialization_required | ||||
| @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | ||||
| def delete(self, app_model, conversation_id): | def delete(self, app_model, conversation_id): | ||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| conversation_id = str(conversation_id) | conversation_id = str(conversation_id) | ||||
| conversation = db.session.query(Conversation) \ | conversation = db.session.query(Conversation) \ | ||||
| @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | ||||
| @marshal_with(conversation_with_summary_pagination_fields) | @marshal_with(conversation_with_summary_pagination_fields) | ||||
| def get(self, app_model): | def get(self, app_model): | ||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| parser.add_argument('keyword', type=str, location='args') | parser.add_argument('keyword', type=str, location='args') | ||||
| parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args') | parser.add_argument('start', type=datetime_string('%Y-%m-%d %H:%M'), location='args') | ||||
| @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | ||||
| @marshal_with(conversation_detail_fields) | @marshal_with(conversation_detail_fields) | ||||
| def get(self, app_model, conversation_id): | def get(self, app_model, conversation_id): | ||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| conversation_id = str(conversation_id) | conversation_id = str(conversation_id) | ||||
| return _get_conversation(app_model, conversation_id) | return _get_conversation(app_model, conversation_id) | ||||
| @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT]) | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def delete(self, app_model, conversation_id): | def delete(self, app_model, conversation_id): | ||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| conversation_id = str(conversation_id) | conversation_id = str(conversation_id) | ||||
| conversation = db.session.query(Conversation) \ | conversation = db.session.query(Conversation) \ |
| def post(self, app_model): | def post(self, app_model): | ||||
| args = parse_app_site_args() | args = parse_app_site_args() | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be editor, admin, or owner | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| site = db.session.query(Site). \ | site = db.session.query(Site). \ |
| help='Invalid indexing technique.') | help='Invalid indexing technique.') | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| try: | try: | ||||
| parser.add_argument('retrieval_model', type=dict, location='json', help='Invalid retrieval model.') | parser.add_argument('retrieval_model', type=dict, location='json', help='Invalid retrieval model.') | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| dataset = DatasetService.update_dataset( | dataset = DatasetService.update_dataset( | ||||
| def delete(self, dataset_id): | def delete(self, dataset_id): | ||||
| dataset_id_str = str(dataset_id) | dataset_id_str = str(dataset_id) | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| try: | try: |
| if not dataset: | if not dataset: | ||||
| raise NotFound('Dataset not found.') | raise NotFound('Dataset not found.') | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| try: | try: | ||||
| @marshal_with(dataset_and_document_fields) | @marshal_with(dataset_and_document_fields) | ||||
| @cloud_edition_billing_resource_check('vector_space') | @cloud_edition_billing_resource_check('vector_space') | ||||
| def post(self): | def post(self): | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| document_id = str(document_id) | document_id = str(document_id) | ||||
| document = self.get_document(dataset_id, document_id) | document = self.get_document(dataset_id, document_id) | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| if action == "pause": | if action == "pause": | ||||
| doc_type = req_data.get('doc_type') | doc_type = req_data.get('doc_type') | ||||
| doc_metadata = req_data.get('doc_metadata') | doc_metadata = req_data.get('doc_metadata') | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| if doc_type is None or doc_metadata is None: | if doc_type is None or doc_metadata is None: | ||||
| document = self.get_document(dataset_id, document_id) | document = self.get_document(dataset_id, document_id) | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| indexing_cache_key = 'document_{}_indexing'.format(document.id) | indexing_cache_key = 'document_{}_indexing'.format(document.id) |
| raise NotFound('Dataset not found.') | raise NotFound('Dataset not found.') | ||||
| # check user's model setting | # check user's model setting | ||||
| DatasetService.check_dataset_model_setting(dataset) | DatasetService.check_dataset_model_setting(dataset) | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| try: | try: | ||||
| ).first() | ).first() | ||||
| if not segment: | if not segment: | ||||
| raise NotFound('Segment not found.') | raise NotFound('Segment not found.') | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| try: | try: | ||||
| DatasetService.check_dataset_permission(dataset, current_user) | DatasetService.check_dataset_permission(dataset, current_user) |
| @login_required | @login_required | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def post(self): | def post(self): | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def patch(self, tag_id): | def patch(self, tag_id): | ||||
| tag_id = str(tag_id) | tag_id = str(tag_id) | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def delete(self, tag_id): | def delete(self, tag_id): | ||||
| tag_id = str(tag_id) | tag_id = str(tag_id) | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| TagService.delete_tag(tag_id) | TagService.delete_tag(tag_id) | ||||
| @login_required | @login_required | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def post(self): | def post(self): | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| @login_required | @login_required | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def post(self): | def post(self): | ||||
| # The role of the current user in the ta table must be admin or owner | |||||
| if not current_user.is_admin_or_owner: | |||||
| # The role of the current user in the ta table must be admin, owner, or editor | |||||
| if not current_user.is_editor: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() |
| invitee_emails = args['emails'] | invitee_emails = args['emails'] | ||||
| invitee_role = args['role'] | invitee_role = args['role'] | ||||
| interface_language = args['language'] | interface_language = args['language'] | ||||
| if invitee_role not in [TenantAccountRole.ADMIN, TenantAccountRole.NORMAL]: | |||||
| if not TenantAccountRole.is_non_owner_role(invitee_role): | |||||
| return {'code': 'invalid-role', 'message': 'Invalid role'}, 400 | return {'code': 'invalid-role', 'message': 'Invalid role'}, 400 | ||||
| inviter = current_user | inviter = current_user | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| new_role = args['role'] | new_role = args['role'] | ||||
| if new_role not in ['admin', 'normal', 'owner']: | |||||
| if not TenantAccountRole.is_valid_role(new_role): | |||||
| return {'code': 'invalid-role', 'message': 'Invalid role'}, 400 | return {'code': 'invalid-role', 'message': 'Invalid role'}, 400 | ||||
| member = Account.query.get(str(member_id)) | member = Account.query.get(str(member_id)) |
| from core.model_runtime.errors.validate import CredentialsValidateFailedError | from core.model_runtime.errors.validate import CredentialsValidateFailedError | ||||
| from core.model_runtime.utils.encoders import jsonable_encoder | from core.model_runtime.utils.encoders import jsonable_encoder | ||||
| from libs.login import login_required | from libs.login import login_required | ||||
| from models.account import TenantAccountRole | |||||
| from services.model_load_balancing_service import ModelLoadBalancingService | from services.model_load_balancing_service import ModelLoadBalancingService | ||||
| from services.model_provider_service import ModelProviderService | from services.model_provider_service import ModelProviderService | ||||
| @login_required | @login_required | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def post(self): | def post(self): | ||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | |||||
| parser = reqparse.RequestParser() | parser = reqparse.RequestParser() | ||||
| parser.add_argument('model_settings', type=list, required=True, nullable=False, location='json') | parser.add_argument('model_settings', type=list, required=True, nullable=False, location='json') | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| @login_required | @login_required | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def post(self, provider: str): | def post(self, provider: str): | ||||
| if not TenantAccountRole.is_privileged_role(current_user.current_tenant.current_role): | |||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| tenant_id = current_user.current_tenant_id | tenant_id = current_user.current_tenant_id | ||||
| @login_required | @login_required | ||||
| @account_initialization_required | @account_initialization_required | ||||
| def delete(self, provider: str): | def delete(self, provider: str): | ||||
| if not TenantAccountRole.is_privileged_role(current_user.current_tenant.current_role): | |||||
| if not current_user.is_admin_or_owner: | |||||
| raise Forbidden() | raise Forbidden() | ||||
| tenant_id = current_user.current_tenant_id | tenant_id = current_user.current_tenant_id |
| def is_admin_or_owner(self): | def is_admin_or_owner(self): | ||||
| return TenantAccountRole.is_privileged_role(self._current_tenant.current_role) | return TenantAccountRole.is_privileged_role(self._current_tenant.current_role) | ||||
| @property | |||||
| def is_editor(self): | |||||
| return TenantAccountRole.is_editing_role(self._current_tenant.current_role) | |||||
| class TenantStatus(str, enum.Enum): | class TenantStatus(str, enum.Enum): | ||||
| NORMAL = 'normal' | NORMAL = 'normal' | ||||
| class TenantAccountRole(str, enum.Enum): | class TenantAccountRole(str, enum.Enum): | ||||
| OWNER = 'owner' | OWNER = 'owner' | ||||
| ADMIN = 'admin' | ADMIN = 'admin' | ||||
| EDITOR = 'editor' | |||||
| NORMAL = 'normal' | NORMAL = 'normal' | ||||
| @staticmethod | |||||
| def is_valid_role(role: str) -> bool: | |||||
| return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL} | |||||
| @staticmethod | @staticmethod | ||||
| def is_privileged_role(role: str) -> bool: | def is_privileged_role(role: str) -> bool: | ||||
| return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.OWNER} | |||||
| return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN} | |||||
| @staticmethod | |||||
| def is_non_owner_role(role: str) -> bool: | |||||
| return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL} | |||||
| @staticmethod | |||||
| def is_editing_role(role: str) -> bool: | |||||
| return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR} | |||||
| class Tenant(db.Model): | class Tenant(db.Model): |
| def test_account_is_privileged_role() -> None: | def test_account_is_privileged_role() -> None: | ||||
| assert TenantAccountRole.ADMIN == 'admin' | assert TenantAccountRole.ADMIN == 'admin' | ||||
| assert TenantAccountRole.OWNER == 'owner' | assert TenantAccountRole.OWNER == 'owner' | ||||
| assert TenantAccountRole.EDITOR == 'editor' | |||||
| assert TenantAccountRole.NORMAL == 'normal' | assert TenantAccountRole.NORMAL == 'normal' | ||||
| assert TenantAccountRole.is_privileged_role(TenantAccountRole.ADMIN) | assert TenantAccountRole.is_privileged_role(TenantAccountRole.ADMIN) | ||||
| assert TenantAccountRole.is_privileged_role(TenantAccountRole.OWNER) | assert TenantAccountRole.is_privileged_role(TenantAccountRole.OWNER) | ||||
| assert not TenantAccountRole.is_privileged_role(TenantAccountRole.NORMAL) | assert not TenantAccountRole.is_privileged_role(TenantAccountRole.NORMAL) | ||||
| assert not TenantAccountRole.is_privileged_role(TenantAccountRole.EDITOR) | |||||
| assert not TenantAccountRole.is_privileged_role('') | assert not TenantAccountRole.is_privileged_role('') |
| const pathname = usePathname() | const pathname = usePathname() | ||||
| const media = useBreakpoints() | const media = useBreakpoints() | ||||
| const isMobile = media === MediaType.mobile | const isMobile = media === MediaType.mobile | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceManager, isCurrentWorkspaceEditor } = useAppContext() | |||||
| const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({ | const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({ | ||||
| appDetail: state.appDetail, | appDetail: state.appDetail, | ||||
| setAppDetail: state.setAppDetail, | setAppDetail: state.setAppDetail, | ||||
| selectedIcon: NavIcon | selectedIcon: NavIcon | ||||
| }>>([]) | }>>([]) | ||||
| const getNavigations = useCallback((appId: string, isCurrentWorkspaceManager: boolean, mode: string) => { | |||||
| const getNavigations = useCallback((appId: string, isCurrentWorkspaceManager: boolean, isCurrentWorkspaceEditor: boolean, mode: string) => { | |||||
| const navs = [ | const navs = [ | ||||
| ...(isCurrentWorkspaceManager | |||||
| ...(isCurrentWorkspaceEditor | |||||
| ? [{ | ? [{ | ||||
| name: t('common.appMenus.promptEng'), | name: t('common.appMenus.promptEng'), | ||||
| href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`, | href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`, | ||||
| icon: TerminalSquare, | icon: TerminalSquare, | ||||
| selectedIcon: TerminalSquareSolid, | selectedIcon: TerminalSquareSolid, | ||||
| }, | }, | ||||
| { | |||||
| name: mode !== 'workflow' | |||||
| ? t('common.appMenus.logAndAnn') | |||||
| : t('common.appMenus.logs'), | |||||
| href: `/app/${appId}/logs`, | |||||
| icon: FileHeart02, | |||||
| selectedIcon: FileHeart02Solid, | |||||
| }, | |||||
| ...(isCurrentWorkspaceManager | |||||
| ? [{ | |||||
| name: mode !== 'workflow' | |||||
| ? t('common.appMenus.logAndAnn') | |||||
| : t('common.appMenus.logs'), | |||||
| href: `/app/${appId}/logs`, | |||||
| icon: FileHeart02, | |||||
| selectedIcon: FileHeart02Solid, | |||||
| }] | |||||
| : [] | |||||
| ), | |||||
| { | { | ||||
| name: t('common.appMenus.overview'), | name: t('common.appMenus.overview'), | ||||
| href: `/app/${appId}/overview`, | href: `/app/${appId}/overview`, | ||||
| } | } | ||||
| else { | else { | ||||
| setAppDetail(res) | setAppDetail(res) | ||||
| setNavigation(getNavigations(appId, isCurrentWorkspaceManager, res.mode)) | |||||
| setNavigation(getNavigations(appId, isCurrentWorkspaceManager, isCurrentWorkspaceEditor, res.mode)) | |||||
| } | } | ||||
| }) | }) | ||||
| }, [appId, isCurrentWorkspaceManager]) | |||||
| }, [appId, isCurrentWorkspaceManager, isCurrentWorkspaceEditor]) | |||||
| useUnmount(() => { | useUnmount(() => { | ||||
| setAppDetail() | setAppDetail() |
| const AppCard = ({ app, onRefresh }: AppCardProps) => { | const AppCard = ({ app, onRefresh }: AppCardProps) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { notify } = useContext(ToastContext) | const { notify } = useContext(ToastContext) | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const { onPlanInfoChanged } = useProviderContext() | const { onPlanInfoChanged } = useProviderContext() | ||||
| const { push } = useRouter() | const { push } = useRouter() | ||||
| onRefresh() | onRefresh() | ||||
| mutateApps() | mutateApps() | ||||
| onPlanInfoChanged() | onPlanInfoChanged() | ||||
| getRedirection(isCurrentWorkspaceManager, newApp, push) | |||||
| getRedirection(isCurrentWorkspaceEditor, newApp, push) | |||||
| } | } | ||||
| catch (e) { | catch (e) { | ||||
| notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) | ||||
| <div | <div | ||||
| onClick={(e) => { | onClick={(e) => { | ||||
| e.preventDefault() | e.preventDefault() | ||||
| getRedirection(isCurrentWorkspaceManager, app, push) | |||||
| getRedirection(isCurrentWorkspaceEditor, app, push) | |||||
| }} | }} | ||||
| className='group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg' | className='group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg' | ||||
| > | > | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {isCurrentWorkspaceManager && ( | |||||
| {isCurrentWorkspaceEditor && ( | |||||
| <> | <> | ||||
| <div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-gray-200'/> | <div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-gray-200'/> | ||||
| <div className='!hidden group-hover:!flex shrink-0'> | <div className='!hidden group-hover:!flex shrink-0'> |
| const Apps = () => { | const Apps = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const showTagManagementModal = useTagStore(s => s.showTagManagementModal) | const showTagManagementModal = useTagStore(s => s.showTagManagementModal) | ||||
| const [activeTab, setActiveTab] = useTabSearchParams({ | const [activeTab, setActiveTab] = useTabSearchParams({ | ||||
| defaultTab: 'all', | defaultTab: 'all', | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> | <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> | ||||
| {isCurrentWorkspaceManager | |||||
| {isCurrentWorkspaceEditor | |||||
| && <NewAppCard onSuccess={mutate} />} | && <NewAppCard onSuccess={mutate} />} | ||||
| {data?.map(({ data: apps }: any) => apps.map((app: any) => ( | {data?.map(({ data: apps }: any) => apps.map((app: any) => ( | ||||
| <AppCard key={app.id} app={app} onRefresh={mutate} /> | <AppCard key={app.id} app={app} onRefresh={mutate} /> |
| tags, | tags, | ||||
| keywords, | keywords, | ||||
| }: Props) => { | }: Props) => { | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const { data, isLoading, setSize, mutate } = useSWRInfinite( | const { data, isLoading, setSize, mutate } = useSWRInfinite( | ||||
| (pageIndex: number, previousPageData: DataSetListResponse) => getKey(pageIndex, previousPageData, tags, keywords), | (pageIndex: number, previousPageData: DataSetListResponse) => getKey(pageIndex, previousPageData, tags, keywords), | ||||
| fetchDatasets, | fetchDatasets, | ||||
| return ( | return ( | ||||
| <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> | <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> | ||||
| { isCurrentWorkspaceManager && <NewDatasetCard ref={anchorRef} /> } | |||||
| { isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} /> } | |||||
| {data?.map(({ data: datasets }) => datasets.map(dataset => ( | {data?.map(({ data: datasets }) => datasets.map(dataset => ( | ||||
| <DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />), | <DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />), | ||||
| ))} | ))} |
| 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) | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const isCreatingRef = useRef(false) | const isCreatingRef = useRef(false) | ||||
| const onCreate: MouseEventHandler = useCallback(async () => { | const onCreate: MouseEventHandler = useCallback(async () => { | ||||
| onClose() | onClose() | ||||
| mutateApps() | mutateApps() | ||||
| localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | ||||
| getRedirection(isCurrentWorkspaceManager, app, push) | |||||
| getRedirection(isCurrentWorkspaceEditor, app, push) | |||||
| } | } | ||||
| catch (e) { | catch (e) { | ||||
| notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) | ||||
| } | } | ||||
| isCreatingRef.current = false | isCreatingRef.current = false | ||||
| }, [name, notify, t, appMode, emoji.icon, emoji.icon_background, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceManager]) | |||||
| }, [name, notify, t, appMode, emoji.icon, emoji.icon_background, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceEditor]) | |||||
| return ( | return ( | ||||
| <Modal | <Modal |
| setFileContent('') | setFileContent('') | ||||
| } | } | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| 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) | ||||
| onClose() | onClose() | ||||
| notify({ type: 'success', message: t('app.newApp.appCreated') }) | notify({ type: 'success', message: t('app.newApp.appCreated') }) | ||||
| localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | ||||
| getRedirection(isCurrentWorkspaceManager, app, push) | |||||
| getRedirection(isCurrentWorkspaceEditor, app, push) | |||||
| } | } | ||||
| catch (e) { | catch (e) { | ||||
| notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| }: IAppCardProps) { | }: IAppCardProps) { | ||||
| const router = useRouter() | const router = useRouter() | ||||
| const pathname = usePathname() | const pathname = usePathname() | ||||
| const { currentWorkspace, isCurrentWorkspaceManager } = useAppContext() | |||||
| const { currentWorkspace, isCurrentWorkspaceManager, isCurrentWorkspaceEditor } = useAppContext() | |||||
| const [showSettingsModal, setShowSettingsModal] = useState(false) | const [showSettingsModal, setShowSettingsModal] = useState(false) | ||||
| const [showEmbedded, setShowEmbedded] = useState(false) | const [showEmbedded, setShowEmbedded] = useState(false) | ||||
| const [showCustomizeModal, setShowCustomizeModal] = useState(false) | const [showCustomizeModal, setShowCustomizeModal] = useState(false) | ||||
| if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') | if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') | ||||
| operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon }) | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon }) | ||||
| if (isCurrentWorkspaceManager) | |||||
| if (isCurrentWorkspaceEditor) | |||||
| operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon }) | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon }) | ||||
| return operationsMap | return operationsMap | ||||
| }, [isCurrentWorkspaceManager, appInfo, t]) | |||||
| }, [isCurrentWorkspaceEditor, appInfo, t]) | |||||
| const isApp = cardType === 'webapp' | const isApp = cardType === 'webapp' | ||||
| const basicName = isApp | const basicName = isApp | ||||
| ? appInfo?.site?.title | ? appInfo?.site?.title | ||||
| : t('appOverview.overview.apiInfo.title') | : t('appOverview.overview.apiInfo.title') | ||||
| const toggleDisabled = isApp ? !isCurrentWorkspaceEditor : !isCurrentWorkspaceManager | |||||
| const runningStatus = isApp ? appInfo.enable_site : appInfo.enable_api | const runningStatus = isApp ? appInfo.enable_site : appInfo.enable_api | ||||
| const { app_base_url, access_token } = appInfo.site ?? {} | const { app_base_url, access_token } = appInfo.site ?? {} | ||||
| const appMode = (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') ? 'chat' : appInfo.mode | const appMode = (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') ? 'chat' : appInfo.mode | ||||
| ? t('appOverview.overview.status.running') | ? t('appOverview.overview.status.running') | ||||
| : t('appOverview.overview.status.disable')} | : t('appOverview.overview.status.disable')} | ||||
| </Tag> | </Tag> | ||||
| <Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={currentWorkspace?.role === 'normal'} /> | |||||
| <Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={toggleDisabled} /> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className="flex flex-col justify-center py-2"> | <div className="flex flex-col justify-center py-2"> |
| const { notify } = useContext(ToastContext) | const { notify } = useContext(ToastContext) | ||||
| const setAppDetail = useAppStore(s => s.setAppDetail) | const setAppDetail = useAppStore(s => s.setAppDetail) | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| 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) | ||||
| await deleteApp(appDetail.id) | await deleteApp(appDetail.id) | ||||
| localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | ||||
| getRedirection( | getRedirection( | ||||
| isCurrentWorkspaceManager, | |||||
| isCurrentWorkspaceEditor, | |||||
| { | { | ||||
| id: newAppID, | id: newAppID, | ||||
| mode: appDetail.mode === 'completion' ? 'workflow' : 'advanced-chat', | mode: appDetail.mode === 'completion' ? 'workflow' : 'advanced-chat', |
| ) | ) | ||||
| } | } | ||||
| <div className='flex'> | <div className='flex'> | ||||
| <Button type='default' className={`flex flex-shrink-0 mt-4 ${s.autoWidth}`} onClick={onCreate} disabled={ !currentWorkspace || currentWorkspace.role === 'normal'}> | |||||
| <Button type='default' className={`flex flex-shrink-0 mt-4 ${s.autoWidth}`} onClick={onCreate} disabled={ !currentWorkspace || !isCurrentWorkspaceManager}> | |||||
| <PlusIcon className='flex flex-shrink-0 w-4 h-4' /> | <PlusIcon className='flex flex-shrink-0 w-4 h-4' /> | ||||
| <div className='text-xs font-medium text-gray-800'>{t('appApi.apiKeyModal.createNewSecretKey')}</div> | <div className='text-xs font-medium text-gray-800'>{t('appApi.apiKeyModal.createNewSecretKey')}</div> | ||||
| </Button> | </Button> |
| onSuccess, | onSuccess, | ||||
| }: AppsProps) => { | }: AppsProps) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const { push } = useRouter() | const { push } = useRouter() | ||||
| const { hasEditPermission } = useContext(ExploreContext) | const { hasEditPermission } = useContext(ExploreContext) | ||||
| const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' }) | const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' }) | ||||
| if (onSuccess) | if (onSuccess) | ||||
| onSuccess() | onSuccess() | ||||
| localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') | ||||
| getRedirection(isCurrentWorkspaceManager, app, push) | |||||
| getRedirection(isCurrentWorkspaceEditor, app, push) | |||||
| } | } | ||||
| catch (e) { | catch (e) { | ||||
| Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) | Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| const RoleMap = { | const RoleMap = { | ||||
| owner: t('common.members.owner'), | owner: t('common.members.owner'), | ||||
| admin: t('common.members.admin'), | admin: t('common.members.admin'), | ||||
| editor: t('common.members.editor'), | |||||
| normal: t('common.members.normal'), | normal: t('common.members.normal'), | ||||
| } | } | ||||
| const { locale } = useContext(I18n) | const { locale } = useContext(I18n) |
| name: 'normal', | name: 'normal', | ||||
| description: t('common.members.normalTip'), | description: t('common.members.normalTip'), | ||||
| }, | }, | ||||
| { | |||||
| name: 'editor', | |||||
| description: t('common.members.editorTip'), | |||||
| }, | |||||
| { | { | ||||
| name: 'admin', | name: 'admin', | ||||
| description: t('common.members.adminTip'), | description: t('common.members.adminTip'), | ||||
| <div className='flex flex-row'> | <div className='flex flex-row'> | ||||
| <span | <span | ||||
| className={cn( | className={cn( | ||||
| 'text-indigo-600 w-8', | |||||
| 'text-indigo-600 mr-2', | |||||
| 'flex items-center', | 'flex items-center', | ||||
| )} | )} | ||||
| > | > | ||||
| <span className={`${selected ? 'font-medium' : 'font-normal'} capitalize block truncate`}> | <span className={`${selected ? 'font-medium' : 'font-normal'} capitalize block truncate`}> | ||||
| {t(`common.members.${role.name}`)} | {t(`common.members.${role.name}`)} | ||||
| </span> | </span> | ||||
| <span className={`${selected ? 'font-medium' : 'font-normal'} capitalize block truncate`}> | |||||
| <span className={`${selected ? 'font-medium' : 'font-normal'} capitalize block text-gray-500`}> | |||||
| {role.description} | {role.description} | ||||
| </span> | </span> | ||||
| </div> | </div> |
| const RoleMap = { | const RoleMap = { | ||||
| owner: t('common.members.owner'), | owner: t('common.members.owner'), | ||||
| admin: t('common.members.admin'), | admin: t('common.members.admin'), | ||||
| editor: t('common.members.editor'), | |||||
| normal: t('common.members.normal'), | normal: t('common.members.normal'), | ||||
| } | } | ||||
| const { notify } = useContext(ToastContext) | const { notify } = useContext(ToastContext) | ||||
| > | > | ||||
| <div className="px-1 py-1"> | <div className="px-1 py-1"> | ||||
| { | { | ||||
| ['admin', 'normal'].map(role => ( | |||||
| ['admin', 'editor', 'normal'].map(role => ( | |||||
| <Menu.Item key={role}> | <Menu.Item key={role}> | ||||
| <div className={itemClassName} onClick={() => handleUpdateMemberRole(role)}> | <div className={itemClassName} onClick={() => handleUpdateMemberRole(role)}> | ||||
| { | { |
| } from '@/app/components/base/portal-to-follow-elem' | } from '@/app/components/base/portal-to-follow-elem' | ||||
| import { useToastContext } from '@/app/components/base/toast' | import { useToastContext } from '@/app/components/base/toast' | ||||
| import ConfirmCommon from '@/app/components/base/confirm/common' | import ConfirmCommon from '@/app/components/base/confirm/common' | ||||
| import { useAppContext } from '@/context/app-context' | |||||
| type ModelModalProps = { | type ModelModalProps = { | ||||
| provider: ModelProvider | provider: ModelProvider | ||||
| providerFormSchemaPredefined && provider.custom_configuration.status === CustomConfigurationStatusEnum.active, | providerFormSchemaPredefined && provider.custom_configuration.status === CustomConfigurationStatusEnum.active, | ||||
| currentCustomConfigurationModelFixedFields, | currentCustomConfigurationModelFixedFields, | ||||
| ) | ) | ||||
| const isEditMode = !!formSchemasValue | |||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const isEditMode = !!formSchemasValue && isCurrentWorkspaceManager | |||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { notify } = useToastContext() | const { notify } = useToastContext() | ||||
| const language = useLanguage() | const language = useLanguage() | ||||
| || filteredRequiredFormSchemas.some(item => value[item.variable] === undefined) | || filteredRequiredFormSchemas.some(item => value[item.variable] === undefined) | ||||
| || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2) | || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2) | ||||
| } | } | ||||
| > | > | ||||
| {t('common.operation.save')} | {t('common.operation.save')} | ||||
| </Button> | </Button> |
| import { useProviderContext } from '@/context/provider-context' | import { useProviderContext } from '@/context/provider-context' | ||||
| import { updateDefaultModel } from '@/service/common' | import { updateDefaultModel } from '@/service/common' | ||||
| import { useToastContext } from '@/app/components/base/toast' | import { useToastContext } from '@/app/components/base/toast' | ||||
| import { useAppContext } from '@/context/app-context' | |||||
| type SystemModelSelectorProps = { | type SystemModelSelectorProps = { | ||||
| textGenerationDefaultModel: DefaultModelResponse | undefined | textGenerationDefaultModel: DefaultModelResponse | undefined | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { notify } = useToastContext() | const { notify } = useToastContext() | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { textGenerationModelList } = useProviderContext() | const { textGenerationModelList } = useProviderContext() | ||||
| const updateModelList = useUpdateModelList() | const updateModelList = useUpdateModelList() | ||||
| const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) | const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) | ||||
| type='primary' | type='primary' | ||||
| className='!h-8 !text-[13px]' | className='!h-8 !text-[13px]' | ||||
| onClick={handleSave} | onClick={handleSave} | ||||
| disabled={!isCurrentWorkspaceManager} | |||||
| > | > | ||||
| {t('common.operation.save')} | {t('common.operation.save')} | ||||
| </Button> | </Button> |
| const AppNav = () => { | const AppNav = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { appId } = useParams() | const { appId } = useParams() | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const appDetail = useAppStore(state => state.appDetail) | const appDetail = useAppStore(state => state.appDetail) | ||||
| const [showNewAppDialog, setShowNewAppDialog] = useState(false) | const [showNewAppDialog, setShowNewAppDialog] = useState(false) | ||||
| const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false) | const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false) | ||||
| if (appsData) { | if (appsData) { | ||||
| const appItems = flatten(appsData?.map(appData => appData.data)) | const appItems = flatten(appsData?.map(appData => appData.data)) | ||||
| const navItems = appItems.map((app) => { | const navItems = appItems.map((app) => { | ||||
| const link = ((isCurrentWorkspaceManager, app) => { | |||||
| if (!isCurrentWorkspaceManager) { | |||||
| const link = ((isCurrentWorkspaceEditor, app) => { | |||||
| if (!isCurrentWorkspaceEditor) { | |||||
| return `/app/${app.id}/overview` | return `/app/${app.id}/overview` | ||||
| } | } | ||||
| else { | else { | ||||
| else | else | ||||
| return `/app/${app.id}/configuration` | return `/app/${app.id}/configuration` | ||||
| } | } | ||||
| })(isCurrentWorkspaceManager, app) | |||||
| })(isCurrentWorkspaceEditor, app) | |||||
| return { | return { | ||||
| id: app.id, | id: app.id, | ||||
| icon: app.icon, | icon: app.icon, | ||||
| }) | }) | ||||
| setNavItems(navItems) | setNavItems(navItems) | ||||
| } | } | ||||
| }, [appsData, isCurrentWorkspaceManager, setNavItems]) | |||||
| }, [appsData, isCurrentWorkspaceEditor, setNavItems]) | |||||
| // update current app name | // update current app name | ||||
| useEffect(() => { | useEffect(() => { |
| export default function AppSelector({ appItems, curApp }: IAppSelectorProps) { | export default function AppSelector({ appItems, curApp }: IAppSelectorProps) { | ||||
| const router = useRouter() | const router = useRouter() | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const [showNewAppDialog, setShowNewAppDialog] = useState(false) | const [showNewAppDialog, setShowNewAppDialog] = useState(false) | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| appItems.map((app: AppDetailResponse) => ( | appItems.map((app: AppDetailResponse) => ( | ||||
| <Menu.Item key={app.id}> | <Menu.Item key={app.id}> | ||||
| <div className={itemClassName} onClick={() => | <div className={itemClassName} onClick={() => | ||||
| router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`) | |||||
| router.push(`/app/${app.id}/${isCurrentWorkspaceEditor ? 'configuration' : 'overview'}`) | |||||
| }> | }> | ||||
| <div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'> | <div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'> | ||||
| <AppIcon size='tiny' /> | <AppIcon size='tiny' /> | ||||
| )) | )) | ||||
| } | } | ||||
| </div>)} | </div>)} | ||||
| {isCurrentWorkspaceManager && <Menu.Item> | |||||
| {isCurrentWorkspaceEditor && <Menu.Item> | |||||
| <div className='p-1' onClick={() => setShowNewAppDialog(true)}> | <div className='p-1' onClick={() => setShowNewAppDialog(true)}> | ||||
| <div | <div | ||||
| className='flex items-center h-12 rounded-lg cursor-pointer hover:bg-gray-100' | className='flex items-center h-12 rounded-lg cursor-pointer hover:bg-gray-100' |
| ` | ` | ||||
| const Header = () => { | const Header = () => { | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const selectedSegment = useSelectedLayoutSegment() | const selectedSegment = useSelectedLayoutSegment() | ||||
| const media = useBreakpoints() | const media = useBreakpoints() | ||||
| <div className='flex items-center'> | <div className='flex items-center'> | ||||
| <ExploreNav className={navClassName} /> | <ExploreNav className={navClassName} /> | ||||
| <AppNav /> | <AppNav /> | ||||
| {isCurrentWorkspaceManager && <DatasetNav />} | |||||
| {isCurrentWorkspaceEditor && <DatasetNav />} | |||||
| <ToolsNav className={navClassName} /> | <ToolsNav className={navClassName} /> | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| <div className='w-full flex flex-col p-2 gap-y-1'> | <div className='w-full flex flex-col p-2 gap-y-1'> | ||||
| <ExploreNav className={navClassName} /> | <ExploreNav className={navClassName} /> | ||||
| <AppNav /> | <AppNav /> | ||||
| {isCurrentWorkspaceManager && <DatasetNav />} | |||||
| {isCurrentWorkspaceEditor && <DatasetNav />} | |||||
| <ToolsNav className={navClassName} /> | <ToolsNav className={navClassName} /> | ||||
| </div> | </div> | ||||
| )} | )} |
| const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: INavSelectorProps) => { | const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: INavSelectorProps) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const router = useRouter() | const router = useRouter() | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { isCurrentWorkspaceEditor } = useAppContext() | |||||
| const setAppDetail = useAppStore(state => state.setAppDetail) | const setAppDetail = useAppStore(state => state.setAppDetail) | ||||
| const handleScroll = useCallback(debounce((e) => { | const handleScroll = useCallback(debounce((e) => { | ||||
| </div> | </div> | ||||
| </Menu.Button> | </Menu.Button> | ||||
| )} | )} | ||||
| {isApp && isCurrentWorkspaceManager && ( | |||||
| {isApp && isCurrentWorkspaceEditor && ( | |||||
| <Menu as="div" className="relative w-full h-full"> | <Menu as="div" className="relative w-full h-full"> | ||||
| {({ open }) => ( | {({ open }) => ( | ||||
| <> | <> |
| import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' | import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' | ||||
| import { createCustomCollection } from '@/service/tools' | import { createCustomCollection } from '@/service/tools' | ||||
| import Toast from '@/app/components/base/toast' | import Toast from '@/app/components/base/toast' | ||||
| import { useAppContext } from '@/context/app-context' | |||||
| type Props = { | type Props = { | ||||
| onRefreshData: () => void | onRefreshData: () => void | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { locale } = useContext(I18n) | const { locale } = useContext(I18n) | ||||
| const language = getLanguage(locale) | const language = getLanguage(locale) | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const linkUrl = useMemo(() => { | const linkUrl = useMemo(() => { | ||||
| if (language.startsWith('zh_')) | if (language.startsWith('zh_')) | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| <div className='flex flex-col col-span-1 bg-gray-200 border-[0.5px] border-black/5 rounded-xl min-h-[160px] transition-all duration-200 ease-in-out cursor-pointer hover:bg-gray-50 hover:shadow-lg'> | |||||
| <div className='group grow rounded-t-xl hover:bg-white' onClick={() => setIsShowEditCustomCollectionModal(true)}> | |||||
| <div className='shrink-0 flex items-center p-4 pb-3'> | |||||
| <div className='w-10 h-10 flex items-center justify-center border border-gray-200 bg-gray-100 rounded-lg group-hover:border-primary-100 group-hover:bg-primary-50'> | |||||
| <Plus className='w-4 h-4 text-gray-500 group-hover:text-primary-600'/> | |||||
| {isCurrentWorkspaceManager && ( | |||||
| <div className='flex flex-col col-span-1 bg-gray-200 border-[0.5px] border-black/5 rounded-xl min-h-[160px] transition-all duration-200 ease-in-out cursor-pointer hover:bg-gray-50 hover:shadow-lg'> | |||||
| <div className='group grow rounded-t-xl hover:bg-white' onClick={() => setIsShowEditCustomCollectionModal(true)}> | |||||
| <div className='shrink-0 flex items-center p-4 pb-3'> | |||||
| <div className='w-10 h-10 flex items-center justify-center border border-gray-200 bg-gray-100 rounded-lg group-hover:border-primary-100 group-hover:bg-primary-50'> | |||||
| <Plus className='w-4 h-4 text-gray-500 group-hover:text-primary-600'/> | |||||
| </div> | |||||
| <div className='ml-3 text-sm font-semibold leading-5 text-gray-800 group-hover:text-primary-600'>{t('tools.createCustomTool')}</div> | |||||
| </div> | </div> | ||||
| <div className='ml-3 text-sm font-semibold leading-5 text-gray-800 group-hover:text-primary-600'>{t('tools.createCustomTool')}</div> | |||||
| </div> | |||||
| <div className='px-4 py-3 rounded-b-xl border-t-[0.5px] border-black/5 text-gray-500 hover:text-[#155EEF] hover:bg-white'> | |||||
| <a href={linkUrl} target='_blank' rel='noopener noreferrer' className='flex items-center space-x-1'> | |||||
| <BookOpen01 className='shrink-0 w-3 h-3' /> | |||||
| <div className='grow leading-[18px] text-xs font-normal truncate' title={t('tools.customToolTip') || ''}>{t('tools.customToolTip')}</div> | |||||
| <ArrowUpRight className='shrink-0 w-3 h-3' /> | |||||
| </a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className='px-4 py-3 rounded-b-xl border-t-[0.5px] border-black/5 text-gray-500 hover:text-[#155EEF] hover:bg-white'> | |||||
| <a href={linkUrl} target='_blank' rel='noopener noreferrer' className='flex items-center space-x-1'> | |||||
| <BookOpen01 className='shrink-0 w-3 h-3' /> | |||||
| <div className='grow leading-[18px] text-xs font-normal truncate' title={t('tools.customToolTip') || ''}>{t('tools.customToolTip')}</div> | |||||
| <ArrowUpRight className='shrink-0 w-3 h-3' /> | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| )} | |||||
| {isShowEditCollectionToolModal && ( | {isShowEditCollectionToolModal && ( | ||||
| <EditCustomToolModal | <EditCustomToolModal | ||||
| payload={null} | payload={null} |
| import { useProviderContext } from '@/context/provider-context' | import { useProviderContext } from '@/context/provider-context' | ||||
| import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | ||||
| import Loading from '@/app/components/base/loading' | import Loading from '@/app/components/base/loading' | ||||
| import { useAppContext } from '@/context/app-context' | |||||
| type Props = { | type Props = { | ||||
| collection: Collection | collection: Collection | ||||
| const isAuthed = collection.is_team_authorization | const isAuthed = collection.is_team_authorization | ||||
| const isBuiltIn = collection.type === CollectionType.builtIn | const isBuiltIn = collection.type === CollectionType.builtIn | ||||
| const isModel = collection.type === CollectionType.model | const isModel = collection.type === CollectionType.model | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const [isDetailLoading, setIsDetailLoading] = useState(false) | const [isDetailLoading, setIsDetailLoading] = useState(false) | ||||
| if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) | if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) | ||||
| showSettingAuthModal() | showSettingAuthModal() | ||||
| }} | }} | ||||
| disabled={!isCurrentWorkspaceManager} | |||||
| > | > | ||||
| {isAuthed && <Indicator className='mr-2' color={'green'} />} | {isAuthed && <Indicator className='mr-2' color={'green'} />} | ||||
| <div className={cn('text-white leading-[18px] text-[13px] font-medium', isAuthed && '!text-gray-700')}> | <div className={cn('text-white leading-[18px] text-[13px] font-medium', isAuthed && '!text-gray-700')}> | ||||
| <Button | <Button | ||||
| className={cn('shrink-0 my-3 w-[183px] flex items-center bg-white')} | className={cn('shrink-0 my-3 w-[183px] flex items-center bg-white')} | ||||
| onClick={() => setIsShowEditWorkflowToolModal(true)} | onClick={() => setIsShowEditWorkflowToolModal(true)} | ||||
| disabled={!isCurrentWorkspaceManager} | |||||
| > | > | ||||
| <div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div> | <div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div> | ||||
| </Button> | </Button> |
| import Loading from '@/app/components/base/loading' | import Loading from '@/app/components/base/loading' | ||||
| import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' | import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' | ||||
| import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' | import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' | ||||
| import { useAppContext } from '@/context/app-context' | |||||
| type Props = { | type Props = { | ||||
| collection: Collection | collection: Collection | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const [credentialSchema, setCredentialSchema] = useState<any>(null) | const [credentialSchema, setCredentialSchema] = useState<any>(null) | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const { name: collectionName } = collection | const { name: collectionName } = collection | ||||
| const [tempCredential, setTempCredential] = React.useState<any>({}) | const [tempCredential, setTempCredential] = React.useState<any>({}) | ||||
| useEffect(() => { | useEffect(() => { |
| import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools' | import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools' | ||||
| import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types' | import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types' | ||||
| import type { InputVar } from '@/app/components/workflow/types' | import type { InputVar } from '@/app/components/workflow/types' | ||||
| import { useAppContext } from '@/context/app-context' | |||||
| type Props = { | type Props = { | ||||
| disabled: boolean | disabled: boolean | ||||
| const [showModal, setShowModal] = useState(false) | const [showModal, setShowModal] = useState(false) | ||||
| const [isLoading, setIsLoading] = useState(false) | const [isLoading, setIsLoading] = useState(false) | ||||
| const [detail, setDetail] = useState<WorkflowToolProviderResponse>() | const [detail, setDetail] = useState<WorkflowToolProviderResponse>() | ||||
| const { isCurrentWorkspaceManager } = useAppContext() | |||||
| const outdated = useMemo(() => { | const outdated = useMemo(() => { | ||||
| if (!detail) | if (!detail) | ||||
| disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'cursor-pointer', | disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'cursor-pointer', | ||||
| !published && 'hover:bg-primary-50', | !published && 'hover:bg-primary-50', | ||||
| )}> | )}> | ||||
| <div | |||||
| className='flex justify-start items-center gap-2 px-2.5 py-2' | |||||
| onClick={() => !published && setShowModal(true)} | |||||
| > | |||||
| <Tools className={cn('relative w-4 h-4', !published && 'group-hover:text-primary-600')}/> | |||||
| <div title={t('workflow.common.workflowAsTool') || ''} className={cn('grow shrink basis-0 text-[13px] font-medium leading-[18px] truncate', !published && 'group-hover:text-primary-600')}>{t('workflow.common.workflowAsTool')}</div> | |||||
| {!published && ( | |||||
| <span className='shrink-0 px-1 border border-black/8 rounded-[5px] bg-white text-[10px] font-medium leading-[18px] text-gray-500'>{t('workflow.common.configureRequired').toLocaleUpperCase()}</span> | |||||
| {isCurrentWorkspaceManager | |||||
| ? ( | |||||
| <div | |||||
| className='flex justify-start items-center gap-2 px-2.5 py-2' | |||||
| onClick={() => !published && setShowModal(true)} | |||||
| > | |||||
| <Tools className={cn('relative w-4 h-4', !published && 'group-hover:text-primary-600')} /> | |||||
| <div title={t('workflow.common.workflowAsTool') || ''} className={cn('grow shrink basis-0 text-[13px] font-medium leading-[18px] truncate', !published && 'group-hover:text-primary-600')}>{t('workflow.common.workflowAsTool')}</div> | |||||
| {!published && ( | |||||
| <span className='shrink-0 px-1 border border-black/8 rounded-[5px] bg-white text-[10px] font-medium leading-[18px] text-gray-500'>{t('workflow.common.configureRequired').toLocaleUpperCase()}</span> | |||||
| )} | |||||
| </div>) | |||||
| : ( | |||||
| <div | |||||
| className='flex justify-start items-center gap-2 px-2.5 py-2' | |||||
| > | |||||
| <Tools className='w-4 h-4 text-gray-500' /> | |||||
| <div title={t('workflow.common.workflowAsTool') || ''} className='grow shrink basis-0 text-[13px] font-medium leading-[18px] truncate text-gray-500'>{t('workflow.common.workflowAsTool')}</div> | |||||
| </div> | |||||
| )} | )} | ||||
| </div> | |||||
| {published && ( | {published && ( | ||||
| <div className='px-2.5 py-2 border-t-[0.5px] border-black/5'> | <div className='px-2.5 py-2 border-t-[0.5px] border-black/5'> | ||||
| <div className='flex justify-between'> | <div className='flex justify-between'> | ||||
| <Button | <Button | ||||
| className='px-2 w-[140px] py-0 h-6 shadow-xs rounded-md text-xs font-medium text-gray-700 border-[0.5px] bg-white border-gray-200' | className='px-2 w-[140px] py-0 h-6 shadow-xs rounded-md text-xs font-medium text-gray-700 border-[0.5px] bg-white border-gray-200' | ||||
| onClick={() => setShowModal(true)} | onClick={() => setShowModal(true)} | ||||
| disabled={!isCurrentWorkspaceManager} | |||||
| > | > | ||||
| {t('workflow.common.configure')} | {t('workflow.common.configure')} | ||||
| {outdated && <Indicator className='ml-1' color={'yellow'} />} | {outdated && <Indicator className='ml-1' color={'yellow'} />} | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {published && isLoading && <div className='pt-2'><Loading type='app'/></div>} | |||||
| {published && isLoading && <div className='pt-2'><Loading type='app' /></div>} | |||||
| </div> | </div> | ||||
| {showModal && ( | {showModal && ( | ||||
| <WorkflowToolModal | <WorkflowToolModal |
| currentWorkspace: ICurrentWorkspace | currentWorkspace: ICurrentWorkspace | ||||
| isCurrentWorkspaceManager: boolean | isCurrentWorkspaceManager: boolean | ||||
| isCurrentWorkspaceOwner: boolean | isCurrentWorkspaceOwner: boolean | ||||
| isCurrentWorkspaceEditor: boolean | |||||
| mutateCurrentWorkspace: VoidFunction | mutateCurrentWorkspace: VoidFunction | ||||
| pageContainerRef: React.RefObject<HTMLDivElement> | pageContainerRef: React.RefObject<HTMLDivElement> | ||||
| langeniusVersionInfo: LangGeniusVersionResponse | langeniusVersionInfo: LangGeniusVersionResponse | ||||
| currentWorkspace: initialWorkspaceInfo, | currentWorkspace: initialWorkspaceInfo, | ||||
| isCurrentWorkspaceManager: false, | isCurrentWorkspaceManager: false, | ||||
| isCurrentWorkspaceOwner: false, | isCurrentWorkspaceOwner: false, | ||||
| isCurrentWorkspaceEditor: false, | |||||
| mutateUserProfile: () => { }, | mutateUserProfile: () => { }, | ||||
| mutateCurrentWorkspace: () => { }, | mutateCurrentWorkspace: () => { }, | ||||
| pageContainerRef: createRef(), | pageContainerRef: createRef(), | ||||
| const [currentWorkspace, setCurrentWorkspace] = useState<ICurrentWorkspace>(initialWorkspaceInfo) | const [currentWorkspace, setCurrentWorkspace] = useState<ICurrentWorkspace>(initialWorkspaceInfo) | ||||
| const isCurrentWorkspaceManager = useMemo(() => ['owner', 'admin'].includes(currentWorkspace.role), [currentWorkspace.role]) | const isCurrentWorkspaceManager = useMemo(() => ['owner', 'admin'].includes(currentWorkspace.role), [currentWorkspace.role]) | ||||
| const isCurrentWorkspaceOwner = useMemo(() => currentWorkspace.role === 'owner', [currentWorkspace.role]) | const isCurrentWorkspaceOwner = useMemo(() => currentWorkspace.role === 'owner', [currentWorkspace.role]) | ||||
| const isCurrentWorkspaceEditor = useMemo(() => ['owner', 'admin', 'editor'].includes(currentWorkspace.role), [currentWorkspace.role]) | |||||
| const updateUserProfileAndVersion = useCallback(async () => { | const updateUserProfileAndVersion = useCallback(async () => { | ||||
| if (userProfileResponse && !userProfileResponse.bodyUsed) { | if (userProfileResponse && !userProfileResponse.bodyUsed) { | ||||
| const result = await userProfileResponse.json() | const result = await userProfileResponse.json() | ||||
| currentWorkspace, | currentWorkspace, | ||||
| isCurrentWorkspaceManager, | isCurrentWorkspaceManager, | ||||
| isCurrentWorkspaceOwner, | isCurrentWorkspaceOwner, | ||||
| isCurrentWorkspaceEditor, | |||||
| mutateCurrentWorkspace, | mutateCurrentWorkspace, | ||||
| }}> | }}> | ||||
| <div className='flex flex-col h-full overflow-y-auto'> | <div className='flex flex-col h-full overflow-y-auto'> |
| adminTip: 'Kann Apps erstellen & Team-Einstellungen verwalten', | adminTip: 'Kann Apps erstellen & Team-Einstellungen verwalten', | ||||
| normal: 'Normal', | normal: 'Normal', | ||||
| normalTip: 'Kann nur Apps verwenden, kann keine Apps erstellen', | normalTip: 'Kann nur Apps verwenden, kann keine Apps erstellen', | ||||
| editor: 'Editor', | |||||
| editorTip: 'Kann Apps erstellen & bearbeiten', | |||||
| inviteTeamMember: 'Teammitglied hinzufügen', | inviteTeamMember: 'Teammitglied hinzufügen', | ||||
| inviteTeamMemberTip: 'Sie können direkt nach der Anmeldung auf Ihre Teamdaten zugreifen.', | inviteTeamMemberTip: 'Sie können direkt nach der Anmeldung auf Ihre Teamdaten zugreifen.', | ||||
| email: 'E-Mail', | email: 'E-Mail', | ||||
| removeFromTeamTip: 'Wird den Teamzugang entfernen', | removeFromTeamTip: 'Wird den Teamzugang entfernen', | ||||
| setAdmin: 'Als Administrator einstellen', | setAdmin: 'Als Administrator einstellen', | ||||
| setMember: 'Als normales Mitglied einstellen', | setMember: 'Als normales Mitglied einstellen', | ||||
| setEditor: 'Als Editor einstellen', | |||||
| disinvite: 'Einladung widerrufen', | disinvite: 'Einladung widerrufen', | ||||
| deleteMember: 'Mitglied löschen', | deleteMember: 'Mitglied löschen', | ||||
| you: '(Du)', | you: '(Du)', |
| adminTip: 'Can build apps & manage team settings', | adminTip: 'Can build apps & manage team settings', | ||||
| normal: 'Normal', | normal: 'Normal', | ||||
| normalTip: 'Only can use apps, can not build apps', | normalTip: 'Only can use apps, can not build apps', | ||||
| builder: 'Builder', | |||||
| builderTip: 'Can build & edit own apps', | |||||
| editor: 'Editor', | |||||
| editorTip: 'Can build & edit apps', | |||||
| inviteTeamMember: 'Add team member', | inviteTeamMember: 'Add team member', | ||||
| inviteTeamMemberTip: 'They can access your team data directly after signing in.', | inviteTeamMemberTip: 'They can access your team data directly after signing in.', | ||||
| email: 'Email', | email: 'Email', | ||||
| removeFromTeamTip: 'Will remove team access', | removeFromTeamTip: 'Will remove team access', | ||||
| setAdmin: 'Set as administrator', | setAdmin: 'Set as administrator', | ||||
| setMember: 'Set to ordinary member', | setMember: 'Set to ordinary member', | ||||
| setBuilder: 'Set as builder', | |||||
| setEditor: 'Set as editor', | |||||
| disinvite: 'Cancel the invitation', | disinvite: 'Cancel the invitation', | ||||
| deleteMember: 'Delete Member', | deleteMember: 'Delete Member', | ||||
| you: '(You)', | you: '(You)', |
| adminTip: 'Peut construire des applications & gérer les paramètres de l\'équipe', | adminTip: 'Peut construire des applications & gérer les paramètres de l\'équipe', | ||||
| normal: 'Normal', | normal: 'Normal', | ||||
| normalTip: 'Peut seulement utiliser des applications, ne peut pas construire des applications', | normalTip: 'Peut seulement utiliser des applications, ne peut pas construire des applications', | ||||
| editor: 'Éditeur', | |||||
| editorTip: 'Peut construire des applications, mais ne peut pas gérer les paramètres de l\'équipe', | |||||
| inviteTeamMember: 'Ajouter un membre de l\'équipe', | inviteTeamMember: 'Ajouter un membre de l\'équipe', | ||||
| inviteTeamMemberTip: 'Ils peuvent accéder directement à vos données d\'équipe après s\'être connectés.', | inviteTeamMemberTip: 'Ils peuvent accéder directement à vos données d\'équipe après s\'être connectés.', | ||||
| email: 'Courrier électronique', | email: 'Courrier électronique', | ||||
| removeFromTeamTip: 'Supprimera l\'accès de l\'équipe', | removeFromTeamTip: 'Supprimera l\'accès de l\'équipe', | ||||
| setAdmin: 'Définir comme administrateur', | setAdmin: 'Définir comme administrateur', | ||||
| setMember: 'Définir en tant que membre ordinaire', | setMember: 'Définir en tant que membre ordinaire', | ||||
| setEditor: 'Définir en tant qu\'éditeur', | |||||
| disinvite: 'Annuler l\'invitation', | disinvite: 'Annuler l\'invitation', | ||||
| deleteMember: 'Supprimer Membre', | deleteMember: 'Supprimer Membre', | ||||
| you: '(Vous)', | you: '(Vous)', |
| adminTip: 'アプリの構築およびチーム設定の管理ができます', | adminTip: 'アプリの構築およびチーム設定の管理ができます', | ||||
| normal: '通常', | normal: '通常', | ||||
| normalTip: 'アプリの使用のみが可能で、アプリの構築はできません', | normalTip: 'アプリの使用のみが可能で、アプリの構築はできません', | ||||
| editor: 'エディター', | |||||
| editorTip: 'アプリの構築ができますが、チーム設定の管理はできません', | |||||
| inviteTeamMember: 'チームメンバーを招待する', | inviteTeamMember: 'チームメンバーを招待する', | ||||
| inviteTeamMemberTip: '彼らはサインイン後、直接あなたのチームデータにアクセスできます。', | inviteTeamMemberTip: '彼らはサインイン後、直接あなたのチームデータにアクセスできます。', | ||||
| email: 'メール', | email: 'メール', | ||||
| removeFromTeamTip: 'チームへのアクセスが削除されます', | removeFromTeamTip: 'チームへのアクセスが削除されます', | ||||
| setAdmin: '管理者に設定', | setAdmin: '管理者に設定', | ||||
| setMember: '通常のメンバーに設定', | setMember: '通常のメンバーに設定', | ||||
| setEditor: 'エディターに設定', | |||||
| disinvite: '招待をキャンセル', | disinvite: '招待をキャンセル', | ||||
| deleteMember: 'メンバーを削除', | deleteMember: 'メンバーを削除', | ||||
| you: '(あなた)', | you: '(あなた)', |
| adminTip: '앱 빌드 및 팀 설정 관리 가능', | adminTip: '앱 빌드 및 팀 설정 관리 가능', | ||||
| normal: '일반', | normal: '일반', | ||||
| normalTip: '앱 사용만 가능하고 앱 빌드는 불가능', | normalTip: '앱 사용만 가능하고 앱 빌드는 불가능', | ||||
| editor: '편집자', | |||||
| editorTip: '앱 빌드만 가능하고 팀 설정 관리 불가능', | |||||
| inviteTeamMember: '팀 멤버 초대', | inviteTeamMember: '팀 멤버 초대', | ||||
| inviteTeamMemberTip: '로그인 후에 바로 팀 데이터에 액세스할 수 있습니다.', | inviteTeamMemberTip: '로그인 후에 바로 팀 데이터에 액세스할 수 있습니다.', | ||||
| email: '이메일', | email: '이메일', | ||||
| removeFromTeamTip: '팀 액세스가 제거됩니다', | removeFromTeamTip: '팀 액세스가 제거됩니다', | ||||
| setAdmin: '관리자 설정', | setAdmin: '관리자 설정', | ||||
| setMember: '일반 멤버 설정', | setMember: '일반 멤버 설정', | ||||
| setEditor: '편집자 설정', | |||||
| disinvite: '초대 취소', | disinvite: '초대 취소', | ||||
| deleteMember: '멤버 삭제', | deleteMember: '멤버 삭제', | ||||
| you: '(나)', | you: '(나)', |
| adminTip: 'Może tworzyć aplikacje i zarządzać ustawieniami zespołu', | adminTip: 'Może tworzyć aplikacje i zarządzać ustawieniami zespołu', | ||||
| normal: 'Normalny', | normal: 'Normalny', | ||||
| normalTip: 'Może tylko korzystać z aplikacji, nie może tworzyć aplikacji', | normalTip: 'Może tylko korzystać z aplikacji, nie może tworzyć aplikacji', | ||||
| editor: 'Edytor', | |||||
| editorTip: 'Może tworzyć i edytować aplikacje, ale nie zarządzać ustawieniami zespołu', | |||||
| inviteTeamMember: 'Dodaj członka zespołu', | inviteTeamMember: 'Dodaj członka zespołu', | ||||
| inviteTeamMemberTip: | inviteTeamMemberTip: | ||||
| 'Mogą uzyskać bezpośredni dostęp do danych Twojego zespołu po zalogowaniu.', | 'Mogą uzyskać bezpośredni dostęp do danych Twojego zespołu po zalogowaniu.', | ||||
| removeFromTeamTip: 'Usunie dostęp do zespołu', | removeFromTeamTip: 'Usunie dostęp do zespołu', | ||||
| setAdmin: 'Ustaw jako administratora', | setAdmin: 'Ustaw jako administratora', | ||||
| setMember: 'Ustaw jako zwykłego członka', | setMember: 'Ustaw jako zwykłego członka', | ||||
| setEditor: 'Ustaw jako edytora', | |||||
| disinvite: 'Anuluj zaproszenie', | disinvite: 'Anuluj zaproszenie', | ||||
| deleteMember: 'Usuń członka', | deleteMember: 'Usuń członka', | ||||
| you: '(Ty)', | you: '(Ty)', |
| adminTip: 'Pode criar aplicativos e gerenciar configurações da equipe', | adminTip: 'Pode criar aplicativos e gerenciar configurações da equipe', | ||||
| normal: 'Normal', | normal: 'Normal', | ||||
| normalTip: 'Só pode usar aplicativos, não pode criar aplicativos', | normalTip: 'Só pode usar aplicativos, não pode criar aplicativos', | ||||
| editor: 'Editor', | |||||
| editorTip: 'Pode editar aplicativos, mas não pode gerenciar configurações da equipe', | |||||
| inviteTeamMember: 'Adicionar membro da equipe', | inviteTeamMember: 'Adicionar membro da equipe', | ||||
| inviteTeamMemberTip: 'Eles podem acessar os dados da sua equipe diretamente após fazer login.', | inviteTeamMemberTip: 'Eles podem acessar os dados da sua equipe diretamente após fazer login.', | ||||
| email: 'E-mail', | email: 'E-mail', | ||||
| removeFromTeamTip: 'Removerá o acesso da equipe', | removeFromTeamTip: 'Removerá o acesso da equipe', | ||||
| setAdmin: 'Definir como administrador', | setAdmin: 'Definir como administrador', | ||||
| setMember: 'Definir como membro comum', | setMember: 'Definir como membro comum', | ||||
| setEditor: 'Definir como editor', | |||||
| disinvite: 'Cancelar o convite', | disinvite: 'Cancelar o convite', | ||||
| deleteMember: 'Excluir Membro', | deleteMember: 'Excluir Membro', | ||||
| you: '(Você)', | you: '(Você)', |
| adminTip: 'Poate construi aplicații și gestiona setările echipei', | adminTip: 'Poate construi aplicații și gestiona setările echipei', | ||||
| normal: 'Normal', | normal: 'Normal', | ||||
| normalTip: 'Poate doar utiliza aplicații, nu poate construi aplicații', | normalTip: 'Poate doar utiliza aplicații, nu poate construi aplicații', | ||||
| editor: 'Editor', | |||||
| editorTip: 'Poate construi aplicații, dar nu poate gestiona setările echipei', | |||||
| inviteTeamMember: 'Adaugă membru în echipă', | inviteTeamMember: 'Adaugă membru în echipă', | ||||
| inviteTeamMemberTip: 'Pot accesa direct datele echipei dvs. după autentificare.', | inviteTeamMemberTip: 'Pot accesa direct datele echipei dvs. după autentificare.', | ||||
| email: 'Email', | email: 'Email', | ||||
| removeFromTeamTip: 'Va elimina accesul la echipă', | removeFromTeamTip: 'Va elimina accesul la echipă', | ||||
| setAdmin: 'Setează ca administrator', | setAdmin: 'Setează ca administrator', | ||||
| setMember: 'Setează ca membru obișnuit', | setMember: 'Setează ca membru obișnuit', | ||||
| setEditor: 'Setează ca editor', | |||||
| disinvite: 'Anulează invitația', | disinvite: 'Anulează invitația', | ||||
| deleteMember: 'Șterge membru', | deleteMember: 'Șterge membru', | ||||
| you: '(Dvs.)', | you: '(Dvs.)', |
| adminTip: 'Може створювати програми та керувати налаштуваннями команди', | adminTip: 'Може створювати програми та керувати налаштуваннями команди', | ||||
| normal: 'Звичайний', | normal: 'Звичайний', | ||||
| normalTip: 'Може лише використовувати програми, не може створювати програми', | normalTip: 'Може лише використовувати програми, не може створювати програми', | ||||
| editor: 'Редактор', | |||||
| editorTip: 'Може створювати програми, але не може керувати налаштуваннями команди', | |||||
| inviteTeamMember: 'Додати учасника команди', | inviteTeamMember: 'Додати учасника команди', | ||||
| inviteTeamMemberTip: 'Вони зможуть отримати доступ до даних вашої команди безпосередньо після входу.', | inviteTeamMemberTip: 'Вони зможуть отримати доступ до даних вашої команди безпосередньо після входу.', | ||||
| email: 'Електронна пошта', | email: 'Електронна пошта', | ||||
| removeFromTeamTip: 'Буде видалено доступ до команди', | removeFromTeamTip: 'Буде видалено доступ до команди', | ||||
| setAdmin: 'Призначити адміністратором', | setAdmin: 'Призначити адміністратором', | ||||
| setMember: 'Встановити як звичайного члена', | setMember: 'Встановити як звичайного члена', | ||||
| setEditor: 'Встановити як Редактор', | |||||
| disinvite: 'Скасувати запрошення', | disinvite: 'Скасувати запрошення', | ||||
| deleteMember: 'Видалити учасника', | deleteMember: 'Видалити учасника', | ||||
| you: '(Ви)', | you: '(Ви)', |
| adminTip: 'Có thể xây dựng ứng dụng và quản lý cài đặt nhóm', | adminTip: 'Có thể xây dựng ứng dụng và quản lý cài đặt nhóm', | ||||
| normal: 'Bình thường', | normal: 'Bình thường', | ||||
| normalTip: 'Chỉ có thể sử dụng ứng dụng, không thể xây dựng ứng dụng', | normalTip: 'Chỉ có thể sử dụng ứng dụng, không thể xây dựng ứng dụng', | ||||
| editor: 'Biên tập viên', | |||||
| editorTip: 'Chỉ có thể xây dựng ứng dụng, không thể quản lý cài đặt nhóm', | |||||
| inviteTeamMember: 'Mời thành viên nhóm', | inviteTeamMember: 'Mời thành viên nhóm', | ||||
| inviteTeamMemberTip: 'Sau khi đăng nhập, họ có thể truy cập trực tiếp vào dữ liệu nhóm của bạn.', | inviteTeamMemberTip: 'Sau khi đăng nhập, họ có thể truy cập trực tiếp vào dữ liệu nhóm của bạn.', | ||||
| email: 'Email', | email: 'Email', | ||||
| removeFromTeamTip: 'Sẽ xóa quyền truy cập nhóm', | removeFromTeamTip: 'Sẽ xóa quyền truy cập nhóm', | ||||
| setAdmin: 'Đặt làm quản trị viên', | setAdmin: 'Đặt làm quản trị viên', | ||||
| setMember: 'Đặt thành viên bình thường', | setMember: 'Đặt thành viên bình thường', | ||||
| setEditor: 'Đặt làm biên tập viên', | |||||
| disinvite: 'Hủy lời mời', | disinvite: 'Hủy lời mời', | ||||
| deleteMember: 'Xóa thành viên', | deleteMember: 'Xóa thành viên', | ||||
| you: '(Bạn)', | you: '(Bạn)', |
| adminTip: '能够建立应用程序和管理团队设置', | adminTip: '能够建立应用程序和管理团队设置', | ||||
| normal: '成员', | normal: '成员', | ||||
| normalTip: '只能使用应用程序,不能建立应用程序', | normalTip: '只能使用应用程序,不能建立应用程序', | ||||
| editor: '编辑', | |||||
| editorTip: '能够建立并编辑应用程序,不能管理团队设置', | |||||
| inviteTeamMember: '添加团队成员', | inviteTeamMember: '添加团队成员', | ||||
| inviteTeamMemberTip: '对方在登录后可以访问你的团队数据。', | inviteTeamMemberTip: '对方在登录后可以访问你的团队数据。', | ||||
| email: '邮箱', | email: '邮箱', | ||||
| removeFromTeamTip: '将取消团队访问', | removeFromTeamTip: '将取消团队访问', | ||||
| setAdmin: '设为管理员', | setAdmin: '设为管理员', | ||||
| setMember: '设为普通成员', | setMember: '设为普通成员', | ||||
| setEditor: '设为编辑', | |||||
| disinvite: '取消邀请', | disinvite: '取消邀请', | ||||
| deleteMember: '删除成员', | deleteMember: '删除成员', | ||||
| you: '(你)', | you: '(你)', |
| adminTip: '能夠建立應用程式和管理團隊設定', | adminTip: '能夠建立應用程式和管理團隊設定', | ||||
| normal: '成員', | normal: '成員', | ||||
| normalTip: '只能使用應用程式,不能建立應用程式', | normalTip: '只能使用應用程式,不能建立應用程式', | ||||
| editor: '編輯', | |||||
| editorTip: '能夠建立並編輯應用程式,不能管理團隊設定', | |||||
| inviteTeamMember: '新增團隊成員', | inviteTeamMember: '新增團隊成員', | ||||
| inviteTeamMemberTip: '對方在登入後可以訪問你的團隊資料。', | inviteTeamMemberTip: '對方在登入後可以訪問你的團隊資料。', | ||||
| email: '郵箱', | email: '郵箱', | ||||
| removeFromTeamTip: '將取消團隊訪問', | removeFromTeamTip: '將取消團隊訪問', | ||||
| setAdmin: '設為管理員', | setAdmin: '設為管理員', | ||||
| setMember: '設為普通成員', | setMember: '設為普通成員', | ||||
| setEditor: '設為編輯', | |||||
| disinvite: '取消邀請', | disinvite: '取消邀請', | ||||
| deleteMember: '刪除成員', | deleteMember: '刪除成員', | ||||
| you: '(你)', | you: '(你)', |
| export type Member = Pick<UserProfileResponse, 'id' | 'name' | 'email' | 'last_login_at' | 'created_at'> & { | export type Member = Pick<UserProfileResponse, 'id' | 'name' | 'email' | 'last_login_at' | 'created_at'> & { | ||||
| avatar: string | avatar: string | ||||
| status: 'pending' | 'active' | 'banned' | 'closed' | status: 'pending' | 'active' | 'banned' | 'closed' | ||||
| role: 'owner' | 'admin' | 'normal' | |||||
| role: 'owner' | 'admin' | 'editor' | 'normal' | |||||
| } | } | ||||
| export enum ProviderName { | export enum ProviderName { | ||||
| } | } | ||||
| export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & { | export type ICurrentWorkspace = Omit<IWorkspace, 'current'> & { | ||||
| role: 'normal' | 'admin' | 'owner' | |||||
| role: 'owner' | 'admin' | 'editor' | 'normal' | |||||
| providers: Provider[] | providers: Provider[] | ||||
| in_trail: boolean | in_trail: boolean | ||||
| trial_end_reason?: string | trial_end_reason?: string |
| export const getRedirection = ( | export const getRedirection = ( | ||||
| isCurrentWorkspaceManager: boolean, | |||||
| isCurrentWorkspaceEditor: boolean, | |||||
| app: any, | app: any, | ||||
| redirectionFunc: (href: string) => void, | redirectionFunc: (href: string) => void, | ||||
| ) => { | ) => { | ||||
| if (!isCurrentWorkspaceManager) { | |||||
| if (!isCurrentWorkspaceEditor) { | |||||
| redirectionFunc(`/app/${app.id}/overview`) | redirectionFunc(`/app/${app.id}/overview`) | ||||
| } | } | ||||
| else { | else { |