From 2023fdc13e4a2020274317459a6684578c665c5a Mon Sep 17 00:00:00 2001 From: KevinHuSh Date: Fri, 14 Jun 2024 10:33:59 +0800 Subject: [PATCH] fix file preview in file management (#1151) ### What problem does this PR solve? fix file preview in file management ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- api/apps/canvas_app.py | 157 ++++++++++++++++++++++++++++++ api/apps/file_app.py | 7 +- api/db/services/canvas_service.py | 26 +++++ api/db/services/task_service.py | 10 ++ docker/docker-compose-CN-oc9.yml | 1 + docker/docker-compose-CN.yml | 1 + docker/docker-compose.yml | 1 + rag/app/picture.py | 6 +- rag/llm/cv_model.py | 2 +- rag/llm/embedding_model.py | 3 +- 10 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 api/apps/canvas_app.py create mode 100644 api/db/services/canvas_service.py diff --git a/api/apps/canvas_app.py b/api/apps/canvas_app.py new file mode 100644 index 000000000..d96a59522 --- /dev/null +++ b/api/apps/canvas_app.py @@ -0,0 +1,157 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import json +from functools import partial + +from flask import request, Response +from flask_login import login_required, current_user + +from api.db.services.canvas_service import CanvasTemplateService, UserCanvasService +from api.utils import get_uuid +from api.utils.api_utils import get_json_result, server_error_response, validate_request +from graph.canvas import Canvas + + +@manager.route('/templates', methods=['GET']) +@login_required +def templates(): + return get_json_result(data=[c.to_dict() for c in CanvasTemplateService.get_all()]) + + +@manager.route('/list', methods=['GET']) +@login_required +def canvas_list(): + + return get_json_result(data=[c.to_dict() for c in UserCanvasService.query(user_id=current_user.id)]) + + +@manager.route('/rm', methods=['POST']) +@validate_request("canvas_ids") +@login_required +def rm(): + for i in request.json["canvas_ids"]: + UserCanvasService.delete_by_id(i) + return get_json_result(data=True) + + +@manager.route('/set', methods=['POST']) +@validate_request("dsl", "title") +@login_required +def save(): + req = request.json + req["user_id"] = current_user.id + if not isinstance(req["dsl"], str):req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False) + + req["dsl"] = json.loads(req["dsl"]) + if "id" not in req: + if UserCanvasService.query(user_id=current_user.id, title=req["title"].strip()): + return server_error_response(ValueError("Duplicated title.")) + req["id"] = get_uuid() + if not UserCanvasService.save(**req): + return server_error_response("Fail to save canvas.") + else: + UserCanvasService.update_by_id(req["id"], req) + + return get_json_result(data=req) + + +@manager.route('/get/', methods=['GET']) +@login_required +def get(canvas_id): + e, c = UserCanvasService.get_by_id(canvas_id) + if not e: + return server_error_response("canvas not found.") + return get_json_result(data=c.to_dict()) + + +@manager.route('/completion', methods=['POST']) +@validate_request("id") +@login_required +def run(): + req = request.json + stream = req.get("stream", True) + e, cvs = UserCanvasService.get_by_id(req["id"]) + if not e: + return server_error_response("canvas not found.") + + if not isinstance(cvs.dsl, str): + cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False) + + final_ans = {"reference": [], "content": ""} + try: + canvas = Canvas(cvs.dsl, current_user.id) + print(canvas) + if "message" in req: + canvas.messages.append({"role": "user", "content": req["message"]}) + canvas.add_user_input(req["message"]) + answer = canvas.run(stream=stream) + except Exception as e: + return server_error_response(e) + + if stream: + assert isinstance(answer, partial) + + def sse(): + nonlocal answer, cvs + try: + for ans in answer(): + for k in ans.keys(): + final_ans[k] = ans[k] + ans = {"answer": ans["content"], "reference": ans.get("reference", [])} + yield "data:" + json.dumps({"retcode": 0, "retmsg": "", "data": ans}, ensure_ascii=False) +"\n\n" + + canvas.messages.append({"role": "assistant", "content": final_ans["content"]}) + if "reference" in final_ans: + canvas.reference.append(final_ans["reference"]) + cvs.dsl = json.loads(str(canvas)) + UserCanvasService.update_by_id(req["id"], cvs.to_dict()) + except Exception as e: + yield "data:" + json.dumps({"retcode": 500, "retmsg": str(e), + "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, + ensure_ascii=False) + "\n\n" + yield "data:" + json.dumps({"retcode": 0, "retmsg": "", "data": True}, ensure_ascii=False) + "\n\n" + + resp = Response(sse(), mimetype="text/event-stream") + resp.headers.add_header("Cache-control", "no-cache") + resp.headers.add_header("Connection", "keep-alive") + resp.headers.add_header("X-Accel-Buffering", "no") + resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8") + return resp + + canvas.messages.append({"role": "assistant", "content": final_ans["content"]}) + if "reference" in final_ans: + canvas.reference.append(final_ans["reference"]) + cvs.dsl = json.loads(str(canvas)) + UserCanvasService.update_by_id(req["id"], cvs.to_dict()) + return get_json_result(data=req["dsl"]) + + +@manager.route('/reset', methods=['POST']) +@validate_request("canvas_id") +@login_required +def reset(): + req = request.json + try: + user_canvas = UserCanvasService.get_by_id(req["canvas_id"]) + canvas = Canvas(req["dsl"], current_user.id) + canvas.reset() + req["dsl"] = json.loads(str(canvas)) + UserCanvasService.update_by_id(req["canvas_id"], dsl=req["dsl"]) + return get_json_result(data=req["dsl"]) + except Exception as e: + return server_error_response(e) + + diff --git a/api/apps/file_app.py b/api/apps/file_app.py index cd4e0be20..4f065aa15 100644 --- a/api/apps/file_app.py +++ b/api/apps/file_app.py @@ -331,8 +331,8 @@ def get(file_id): e, file = FileService.get_by_id(file_id) if not e: return get_data_error_result(retmsg="Document not found!") - - response = flask.make_response(MINIO.get(file.parent_id, file.location)) + b, n = File2DocumentService.get_minio_address(file_id=file_id) + response = flask.make_response(MINIO.get(b, n)) ext = re.search(r"\.([^.]+)$", file.name) if ext: if file.type == FileType.VISUAL.value: @@ -345,7 +345,8 @@ def get(file_id): return response except Exception as e: return server_error_response(e) - + + @manager.route('/mv', methods=['POST']) @login_required @validate_request("src_file_ids", "dest_file_id") diff --git a/api/db/services/canvas_service.py b/api/db/services/canvas_service.py new file mode 100644 index 000000000..ed2cdf63a --- /dev/null +++ b/api/db/services/canvas_service.py @@ -0,0 +1,26 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from datetime import datetime +import peewee +from api.db.db_models import DB, API4Conversation, APIToken, Dialog, CanvasTemplate, UserCanvas +from api.db.services.common_service import CommonService + + +class CanvasTemplateService(CommonService): + model = CanvasTemplate + +class UserCanvasService(CommonService): + model = UserCanvas diff --git a/api/db/services/task_service.py b/api/db/services/task_service.py index 0bdfd08b2..942207c06 100644 --- a/api/db/services/task_service.py +++ b/api/db/services/task_service.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import os import random from api.db.db_utils import bulk_insert_into_db @@ -102,6 +103,15 @@ class TaskService(CommonService): @classmethod @DB.connection_context() def update_progress(cls, id, info): + if os.environ.get("MACOS"): + if info["progress_msg"]: + cls.model.update(progress_msg=cls.model.progress_msg + "\n" + info["progress_msg"]).where( + cls.model.id == id).execute() + if "progress" in info: + cls.model.update(progress=info["progress"]).where( + cls.model.id == id).execute() + return + with DB.lock("update_progress", -1): if info["progress_msg"]: cls.model.update(progress_msg=cls.model.progress_msg + "\n" + info["progress_msg"]).where( diff --git a/docker/docker-compose-CN-oc9.yml b/docker/docker-compose-CN-oc9.yml index 5e89fc2bd..c65d1373f 100644 --- a/docker/docker-compose-CN-oc9.yml +++ b/docker/docker-compose-CN-oc9.yml @@ -24,6 +24,7 @@ services: environment: - TZ=${TIMEZONE} - HF_ENDPOINT=https://hf-mirror.com + - MACOS=${MACOS} networks: - ragflow restart: always diff --git a/docker/docker-compose-CN.yml b/docker/docker-compose-CN.yml index 929777580..f11097494 100644 --- a/docker/docker-compose-CN.yml +++ b/docker/docker-compose-CN.yml @@ -24,6 +24,7 @@ services: environment: - TZ=${TIMEZONE} - HF_ENDPOINT=https://hf-mirror.com + - MACOS=${MACOS} networks: - ragflow restart: always diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e34749cc5..97a1cb5c3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -24,6 +24,7 @@ services: environment: - TZ=${TIMEZONE} - HF_ENDPOINT=https://huggingface.co + - MACOS=${MACOS} networks: - ragflow restart: always diff --git a/rag/app/picture.py b/rag/app/picture.py index fbbc9f37b..475b6f3b6 100644 --- a/rag/app/picture.py +++ b/rag/app/picture.py @@ -24,11 +24,6 @@ ocr = OCR() def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs): - try: - cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang) - except Exception as e: - callback(prog=-1, msg=str(e)) - return [] img = Image.open(io.BytesIO(binary)).convert('RGB') doc = { "docnm_kwd": filename, @@ -45,6 +40,7 @@ def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs): try: callback(0.4, "Use CV LLM to describe the picture.") + cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang) ans = cv_mdl.describe(binary) callback(0.8, "CV LLM respoond: %s ..." % ans[:32]) txt += "\n" + ans diff --git a/rag/llm/cv_model.py b/rag/llm/cv_model.py index a195fa2e5..5a6fdc6d9 100644 --- a/rag/llm/cv_model.py +++ b/rag/llm/cv_model.py @@ -78,7 +78,7 @@ class GptV4(Base): prompt = self.prompt(b64) for i in range(len(prompt)): for c in prompt[i]["content"]: - if "type" in c: del c["type"] + if "text" in c: c["type"] = "text" res = self.client.chat.completions.create( model=self.model_name, diff --git a/rag/llm/embedding_model.py b/rag/llm/embedding_model.py index 361dbbbce..b0e1bff96 100644 --- a/rag/llm/embedding_model.py +++ b/rag/llm/embedding_model.py @@ -15,7 +15,6 @@ # import re from typing import Optional - import requests from huggingface_hub import snapshot_download from zhipuai import ZhipuAI @@ -344,4 +343,4 @@ class InfinityEmbed(Base): def encode_queries(self, text: str) -> tuple[np.ndarray, int]: # Using the internal tokenizer to encode the texts and get the total # number of tokens - return self.encode([text]) + return self.encode([text]) \ No newline at end of file