doc-forge-reds/web/src/components/GenerateModal.tsx

180 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from "react";
import {
Modal, Button, Space, message, Card, Tag, Upload, List, Popconfirm,
} from "antd";
import {
UploadOutlined, DeleteOutlined, FileTextOutlined,
ThunderboltOutlined, PaperClipOutlined,
} from "@ant-design/icons";
import type { GenerationPoint, AIModel } from "../types";
import * as taskApi from "../api/tasks";
import request from "../api/request";
interface GenerateModalProps {
open: boolean;
onClose: () => void;
templateId: string;
points: GenerationPoint[];
models: AIModel[];
}
interface FileItem {
uid: string;
name: string;
path?: string;
}
export default function GenerateModal({
open, onClose, templateId, points, models,
}: GenerateModalProps) {
const [filesMap, setFilesMap] = useState<Record<string, FileItem[]>>({});
const [generating, setGenerating] = useState(false);
const handleUpload = async (pointId: string, fileList: File[]) => {
if (fileList.length === 0) return;
const formData = new FormData();
fileList.forEach((f) => formData.append("ref_files", f));
await request.post(`/generation-points/${pointId}/upload-ref`, formData);
const newFiles = fileList.map((f) => ({
uid: f.name + Date.now(),
name: f.name,
}));
setFilesMap((prev) => ({
...prev,
[pointId]: [...(prev[pointId] || []), ...newFiles],
}));
message.success(`已上传 ${fileList.length} 个文件`);
};
const handleRemoveFile = (pointId: string, fileName: string) => {
setFilesMap((prev) => ({
...prev,
[pointId]: (prev[pointId] || []).filter((f) => f.name !== fileName),
}));
};
const handleGenerate = async () => {
setGenerating(true);
try {
const { task_id } = await taskApi.triggerGeneration(templateId);
message.success("已创建生成任务,请前往任务历史查看");
onClose();
} catch (err: unknown) {
if (err instanceof Error) message.error(err.message);
} finally {
setGenerating(false);
}
};
const getModelName = (modelId: string | null) => {
if (!modelId) return "默认模型";
const m = models.find((x) => x.id === modelId);
return m?.name || modelId;
};
const pointsNeedRef = points.filter((p) => p.need_ref_file);
const pointsNoRef = points.filter((p) => !p.need_ref_file);
return (
<Modal
title="确认生成文档"
open={open}
onCancel={onClose}
width={700}
footer={
<Space>
<Button onClick={onClose}></Button>
<Button
type="primary"
danger
icon={<ThunderboltOutlined />}
onClick={handleGenerate}
loading={generating}
>
</Button>
</Space>
}
>
<p style={{ marginBottom: 16, color: "#666" }}>
{points.length} AI
</p>
{pointsNeedRef.length > 0 && (
<div style={{ marginBottom: 12 }}>
<Tag color="orange"> ({pointsNeedRef.length})</Tag>
</div>
)}
{points.map((point, idx) => {
const uploadedFiles = filesMap[point.id] || [];
const modelName = getModelName(point.model_id);
return (
<Card
key={point.id}
size="small"
style={{ marginBottom: 12 }}
title={
<Space size={4}>
<span style={{ fontSize: 12, color: "#999" }}>#{idx + 1}</span>
<span style={{ fontSize: 13 }}>{point.prompt.slice(0, 30)}{point.prompt.length > 30 ? "..." : ""}</span>
</Space>
}
extra={
<Tag color="blue" style={{ fontSize: 11 }}>{modelName}</Tag>
}
>
{point.remark && (
<p style={{ fontSize: 12, color: "#999", marginBottom: 8 }}>
{point.remark}
</p>
)}
{point.need_ref_file && (
<div>
<Upload
multiple
accept=".txt,.docx,.pdf"
showUploadList={false}
beforeUpload={(file) => {
handleUpload(point.id, [file]);
return false;
}}
>
<Button size="small" icon={<UploadOutlined />}>
</Button>
</Upload>
{uploadedFiles.length > 0 && (
<List
size="small"
style={{ marginTop: 8 }}
dataSource={uploadedFiles}
renderItem={(item) => (
<List.Item
extra={
<Popconfirm
title="删除此文件?"
onConfirm={() => handleRemoveFile(point.id, item.name)}
>
<Button type="text" size="small" danger icon={<DeleteOutlined />} />
</Popconfirm>
}
>
<FileTextOutlined style={{ marginRight: 6 }} />
<span style={{ fontSize: 12 }}>{item.name}</span>
</List.Item>
)}
/>
)}
</div>
)}
</Card>
);
})}
</Modal>
);
}