Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. from flask import request
  2. from flask_login import current_user
  3. from flask_restful import Resource, marshal, marshal_with, reqparse
  4. from werkzeug.exceptions import Forbidden
  5. from controllers.common.errors import NoFileUploadedError, TooManyFilesError
  6. from controllers.console import api
  7. from controllers.console.wraps import (
  8. account_initialization_required,
  9. cloud_edition_billing_resource_check,
  10. setup_required,
  11. )
  12. from extensions.ext_redis import redis_client
  13. from fields.annotation_fields import (
  14. annotation_fields,
  15. annotation_hit_history_fields,
  16. )
  17. from libs.login import login_required
  18. from services.annotation_service import AppAnnotationService
  19. class AnnotationReplyActionApi(Resource):
  20. @setup_required
  21. @login_required
  22. @account_initialization_required
  23. @cloud_edition_billing_resource_check("annotation")
  24. def post(self, app_id, action):
  25. if not current_user.is_editor:
  26. raise Forbidden()
  27. app_id = str(app_id)
  28. parser = reqparse.RequestParser()
  29. parser.add_argument("score_threshold", required=True, type=float, location="json")
  30. parser.add_argument("embedding_provider_name", required=True, type=str, location="json")
  31. parser.add_argument("embedding_model_name", required=True, type=str, location="json")
  32. args = parser.parse_args()
  33. if action == "enable":
  34. result = AppAnnotationService.enable_app_annotation(args, app_id)
  35. elif action == "disable":
  36. result = AppAnnotationService.disable_app_annotation(app_id)
  37. else:
  38. raise ValueError("Unsupported annotation reply action")
  39. return result, 200
  40. class AppAnnotationSettingDetailApi(Resource):
  41. @setup_required
  42. @login_required
  43. @account_initialization_required
  44. def get(self, app_id):
  45. if not current_user.is_editor:
  46. raise Forbidden()
  47. app_id = str(app_id)
  48. result = AppAnnotationService.get_app_annotation_setting_by_app_id(app_id)
  49. return result, 200
  50. class AppAnnotationSettingUpdateApi(Resource):
  51. @setup_required
  52. @login_required
  53. @account_initialization_required
  54. def post(self, app_id, annotation_setting_id):
  55. if not current_user.is_editor:
  56. raise Forbidden()
  57. app_id = str(app_id)
  58. annotation_setting_id = str(annotation_setting_id)
  59. parser = reqparse.RequestParser()
  60. parser.add_argument("score_threshold", required=True, type=float, location="json")
  61. args = parser.parse_args()
  62. result = AppAnnotationService.update_app_annotation_setting(app_id, annotation_setting_id, args)
  63. return result, 200
  64. class AnnotationReplyActionStatusApi(Resource):
  65. @setup_required
  66. @login_required
  67. @account_initialization_required
  68. @cloud_edition_billing_resource_check("annotation")
  69. def get(self, app_id, job_id, action):
  70. if not current_user.is_editor:
  71. raise Forbidden()
  72. job_id = str(job_id)
  73. app_annotation_job_key = f"{action}_app_annotation_job_{str(job_id)}"
  74. cache_result = redis_client.get(app_annotation_job_key)
  75. if cache_result is None:
  76. raise ValueError("The job does not exist.")
  77. job_status = cache_result.decode()
  78. error_msg = ""
  79. if job_status == "error":
  80. app_annotation_error_key = f"{action}_app_annotation_error_{str(job_id)}"
  81. error_msg = redis_client.get(app_annotation_error_key).decode()
  82. return {"job_id": job_id, "job_status": job_status, "error_msg": error_msg}, 200
  83. class AnnotationApi(Resource):
  84. @setup_required
  85. @login_required
  86. @account_initialization_required
  87. def get(self, app_id):
  88. if not current_user.is_editor:
  89. raise Forbidden()
  90. page = request.args.get("page", default=1, type=int)
  91. limit = request.args.get("limit", default=20, type=int)
  92. keyword = request.args.get("keyword", default="", type=str)
  93. app_id = str(app_id)
  94. annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_id, page, limit, keyword)
  95. response = {
  96. "data": marshal(annotation_list, annotation_fields),
  97. "has_more": len(annotation_list) == limit,
  98. "limit": limit,
  99. "total": total,
  100. "page": page,
  101. }
  102. return response, 200
  103. @setup_required
  104. @login_required
  105. @account_initialization_required
  106. @cloud_edition_billing_resource_check("annotation")
  107. @marshal_with(annotation_fields)
  108. def post(self, app_id):
  109. if not current_user.is_editor:
  110. raise Forbidden()
  111. app_id = str(app_id)
  112. parser = reqparse.RequestParser()
  113. parser.add_argument("question", required=True, type=str, location="json")
  114. parser.add_argument("answer", required=True, type=str, location="json")
  115. args = parser.parse_args()
  116. annotation = AppAnnotationService.insert_app_annotation_directly(args, app_id)
  117. return annotation
  118. @setup_required
  119. @login_required
  120. @account_initialization_required
  121. def delete(self, app_id):
  122. if not current_user.is_editor:
  123. raise Forbidden()
  124. app_id = str(app_id)
  125. # Use request.args.getlist to get annotation_ids array directly
  126. annotation_ids = request.args.getlist("annotation_id")
  127. # If annotation_ids are provided, handle batch deletion
  128. if annotation_ids:
  129. # Check if any annotation_ids contain empty strings or invalid values
  130. if not all(annotation_id.strip() for annotation_id in annotation_ids if annotation_id):
  131. return {
  132. "code": "bad_request",
  133. "message": "annotation_ids are required if the parameter is provided.",
  134. }, 400
  135. result = AppAnnotationService.delete_app_annotations_in_batch(app_id, annotation_ids)
  136. return result, 204
  137. # If no annotation_ids are provided, handle clearing all annotations
  138. else:
  139. AppAnnotationService.clear_all_annotations(app_id)
  140. return {"result": "success"}, 204
  141. class AnnotationExportApi(Resource):
  142. @setup_required
  143. @login_required
  144. @account_initialization_required
  145. def get(self, app_id):
  146. if not current_user.is_editor:
  147. raise Forbidden()
  148. app_id = str(app_id)
  149. annotation_list = AppAnnotationService.export_annotation_list_by_app_id(app_id)
  150. response = {"data": marshal(annotation_list, annotation_fields)}
  151. return response, 200
  152. class AnnotationUpdateDeleteApi(Resource):
  153. @setup_required
  154. @login_required
  155. @account_initialization_required
  156. @cloud_edition_billing_resource_check("annotation")
  157. @marshal_with(annotation_fields)
  158. def post(self, app_id, annotation_id):
  159. if not current_user.is_editor:
  160. raise Forbidden()
  161. app_id = str(app_id)
  162. annotation_id = str(annotation_id)
  163. parser = reqparse.RequestParser()
  164. parser.add_argument("question", required=True, type=str, location="json")
  165. parser.add_argument("answer", required=True, type=str, location="json")
  166. args = parser.parse_args()
  167. annotation = AppAnnotationService.update_app_annotation_directly(args, app_id, annotation_id)
  168. return annotation
  169. @setup_required
  170. @login_required
  171. @account_initialization_required
  172. def delete(self, app_id, annotation_id):
  173. if not current_user.is_editor:
  174. raise Forbidden()
  175. app_id = str(app_id)
  176. annotation_id = str(annotation_id)
  177. AppAnnotationService.delete_app_annotation(app_id, annotation_id)
  178. return {"result": "success"}, 204
  179. class AnnotationBatchImportApi(Resource):
  180. @setup_required
  181. @login_required
  182. @account_initialization_required
  183. @cloud_edition_billing_resource_check("annotation")
  184. def post(self, app_id):
  185. if not current_user.is_editor:
  186. raise Forbidden()
  187. app_id = str(app_id)
  188. # check file
  189. if "file" not in request.files:
  190. raise NoFileUploadedError()
  191. if len(request.files) > 1:
  192. raise TooManyFilesError()
  193. # get file from request
  194. file = request.files["file"]
  195. # check file type
  196. if not file.filename or not file.filename.lower().endswith(".csv"):
  197. raise ValueError("Invalid file type. Only CSV files are allowed")
  198. return AppAnnotationService.batch_import_app_annotations(app_id, file)
  199. class AnnotationBatchImportStatusApi(Resource):
  200. @setup_required
  201. @login_required
  202. @account_initialization_required
  203. @cloud_edition_billing_resource_check("annotation")
  204. def get(self, app_id, job_id):
  205. if not current_user.is_editor:
  206. raise Forbidden()
  207. job_id = str(job_id)
  208. indexing_cache_key = f"app_annotation_batch_import_{str(job_id)}"
  209. cache_result = redis_client.get(indexing_cache_key)
  210. if cache_result is None:
  211. raise ValueError("The job does not exist.")
  212. job_status = cache_result.decode()
  213. error_msg = ""
  214. if job_status == "error":
  215. indexing_error_msg_key = f"app_annotation_batch_import_error_msg_{str(job_id)}"
  216. error_msg = redis_client.get(indexing_error_msg_key).decode()
  217. return {"job_id": job_id, "job_status": job_status, "error_msg": error_msg}, 200
  218. class AnnotationHitHistoryListApi(Resource):
  219. @setup_required
  220. @login_required
  221. @account_initialization_required
  222. def get(self, app_id, annotation_id):
  223. if not current_user.is_editor:
  224. raise Forbidden()
  225. page = request.args.get("page", default=1, type=int)
  226. limit = request.args.get("limit", default=20, type=int)
  227. app_id = str(app_id)
  228. annotation_id = str(annotation_id)
  229. annotation_hit_history_list, total = AppAnnotationService.get_annotation_hit_histories(
  230. app_id, annotation_id, page, limit
  231. )
  232. response = {
  233. "data": marshal(annotation_hit_history_list, annotation_hit_history_fields),
  234. "has_more": len(annotation_hit_history_list) == limit,
  235. "limit": limit,
  236. "total": total,
  237. "page": page,
  238. }
  239. return response
  240. api.add_resource(AnnotationReplyActionApi, "/apps/<uuid:app_id>/annotation-reply/<string:action>")
  241. api.add_resource(
  242. AnnotationReplyActionStatusApi, "/apps/<uuid:app_id>/annotation-reply/<string:action>/status/<uuid:job_id>"
  243. )
  244. api.add_resource(AnnotationApi, "/apps/<uuid:app_id>/annotations")
  245. api.add_resource(AnnotationExportApi, "/apps/<uuid:app_id>/annotations/export")
  246. api.add_resource(AnnotationUpdateDeleteApi, "/apps/<uuid:app_id>/annotations/<uuid:annotation_id>")
  247. api.add_resource(AnnotationBatchImportApi, "/apps/<uuid:app_id>/annotations/batch-import")
  248. api.add_resource(AnnotationBatchImportStatusApi, "/apps/<uuid:app_id>/annotations/batch-import-status/<uuid:job_id>")
  249. api.add_resource(AnnotationHitHistoryListApi, "/apps/<uuid:app_id>/annotations/<uuid:annotation_id>/hit-histories")
  250. api.add_resource(AppAnnotationSettingDetailApi, "/apps/<uuid:app_id>/annotation-setting")
  251. api.add_resource(AppAnnotationSettingUpdateApi, "/apps/<uuid:app_id>/annotation-settings/<uuid:annotation_setting_id>")