Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

document_app.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. #
  2. # Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License
  15. #
  16. import base64
  17. import os
  18. import pathlib
  19. import re
  20. import flask
  21. from elasticsearch_dsl import Q
  22. from flask import request
  23. from flask_login import login_required, current_user
  24. from api.db.services.file2document_service import File2DocumentService
  25. from api.db.services.file_service import FileService
  26. from rag.nlp import search
  27. from rag.utils.es_conn import ELASTICSEARCH
  28. from api.db.services import duplicate_name
  29. from api.db.services.knowledgebase_service import KnowledgebaseService
  30. from api.utils.api_utils import server_error_response, get_data_error_result, validate_request
  31. from api.utils import get_uuid
  32. from api.db import FileType, TaskStatus, ParserType
  33. from api.db.services.document_service import DocumentService
  34. from api.settings import RetCode
  35. from api.utils.api_utils import get_json_result
  36. from rag.utils.minio_conn import MINIO
  37. from api.utils.file_utils import filename_type, thumbnail
  38. @manager.route('/upload', methods=['POST'])
  39. @login_required
  40. @validate_request("kb_id")
  41. def upload():
  42. kb_id = request.form.get("kb_id")
  43. if not kb_id:
  44. return get_json_result(
  45. data=False, retmsg='Lack of "KB ID"', retcode=RetCode.ARGUMENT_ERROR)
  46. if 'file' not in request.files:
  47. return get_json_result(
  48. data=False, retmsg='No file part!', retcode=RetCode.ARGUMENT_ERROR)
  49. file = request.files['file']
  50. if file.filename == '':
  51. return get_json_result(
  52. data=False, retmsg='No file selected!', retcode=RetCode.ARGUMENT_ERROR)
  53. try:
  54. e, kb = KnowledgebaseService.get_by_id(kb_id)
  55. if not e:
  56. return get_data_error_result(
  57. retmsg="Can't find this knowledgebase!")
  58. MAX_FILE_NUM_PER_USER = int(os.environ.get('MAX_FILE_NUM_PER_USER', 0))
  59. if MAX_FILE_NUM_PER_USER > 0 and DocumentService.get_doc_count(kb.tenant_id) >= MAX_FILE_NUM_PER_USER:
  60. return get_data_error_result(
  61. retmsg="Exceed the maximum file number of a free user!")
  62. filename = duplicate_name(
  63. DocumentService.query,
  64. name=file.filename,
  65. kb_id=kb.id)
  66. filetype = filename_type(filename)
  67. if filetype == FileType.OTHER.value:
  68. return get_data_error_result(
  69. retmsg="This type of file has not been supported yet!")
  70. location = filename
  71. while MINIO.obj_exist(kb_id, location):
  72. location += "_"
  73. blob = request.files['file'].read()
  74. MINIO.put(kb_id, location, blob)
  75. doc = {
  76. "id": get_uuid(),
  77. "kb_id": kb.id,
  78. "parser_id": kb.parser_id,
  79. "parser_config": kb.parser_config,
  80. "created_by": current_user.id,
  81. "type": filetype,
  82. "name": filename,
  83. "location": location,
  84. "size": len(blob),
  85. "thumbnail": thumbnail(filename, blob)
  86. }
  87. if doc["type"] == FileType.VISUAL:
  88. doc["parser_id"] = ParserType.PICTURE.value
  89. if re.search(r"\.(ppt|pptx|pages)$", filename):
  90. doc["parser_id"] = ParserType.PRESENTATION.value
  91. doc = DocumentService.insert(doc)
  92. return get_json_result(data=doc.to_json())
  93. except Exception as e:
  94. return server_error_response(e)
  95. @manager.route('/create', methods=['POST'])
  96. @login_required
  97. @validate_request("name", "kb_id")
  98. def create():
  99. req = request.json
  100. kb_id = req["kb_id"]
  101. if not kb_id:
  102. return get_json_result(
  103. data=False, retmsg='Lack of "KB ID"', retcode=RetCode.ARGUMENT_ERROR)
  104. try:
  105. e, kb = KnowledgebaseService.get_by_id(kb_id)
  106. if not e:
  107. return get_data_error_result(
  108. retmsg="Can't find this knowledgebase!")
  109. if DocumentService.query(name=req["name"], kb_id=kb_id):
  110. return get_data_error_result(
  111. retmsg="Duplicated document name in the same knowledgebase.")
  112. doc = DocumentService.insert({
  113. "id": get_uuid(),
  114. "kb_id": kb.id,
  115. "parser_id": kb.parser_id,
  116. "parser_config": kb.parser_config,
  117. "created_by": current_user.id,
  118. "type": FileType.VIRTUAL,
  119. "name": req["name"],
  120. "location": "",
  121. "size": 0
  122. })
  123. return get_json_result(data=doc.to_json())
  124. except Exception as e:
  125. return server_error_response(e)
  126. @manager.route('/list', methods=['GET'])
  127. @login_required
  128. def list():
  129. kb_id = request.args.get("kb_id")
  130. if not kb_id:
  131. return get_json_result(
  132. data=False, retmsg='Lack of "KB ID"', retcode=RetCode.ARGUMENT_ERROR)
  133. keywords = request.args.get("keywords", "")
  134. page_number = int(request.args.get("page", 1))
  135. items_per_page = int(request.args.get("page_size", 15))
  136. orderby = request.args.get("orderby", "create_time")
  137. desc = request.args.get("desc", True)
  138. try:
  139. docs, tol = DocumentService.get_by_kb_id(
  140. kb_id, page_number, items_per_page, orderby, desc, keywords)
  141. return get_json_result(data={"total": tol, "docs": docs})
  142. except Exception as e:
  143. return server_error_response(e)
  144. @manager.route('/thumbnails', methods=['GET'])
  145. @login_required
  146. def thumbnails():
  147. doc_ids = request.args.get("doc_ids").split(",")
  148. if not doc_ids:
  149. return get_json_result(
  150. data=False, retmsg='Lack of "Document ID"', retcode=RetCode.ARGUMENT_ERROR)
  151. try:
  152. docs = DocumentService.get_thumbnails(doc_ids)
  153. return get_json_result(data={d["id"]: d["thumbnail"] for d in docs})
  154. except Exception as e:
  155. return server_error_response(e)
  156. @manager.route('/change_status', methods=['POST'])
  157. @login_required
  158. @validate_request("doc_id", "status")
  159. def change_status():
  160. req = request.json
  161. if str(req["status"]) not in ["0", "1"]:
  162. get_json_result(
  163. data=False,
  164. retmsg='"Status" must be either 0 or 1!',
  165. retcode=RetCode.ARGUMENT_ERROR)
  166. try:
  167. e, doc = DocumentService.get_by_id(req["doc_id"])
  168. if not e:
  169. return get_data_error_result(retmsg="Document not found!")
  170. e, kb = KnowledgebaseService.get_by_id(doc.kb_id)
  171. if not e:
  172. return get_data_error_result(
  173. retmsg="Can't find this knowledgebase!")
  174. if not DocumentService.update_by_id(
  175. req["doc_id"], {"status": str(req["status"])}):
  176. return get_data_error_result(
  177. retmsg="Database error (Document update)!")
  178. if str(req["status"]) == "0":
  179. ELASTICSEARCH.updateScriptByQuery(Q("term", doc_id=req["doc_id"]),
  180. scripts="ctx._source.available_int=0;",
  181. idxnm=search.index_name(
  182. kb.tenant_id)
  183. )
  184. else:
  185. ELASTICSEARCH.updateScriptByQuery(Q("term", doc_id=req["doc_id"]),
  186. scripts="ctx._source.available_int=1;",
  187. idxnm=search.index_name(
  188. kb.tenant_id)
  189. )
  190. return get_json_result(data=True)
  191. except Exception as e:
  192. return server_error_response(e)
  193. @manager.route('/rm', methods=['POST'])
  194. @login_required
  195. @validate_request("doc_id")
  196. def rm():
  197. req = request.json
  198. doc_ids = req["doc_id"]
  199. if isinstance(doc_ids, str): doc_ids = [doc_ids]
  200. errors = ""
  201. for doc_id in doc_ids:
  202. try:
  203. e, doc = DocumentService.get_by_id(doc_id)
  204. if not e:
  205. return get_data_error_result(retmsg="Document not found!")
  206. tenant_id = DocumentService.get_tenant_id(doc_id)
  207. if not tenant_id:
  208. return get_data_error_result(retmsg="Tenant not found!")
  209. ELASTICSEARCH.deleteByQuery(
  210. Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
  211. DocumentService.increment_chunk_num(
  212. doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, 0)
  213. if not DocumentService.delete(doc):
  214. return get_data_error_result(
  215. retmsg="Database error (Document removal)!")
  216. informs = File2DocumentService.get_by_document_id(doc_id)
  217. if not informs:
  218. MINIO.rm(doc.kb_id, doc.location)
  219. else:
  220. File2DocumentService.delete_by_document_id(doc_id)
  221. except Exception as e:
  222. errors += str(e)
  223. if errors: return server_error_response(e)
  224. return get_json_result(data=True)
  225. @manager.route('/run', methods=['POST'])
  226. @login_required
  227. @validate_request("doc_ids", "run")
  228. def run():
  229. req = request.json
  230. try:
  231. for id in req["doc_ids"]:
  232. info = {"run": str(req["run"]), "progress": 0}
  233. if str(req["run"]) == TaskStatus.RUNNING.value:
  234. info["progress_msg"] = ""
  235. info["chunk_num"] = 0
  236. info["token_num"] = 0
  237. DocumentService.update_by_id(id, info)
  238. # if str(req["run"]) == TaskStatus.CANCEL.value:
  239. tenant_id = DocumentService.get_tenant_id(id)
  240. if not tenant_id:
  241. return get_data_error_result(retmsg="Tenant not found!")
  242. ELASTICSEARCH.deleteByQuery(
  243. Q("match", doc_id=id), idxnm=search.index_name(tenant_id))
  244. return get_json_result(data=True)
  245. except Exception as e:
  246. return server_error_response(e)
  247. @manager.route('/rename', methods=['POST'])
  248. @login_required
  249. @validate_request("doc_id", "name")
  250. def rename():
  251. req = request.json
  252. try:
  253. e, doc = DocumentService.get_by_id(req["doc_id"])
  254. if not e:
  255. return get_data_error_result(retmsg="Document not found!")
  256. if pathlib.Path(req["name"].lower()).suffix != pathlib.Path(
  257. doc.name.lower()).suffix:
  258. return get_json_result(
  259. data=False,
  260. retmsg="The extension of file can't be changed",
  261. retcode=RetCode.ARGUMENT_ERROR)
  262. if DocumentService.query(name=req["name"], kb_id=doc.kb_id):
  263. return get_data_error_result(
  264. retmsg="Duplicated document name in the same knowledgebase.")
  265. if not DocumentService.update_by_id(
  266. req["doc_id"], {"name": req["name"]}):
  267. return get_data_error_result(
  268. retmsg="Database error (Document rename)!")
  269. informs = File2DocumentService.get_by_document_id(req["doc_id"])
  270. if informs:
  271. e, file = FileService.get_by_id(informs[0].file_id)
  272. FileService.update_by_id(file.id, {"name": req["name"]})
  273. return get_json_result(data=True)
  274. except Exception as e:
  275. return server_error_response(e)
  276. @manager.route('/get/<doc_id>', methods=['GET'])
  277. # @login_required
  278. def get(doc_id):
  279. try:
  280. e, doc = DocumentService.get_by_id(doc_id)
  281. if not e:
  282. return get_data_error_result(retmsg="Document not found!")
  283. informs = File2DocumentService.get_by_document_id(doc_id)
  284. if not informs:
  285. response = flask.make_response(MINIO.get(doc.kb_id, doc.location))
  286. else:
  287. e, file = FileService.get_by_id(informs[0].file_id)
  288. response = flask.make_response(MINIO.get(file.parent_id, doc.location))
  289. ext = re.search(r"\.([^.]+)$", doc.name)
  290. if ext:
  291. if doc.type == FileType.VISUAL.value:
  292. response.headers.set('Content-Type', 'image/%s' % ext.group(1))
  293. else:
  294. response.headers.set(
  295. 'Content-Type',
  296. 'application/%s' %
  297. ext.group(1))
  298. return response
  299. except Exception as e:
  300. return server_error_response(e)
  301. @manager.route('/change_parser', methods=['POST'])
  302. @login_required
  303. @validate_request("doc_id", "parser_id")
  304. def change_parser():
  305. req = request.json
  306. try:
  307. e, doc = DocumentService.get_by_id(req["doc_id"])
  308. if not e:
  309. return get_data_error_result(retmsg="Document not found!")
  310. if doc.parser_id.lower() == req["parser_id"].lower():
  311. if "parser_config" in req:
  312. if req["parser_config"] == doc.parser_config:
  313. return get_json_result(data=True)
  314. else:
  315. return get_json_result(data=True)
  316. if doc.type == FileType.VISUAL or re.search(
  317. r"\.(ppt|pptx|pages)$", doc.name):
  318. return get_data_error_result(retmsg="Not supported yet!")
  319. e = DocumentService.update_by_id(doc.id,
  320. {"parser_id": req["parser_id"], "progress": 0, "progress_msg": "", "run": "0"})
  321. if not e:
  322. return get_data_error_result(retmsg="Document not found!")
  323. if "parser_config" in req:
  324. DocumentService.update_parser_config(doc.id, req["parser_config"])
  325. if doc.token_num > 0:
  326. e = DocumentService.increment_chunk_num(doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1,
  327. doc.process_duation * -1)
  328. if not e:
  329. return get_data_error_result(retmsg="Document not found!")
  330. tenant_id = DocumentService.get_tenant_id(req["doc_id"])
  331. if not tenant_id:
  332. return get_data_error_result(retmsg="Tenant not found!")
  333. ELASTICSEARCH.deleteByQuery(
  334. Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
  335. return get_json_result(data=True)
  336. except Exception as e:
  337. return server_error_response(e)
  338. @manager.route('/image/<image_id>', methods=['GET'])
  339. # @login_required
  340. def get_image(image_id):
  341. try:
  342. bkt, nm = image_id.split("-")
  343. response = flask.make_response(MINIO.get(bkt, nm))
  344. response.headers.set('Content-Type', 'image/JPEG')
  345. return response
  346. except Exception as e:
  347. return server_error_response(e)