commands.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import base64
  2. import json
  3. import secrets
  4. import click
  5. from core.embedding.cached_embedding import CacheEmbedding
  6. from core.model_manager import ModelManager
  7. from core.model_runtime.entities.model_entities import ModelType
  8. from extensions.ext_database import db
  9. from flask import current_app
  10. from libs.helper import email as email_validate
  11. from libs.password import hash_password, password_pattern, valid_password
  12. from libs.rsa import generate_key_pair
  13. from models.account import Tenant
  14. from models.dataset import Dataset
  15. from models.model import Account
  16. from models.provider import Provider, ProviderModel
  17. from werkzeug.exceptions import NotFound
  18. @click.command('reset-password', help='Reset the account password.')
  19. @click.option('--email', prompt=True, help='The email address of the account whose password you need to reset')
  20. @click.option('--new-password', prompt=True, help='the new password.')
  21. @click.option('--password-confirm', prompt=True, help='the new password confirm.')
  22. def reset_password(email, new_password, password_confirm):
  23. """
  24. Reset password of owner account
  25. Only available in SELF_HOSTED mode
  26. """
  27. if str(new_password).strip() != str(password_confirm).strip():
  28. click.echo(click.style('sorry. The two passwords do not match.', fg='red'))
  29. return
  30. account = db.session.query(Account). \
  31. filter(Account.email == email). \
  32. one_or_none()
  33. if not account:
  34. click.echo(click.style('sorry. the account: [{}] not exist .'.format(email), fg='red'))
  35. return
  36. try:
  37. valid_password(new_password)
  38. except:
  39. click.echo(
  40. click.style('sorry. The passwords must match {} '.format(password_pattern), fg='red'))
  41. return
  42. # generate password salt
  43. salt = secrets.token_bytes(16)
  44. base64_salt = base64.b64encode(salt).decode()
  45. # encrypt password with salt
  46. password_hashed = hash_password(new_password, salt)
  47. base64_password_hashed = base64.b64encode(password_hashed).decode()
  48. account.password = base64_password_hashed
  49. account.password_salt = base64_salt
  50. db.session.commit()
  51. click.echo(click.style('Congratulations!, password has been reset.', fg='green'))
  52. @click.command('reset-email', help='Reset the account email.')
  53. @click.option('--email', prompt=True, help='The old email address of the account whose email you need to reset')
  54. @click.option('--new-email', prompt=True, help='the new email.')
  55. @click.option('--email-confirm', prompt=True, help='the new email confirm.')
  56. def reset_email(email, new_email, email_confirm):
  57. """
  58. Replace account email
  59. :return:
  60. """
  61. if str(new_email).strip() != str(email_confirm).strip():
  62. click.echo(click.style('Sorry, new email and confirm email do not match.', fg='red'))
  63. return
  64. account = db.session.query(Account). \
  65. filter(Account.email == email). \
  66. one_or_none()
  67. if not account:
  68. click.echo(click.style('sorry. the account: [{}] not exist .'.format(email), fg='red'))
  69. return
  70. try:
  71. email_validate(new_email)
  72. except:
  73. click.echo(
  74. click.style('sorry. {} is not a valid email. '.format(email), fg='red'))
  75. return
  76. account.email = new_email
  77. db.session.commit()
  78. click.echo(click.style('Congratulations!, email has been reset.', fg='green'))
  79. @click.command('reset-encrypt-key-pair', help='Reset the asymmetric key pair of workspace for encrypt LLM credentials. '
  80. 'After the reset, all LLM credentials will become invalid, '
  81. 'requiring re-entry.'
  82. 'Only support SELF_HOSTED mode.')
  83. @click.confirmation_option(prompt=click.style('Are you sure you want to reset encrypt key pair?'
  84. ' this operation cannot be rolled back!', fg='red'))
  85. def reset_encrypt_key_pair():
  86. """
  87. Reset the encrypted key pair of workspace for encrypt LLM credentials.
  88. After the reset, all LLM credentials will become invalid, requiring re-entry.
  89. Only support SELF_HOSTED mode.
  90. """
  91. if current_app.config['EDITION'] != 'SELF_HOSTED':
  92. click.echo(click.style('Sorry, only support SELF_HOSTED mode.', fg='red'))
  93. return
  94. tenant = db.session.query(Tenant).first()
  95. if not tenant:
  96. click.echo(click.style('Sorry, no workspace found. Please enter /install to initialize.', fg='red'))
  97. return
  98. tenant.encrypt_public_key = generate_key_pair(tenant.id)
  99. db.session.query(Provider).filter(Provider.provider_type == 'custom').delete()
  100. db.session.query(ProviderModel).delete()
  101. db.session.commit()
  102. click.echo(click.style('Congratulations! '
  103. 'the asymmetric key pair of workspace {} has been reset.'.format(tenant.id), fg='green'))
  104. @click.command('create-qdrant-indexes', help='Create qdrant indexes.')
  105. def create_qdrant_indexes():
  106. """
  107. Migrate other vector database datas to Qdrant.
  108. """
  109. click.echo(click.style('Start create qdrant indexes.', fg='green'))
  110. create_count = 0
  111. page = 1
  112. while True:
  113. try:
  114. datasets = db.session.query(Dataset).filter(Dataset.indexing_technique == 'high_quality') \
  115. .order_by(Dataset.created_at.desc()).paginate(page=page, per_page=50)
  116. except NotFound:
  117. break
  118. model_manager = ModelManager()
  119. page += 1
  120. for dataset in datasets:
  121. if dataset.index_struct_dict:
  122. if dataset.index_struct_dict['type'] != 'qdrant':
  123. try:
  124. click.echo('Create dataset qdrant index: {}'.format(dataset.id))
  125. try:
  126. embedding_model = model_manager.get_model_instance(
  127. tenant_id=dataset.tenant_id,
  128. provider=dataset.embedding_model_provider,
  129. model_type=ModelType.TEXT_EMBEDDING,
  130. model=dataset.embedding_model
  131. )
  132. except Exception:
  133. continue
  134. embeddings = CacheEmbedding(embedding_model)
  135. from core.index.vector_index.qdrant_vector_index import QdrantConfig, QdrantVectorIndex
  136. index = QdrantVectorIndex(
  137. dataset=dataset,
  138. config=QdrantConfig(
  139. endpoint=current_app.config.get('QDRANT_URL'),
  140. api_key=current_app.config.get('QDRANT_API_KEY'),
  141. root_path=current_app.root_path
  142. ),
  143. embeddings=embeddings
  144. )
  145. if index:
  146. index.create_qdrant_dataset(dataset)
  147. index_struct = {
  148. "type": 'qdrant',
  149. "vector_store": {
  150. "class_prefix": dataset.index_struct_dict['vector_store']['class_prefix']}
  151. }
  152. dataset.index_struct = json.dumps(index_struct)
  153. db.session.commit()
  154. create_count += 1
  155. else:
  156. click.echo('passed.')
  157. except Exception as e:
  158. click.echo(
  159. click.style('Create dataset index error: {} {}'.format(e.__class__.__name__, str(e)),
  160. fg='red'))
  161. continue
  162. click.echo(click.style('Congratulations! Create {} dataset indexes.'.format(create_count), fg='green'))
  163. def register_commands(app):
  164. app.cli.add_command(reset_password)
  165. app.cli.add_command(reset_email)
  166. app.cli.add_command(reset_encrypt_key_pair)
  167. app.cli.add_command(create_qdrant_indexes)