You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用jQuery实现加载轮,在Python后台进程完成后自动隐藏?

问题描述

需要实现一个加载轮,在后台数据摄入流程运行时显示,流程完成后自动隐藏。数据摄入流程包括:爬取维基百科页面并转换为文本文件,再导入LLM模型。使用Flask做前后端通信,曾尝试用ajaxComplete处理POST请求,但加载轮仅显示毫秒级时长,目前采用轮询方式实现,现有代码如下:

现有HTML代码

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style> #loading { display: none; } </style>
<div class="card-footer">
            <form id="ingestArea" class="input-group">
                <input type="text" id="html" name="url" placeholder="Type your message..." autocomplete="off" class="form-control type_msg" required/>
                <div class="input-group-append">
                    <button type="submit" id="feed" class="input-group-text send_btn"><i class="fas fa-location-arrow"></i></button>
                </div>
                <div id="loading"><img src="https://i.pinimg.com/originals/36/3c/2e/363c2ec45f7668e82807a0c053d1e1d0.gif"></div>
            </form>
        </div>
<script>
            $(document).ready(function() {
                var interval;
                $("#messageArea").on("submit", function(event) {
                    const date = new Date();
                    const hour = date.getHours();
                    const minute = date.getMinutes();
                    const str_time = hour+":"+minute;
                    var rawText = $("#text").val();

                    var userHtml = '<div class="d-flex justify-content-end mb-4"><div class="msg_cotainer_send">' + rawText + '<span class="msg_time_send">'+ str_time + '</span></div><div class="img_cont_msg"><img src="https://i.ibb.co/d5b84Xw/Untitled-design.png" class="rounded-circle user_img_msg"></div></div>';
                    
                    $("#text").val("");
                    $("#messageFormeight").append(userHtml);

                    $.ajax({
                        data: {
                            msg: rawText,   
                        },
                        type: "POST",
                        url: "/get",
                    }).done(function(data) {
                        var botHtml = '<div class="d-flex justify-content-start mb-4"><div class="img_cont_msg"><img src="https://i.ibb.co/fSNP7Rz/icons8-chatgpt-512.png" class="rounded-circle user_img_msg"></div><div class="msg_cotainer">' + data + '<span class="msg_time">' + str_time + '</span></div></div>';
                        $("#messageFormeight").append($.parseHTML(botHtml));
                    });
                    event.preventDefault();
                });
                $("#ingestArea").on("submit",function(event) {
                    var rawText = $("#html").val();
                    $('#loading').show();
                    interval = setInterval(checkStatus, 1000);
                    $("#html").val("");
                    $.ajax({
                        data:{
                            url: rawText,
                        },
                        type: "POST",
                        url: "/ingest",
                    });
                });
            });
            function checkStatus() {
                $.ajax({
                    url: '/check_status',
                    type: 'GET',
                    success: function(response) {
                        if (response.status === "in_progress") {
                            $('#loading').show();
                        } else{
                            clearInterval(interval);
                            $('#loading').hide();
                        }
                    },
                    error: function(xhr) {
                        clearInterval(interval);
                        $('#loading').hide();
                    }
                });
        </script>

现有Python(Flask)代码

is_ingesting = False

@app.route('/check_status', methods=['GET'])
def check_status():
    global is_ingesting
    if is_ingesting:
        return jsonify(status="in_progress")
    else:
        return jsonify(status="completed")


def ingest():
    global is_ingesting
    is_ingesting = True
    start_url = request.form["url"]
    scrape_web(start_url, 0)
    if does_vectorstore_exist(persist_directory):
        # Update and store locally vectorstore
        print(f"Appending to existing vectorstore at {persist_directory}")
        db = Chroma(persist_directory=persist_directory, embedding_function=embeddings, client_settings=CHROMA_SETTINGS)
        collection = db.get()
        texts = process_documents([metadata['source'] for metadata in collection['metadatas']])
        print(f"Creating embeddings. May take some minutes...")
        db.add_documents(texts)
    else:
        # Create and store locally vectorstore
        print("Creating new vectorstore")
        texts = process_documents()
        print(f"Creating embeddings. May take some minutes...")
        db = Chroma.from_documents(texts, embeddings, persist_directory=persist_directory,
                                   client_settings=CHROMA_SETTINGS)
    db.persist()
    db = None
    is_ingesting = False
    print(f"Ingestion complete! You can now run privateGPT.py to query your documents")
    return jsonify("All done!")

现有代码问题分析

  1. Flask路由缺失:ingest函数未绑定@app.route('/ingest')装饰器,前端发送的/ingest请求无法被处理
  2. 全局变量线程不安全:Flask默认多线程运行,全局变量is_ingesting会被多个请求篡改,导致状态判断错误
  3. 前端表单未阻止默认行为:提交表单会触发页面刷新,导致加载轮异常消失
  4. 重复引入jQuery:同时加载两个不同版本的jQuery,易引发兼容性问题
  5. 无任务唯一标识:轮询时无法区分不同用户的任务,多用户操作时状态会混乱

优化后的实现方案

1. Flask后端改进

使用线程处理长时间摄入任务,避免阻塞请求;用线程安全的状态存储替代全局变量,支持多用户并发操作:

from flask import Flask, request, jsonify
import threading
# 导入你自己的业务模块
from your_module import scrape_web, process_documents, does_vectorstore_exist, Chroma, embeddings, CHROMA_SETTINGS, persist_directory

app = Flask(__name__)
# 线程安全的任务状态存储,key为任务ID,value为运行状态
ingest_tasks = {}
task_lock = threading.Lock()

@app.route('/ingest', methods=['POST'])
def start_ingest():
    start_url = request.form.get("url")
    if not start_url:
        return jsonify(error="URL不能为空"), 400
    
    # 生成唯一任务ID
    task_id = f"task_{threading.get_ident()}"
    with task_lock:
        ingest_tasks[task_id] = True
    
    # 启动后台线程执行摄入任务
    threading.Thread(target=run_ingest, args=(start_url, task_id)).start()
    return jsonify(task_id=task_id)

def run_ingest(start_url, task_id):
    try:
        scrape_web(start_url, 0)
        if does_vectorstore_exist(persist_directory):
            print(f"追加到现有向量库:{persist_directory}")
            db = Chroma(persist_directory=persist_directory, embedding_function=embeddings, client_settings=CHROMA_SETTINGS)
            collection = db.get()
            texts = process_documents([metadata['source'] for metadata in collection['metadatas']])
            print("生成嵌入向量,可能需要几分钟...")
            db.add_documents(texts)
        else:
            print("创建新向量库")
            texts = process_documents()
            print("生成嵌入向量,可能需要几分钟...")
            db = Chroma.from_documents(texts, embeddings, persist_directory=persist_directory,
                                       client_settings=CHROMA_SETTINGS)
        db.persist()
        db = None
        print("数据摄入完成!现在可以运行privateGPT.py查询文档")
    finally:
        # 任务结束后更新状态
        with task_lock:
            ingest_tasks.pop(task_id, None)

@app.route('/check_status/<task_id>', methods=['GET'])
def check_status(task_id):
    with task_lock:
        is_running = ingest_tasks.get(task_id, False)
    return jsonify(status="in_progress" if is_running else "completed")

2. 前端改进

移除重复jQuery引入,阻止表单默认提交行为,基于任务ID跟踪状态,优化错误处理:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style> 
#loading { 
    display: none; 
    margin-left: 10px;
}
</style>
<div class="card-footer">
    <form id="ingestArea" class="input-group">
        <input type="text" id="html" name="url" placeholder="输入维基百科URL..." autocomplete="off" class="form-control type_msg" required/>
        <div class="input-group-append">
            <button type="submit" id="feed" class="input-group-text send_btn"><i class="fas fa-location-arrow"></i></button>
        </div>
        <div id="loading"><img src="https://i.pinimg.com/originals/36/3c/2e/363c2ec45f7668e82807a0c053d1e1d0.gif"></div>
    </form>
</div>
<script>
$(document).ready(function() {
    var interval;

    $("#ingestArea").on("submit",function(event) {
        event.preventDefault(); // 阻止表单默认刷新行为
        var rawText = $("#html").val().trim();
        if (!rawText) return;
        
        $('#loading').show();
        $("#html").val("");

        // 启动摄入任务并获取唯一任务ID
        $.ajax({
            data:{
                url: rawText,
            },
            type: "POST",
            url: "/ingest",
            success: function(response) {
                var taskId = response.task_id;
                // 基于任务ID轮询状态
                interval = setInterval(function() {
                    checkStatus(taskId);
                }, 1000);
            },
            error: function() {
                $('#loading').hide();
                alert("任务启动失败,请重试");
            }
        });
    });

    function checkStatus(taskId) {
        $.ajax({
            url: `/check_status/${taskId}`,
            type: 'GET',
            success: function(response) {
                if (response.status !== "in_progress") {
                    clearInterval(interval);
                    $('#loading').hide();
                    alert("数据摄入完成!");
                }
            },
            error: function() {
                clearInterval(interval);
                $('#loading').hide();
                alert("获取任务状态失败");
            }
        });
    }

    // 保留原有的消息交互逻辑
    $("#messageArea").on("submit", function(event) {
        event.preventDefault();
        const date = new Date();
        const hour = date.getHours();
        const minute = date.getMinutes();
        const str_time = hour+":"+minute;
        var rawText = $("#text").val().trim();
        if (!rawText) return;

        var userHtml = '<div class="d-flex justify-content-end mb-4"><div class="msg_cotainer_send">' + rawText + '<span class="msg_time_send">'+ str_time + '</span></div><div class="img_cont_msg"><img src="https://i.ibb.co/d5b84Xw/Untitled-design.png" class="rounded-circle user_img_msg"></div></div>';
        
        $("#text").val("");
        $("#messageFormeight").append(userHtml);

        $.ajax({
            data: {
                msg: rawText,   
            },
            type: "POST",
            url: "/get",
            done: function(data) {
                var botHtml = '<div class="d-flex justify-content-start mb-4"><div class="img_cont_msg"><img src="https://i.ibb.co/fSNP7Rz/icons8-chatgpt-512.png" class="rounded-circle user_img_msg"></div><div class="msg_cotainer">' + data + '<span class="msg_time">' + str_time + '</span></div></div>';
                $("#messageFormeight").append($.parseHTML(botHtml));
            }
        });
    });
});
</script>

内容的提问来源于stack exchange,提问作者Jarod Haley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 20:24:55