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

Flask向Next.js(App Router)实时推进度:解决实例进度隔离问题

问题:PDF生成进度条实例隔离失败

初始代码与背景

Python后端代码(/api/generate/all)

@app.route("/api/generate/all", methods=["POST"])
async def render_code_all():
        data = request.get_json()
        doc = SimpleDocTemplate(pdf_filename, pagesize=letter,
                                rightMargin=0.6*inch, leftMargin=0.6*inch,
                                topMargin=0.3*inch, bottomMargin=0.3*inch)
        elements = []
        for practical in practicals:
            heading_ = Paragraph(f"Experiment - {num}", pdf_styles["Title"])
            elements.append(heading_)
            processed += 1
            progress = round((processed/total)*100, 2)
            print(f"Generated {num} of {total} practicals.")
            print(f"Progress: {progress}%")
        doc.title = f"{subject} | Semester {semester}"
        doc.build(elements)
        r2_client.upload_file(os.path.join(os.getcwd(), pdf_filename), pdf_filename, namespace_id)
        pdf_link = f"https://files.praxout.co/{pdf_filename}"
        os.remove(os.path.join(os.getcwd(), pdf_filename))
        return jsonify({"pdf_link": pdf_link})

前端表单组件

export default function CreateOutPutForm() {
  const [codePDFData, setCodePDFData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [progress, setProgress] = useState(0);
  const { toast } = useToast();
  const handleSubmit = async (values: typeof formSchema._type) => {
    try {
      setLoading(true);
      setProgress(0);
      const response = await fetch(`/api/practicals/generate`, {
        method: "POST",
        body: JSON.stringify({ ...values }),
      }).then((res) => res.json());
      if (response.pdf_link) {
        setCodePDFData(response.pdf_link);
        toast({
          title: "Practical Generated",
          description: "The practical has been generated successfully.",
        });
      } else {
        console.error("Failed to generate output.");
        toast({
          title: "Failed to Generate Practical",
          description: `An error occurred while generating the practical...`,
        });
      }
    } 
  };

  return (
    <div className="flex w-full flex-col items-center justify-center gap-8 pb-24 ">
      <AutoForm
        formSchema={formSchema}
        values={values}
        onParsedValuesChange={setValues}
        onSubmit={(values) => handleSubmit(values)}
      >
        <AutoFormSubmit disabled={loading} className="w-full">
          Generate Practical
        </AutoFormSubmit>
      </AutoForm>
      {codePDFData && !loading && (
        <PDFVIewer
          key={codePDFData}
          link={codePDFData}
        />
      )}
      {loading && (
        <div className="w-full">
          <Progress value={progress} />
        </div>
      )}
    </div>
  );
}

前端API路由(/api/practicals/generate)

import { NextRequest, NextResponse } from "next/server";
import { neon } from "@neondatabase/serverless";
import axios from "axios";
export async function POST(req: NextRequest, res: NextResponse) {
    const sql = neon(process.env.DATABASE_URL!);
    if (!req || !req.body) {
        return NextResponse.json({ error: "Request body required.", status: 400 });
    }
        const { semester, subject, num, name, enrollment_no, _class, invert, type }: GeneratePracticalOutputRequestProps = await req.json();
        console.log(type)
        if (type === "single") {
            const res = await sql`SELECT DISTINCT * FROM practicals WHERE semester = ${semester} AND subject = ${subject} AND num = ${num};`;
            const data = res[0];
            const code_template = data.code_template;
            const code_theme = data.code_template_highlight;
            const output_template = data.output_template;
            const output_theme = data.output_highlight;
            const language = data.code_language;
            const response = await axios.post(`${BACKEND_URI}/api/generate`, { ...all_values }, {
                headers: {
                    "Authorization": process.env.BACKEND_API_KEY!,
                },
            });
            return NextResponse.json({ pdf_link: response.data.pdf_link, status: 200, success: true });
        }
        if (type === "all") {
            const res = await sql`SELECT * FROM practicals WHERE semester = ${semester} AND subject = ${subject} ORDER BY num ASC;`;
            const response = await axios.post(`${BACKEND_URI}/api/generate/all`, { ...all_values }, {
                headers: {
                    "Authorization": process.env.BACKEND_API_KEY!,
                },
            });
            return NextResponse.json({ pdf_link: response.data.pdf_link, status: 200, success: true });
    }
}

已尝试的初始方案

  • Flask SSE:无法在后端初始化,推测是事件循环已运行导致无法创建新任务。
  • SocketIO:同样无法在后端初始化,@socketio.on("connect")事件从未触发,推测也是事件循环问题。

进度条实现与新问题

已实现进度条功能,但所有PDF生成实例共用同一个进度值,实现方式如下:

后端进度推送代码

async def emit_progress_single(progress):
    socketio.emit("progress", {"progress": progress})
...
for practical in practicals:
    ....
    await emit_progress_single(progress)

前端监听代码

useEffect(() => {
    const BACKEND_URI =
      process.env.NODE_ENV === "production"
        ? process.env.BACKEND_API_URI!
        : "http://127.0.0.1:25565";
    const socket = io(BACKEND_URI);
    socket.on("connect", () => {
      console.log("Connected to backend socketIO server.");
      const socketid = socket.id;
      if (socketid) {
        setSocketId(socketid);
      }
    });
    socket.connect();
    socket.on(`progress`, (data) => {
      setProgress(data.progress);
    });
  }, []);

尝试的隔离改进方案(无效)

前端修改代码

const [progress, setProgress] = useState(0);
  const [socketId, setSocketId] = useState<string>("");

  useEffect(() => {
    const BACKEND_URI =
      process.env.NODE_ENV === "production"
        ? process.env.BACKEND_API_URI!
        : "http://127.0.0.1:25565";
    const socket = io(BACKEND_URI);
    socket.on("connect", () => {
      console.log("Connected to backend socketIO server.");
      const socketid = socket.id;
      if (socketid) {
        setSocketId(socketid);
      }
    });
    socket.connect();
    socket.on(`progress/${socketId}`, (data) => {
      setProgress(data.progress);
    });
  }, [socketId]);

后端修改代码

async def emit_progress(progress, session_id):
    socketio.emit(f"progress/{session_id}", {"progress": progress}, namespace=f"/progress/{session_id}", to=session_id)
...
for practical in practicals:
    ...
    await emit_progress(progress, socket_id)

上述改进方案完全无法接收进度更新/事件,需要解决进度条实例隔离的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:35:53