Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

annotation.py 11KB

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