AI 生成: 一个基于 Flask 的 Web 应用,用户输入主题后由 AI 生成结构化的登记手册,包含封面、目录、正文与附录,并支持导出为 Markdown 文件。
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
const topicInput = document.getElementById("topic");
|
||||
const generateBtn = document.getElementById("generate-btn");
|
||||
const exportBtn = document.getElementById("export-btn");
|
||||
const statusEl = document.getElementById("status");
|
||||
const outputEl = document.getElementById("output");
|
||||
|
||||
let current = { topic: "", markdown: "" };
|
||||
|
||||
function setStatus(text, isError = false) {
|
||||
statusEl.textContent = text;
|
||||
statusEl.classList.toggle("error", isError);
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
const topic = topicInput.value.trim();
|
||||
if (!topic) {
|
||||
setStatus("请先输入主题", true);
|
||||
return;
|
||||
}
|
||||
|
||||
generateBtn.disabled = true;
|
||||
exportBtn.disabled = true;
|
||||
setStatus("生成中,请稍候…");
|
||||
outputEl.textContent = "正在生成…";
|
||||
|
||||
try {
|
||||
const resp = await fetch("/api/generate", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ topic }),
|
||||
});
|
||||
const data = await resp.json();
|
||||
|
||||
if (!resp.ok) {
|
||||
throw new Error(data.error || "生成失败");
|
||||
}
|
||||
|
||||
current = { topic: data.topic, markdown: data.markdown };
|
||||
outputEl.textContent = data.markdown;
|
||||
exportBtn.disabled = false;
|
||||
setStatus("生成完成");
|
||||
} catch (err) {
|
||||
outputEl.textContent = "生成失败";
|
||||
setStatus(err.message, true);
|
||||
} finally {
|
||||
generateBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function exportMarkdown() {
|
||||
if (!current.markdown) return;
|
||||
|
||||
try {
|
||||
const resp = await fetch("/api/export", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(current),
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
throw new Error(data.error || "导出失败");
|
||||
}
|
||||
|
||||
const blob = await resp.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${current.topic}-登记手册.md`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
setStatus("已开始下载");
|
||||
} catch (err) {
|
||||
setStatus(err.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
generateBtn.addEventListener("click", generate);
|
||||
exportBtn.addEventListener("click", exportMarkdown);
|
||||
topicInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") generate();
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||
"Microsoft YaHei", sans-serif;
|
||||
background: #f5f7fb;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 20px 64px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 24px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.panel label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type="text"]:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 18px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #2563eb;
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: #ffffff;
|
||||
color: #2563eb;
|
||||
border: 1px solid #2563eb;
|
||||
}
|
||||
|
||||
button.secondary:hover:not(:disabled) {
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin: 12px 0 0;
|
||||
min-height: 20px;
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.panel-head h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.output {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
}
|
||||
Reference in New Issue
Block a user