180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
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>
|
||
);
|
||
}
|