baodan/baodanppt/public/index.html

620 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>IP Insurance AI - 保险计划书助手</title>
<script>
if (location.protocol === 'file:') {
const target = 'http://localhost:3000/' + (location.search || '') + (location.hash || '');
location.replace(target);
}
</script>
<!-- Tailwind -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
<!-- 本地设计系统 token + 共享样式 -->
<link rel="stylesheet" href="/css/tokens.css">
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"surface-variant": "#e2e2e2",
"brand-gold-soft": "#FBF6EC",
"secondary-container": "#e2dfe1",
"on-error": "#ffffff",
"on-surface-variant": "#4f4537",
"on-secondary-fixed-variant": "#474649",
"surface-container-low": "#f3f3f3",
"surface-container-highest": "#e2e2e2",
"status-warning": "#FF9500",
"on-primary-fixed": "#281900",
"on-secondary-container": "#636264",
"surface": "#f9f9f9",
"inverse-primary": "#f4bd61",
"outline-variant": "#d3c4b2",
"on-secondary-fixed": "#1b1b1d",
"primary-fixed": "#ffdead",
"tertiary-fixed-dim": "#adc6ff",
"primary-fixed-dim": "#f4bd61",
"status-success": "#34C759",
"inverse-on-surface": "#f0f1f1",
"surface-dim": "#dadada",
"on-primary": "#ffffff",
"error-container": "#ffdad6",
"on-surface": "#1a1c1c",
"surface-bright": "#f9f9f9",
"tertiary": "#005bc1",
"surface-container-lowest": "#ffffff",
"tertiary-container": "#689dff",
"on-tertiary-fixed": "#001a41",
"surface-tint": "#7e5700",
"inverse-surface": "#2f3131",
"secondary-fixed-dim": "#c8c6c8",
"brand-gold-hover": "#A87E2A",
"on-primary-container": "#4a3100",
"tertiary-fixed": "#d8e2ff",
"background": "#f9f9f9",
"on-error-container": "#93000a",
"surface-container": "#eeeeee",
"outline": "#817565",
"secondary-fixed": "#e4e2e4",
"primary": "#7e5700",
"text-tertiary": "#AEAEB2",
"on-tertiary": "#ffffff",
"on-background": "#1a1c1c",
"secondary": "#5f5e60",
"surface-container-high": "#e8e8e8",
"on-primary-fixed-variant": "#604100",
"status-error": "#FF3B30",
"on-secondary": "#ffffff",
"border-subtle": "#E5E5EA",
"on-tertiary-fixed-variant": "#004493",
"text-secondary": "#6E6E73",
"error": "#ba1a1a",
"on-tertiary-container": "#003373",
"primary-container": "#c8963e"
},
"borderRadius": {
"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"
},
"spacing": {
"container-max": "1120px",
"margin-mobile": "16px",
"margin-desktop": "32px",
"stack-sm": "12px",
"base": "8px",
"gutter": "24px",
"stack-md": "24px",
"stack-lg": "40px"
},
"fontFamily": {
"display-mobile": ["SF Pro Display", "sans-serif"],
"h2-mobile": ["SF Pro Display", "sans-serif"],
"display": ["SF Pro Display", "sans-serif"],
"micro": ["SF Pro Display", "sans-serif"],
"body-lg": ["SF Pro Display", "sans-serif"],
"h3": ["SF Pro Display", "sans-serif"],
"h2": ["SF Pro Display", "sans-serif"],
"h1": ["SF Pro Display", "sans-serif"],
"caption": ["SF Pro Display", "sans-serif"],
"body": ["SF Pro Display", "sans-serif"]
},
"fontSize": {
"display-mobile": ["36px", { "lineHeight": "1.2", "fontWeight": "700" }],
"h2-mobile": ["24px", { "lineHeight": "1.3", "fontWeight": "600" }],
"display": ["56px", { "lineHeight": "1.25", "letterSpacing": "-0.02em", "fontWeight": "700" }],
"micro": ["11px", { "lineHeight": "1.2", "letterSpacing": "0.06em", "fontWeight": "600" }],
"body-lg": ["17px", { "lineHeight": "1.5", "letterSpacing": "0", "fontWeight": "400" }],
"h3": ["20px", { "lineHeight": "1.4", "letterSpacing": "-0.005em", "fontWeight": "600" }],
"h2": ["28px", { "lineHeight": "1.3", "letterSpacing": "-0.01em", "fontWeight": "600" }],
"h1": ["40px", { "lineHeight": "1.2", "letterSpacing": "-0.015em", "fontWeight": "700" }],
"caption": ["13px", { "lineHeight": "1.4", "letterSpacing": "0.02em", "fontWeight": "500" }],
"body": ["15px", { "lineHeight": "1.5", "letterSpacing": "0", "fontWeight": "400" }]
}
}
}
}
</script>
<style>
.material-symbols-outlined { vertical-align: middle; }
.drag-active { border-color: var(--primary-container) !important; background: rgba(200,150,62,0.05) !important; }
.markdown-prose p { margin-bottom: 0.5rem; }
.markdown-prose p:last-child { margin-bottom: 0; }
.markdown-prose strong { font-weight: 600; color: #1a1c1c; }
</style>
</head>
<body class="font-body text-on-surface antialiased bg-surface">
<!-- 步骤指示器挂载点 -->
<div id="stepsNavHost"></div>
<!-- ============================================================== -->
<!-- SCREEN 1: Upload -->
<!-- ============================================================== -->
<section id="screen-upload" class="screen pt-[80px] pb-[120px]">
<main class="px-margin-mobile py-stack-md w-full max-w-[640px] mx-auto">
<!-- Hero -->
<div class="mb-stack-lg">
<p class="text-[11px] font-semibold tracking-[0.1em] text-primary-container mb-3">INSURANCE PLAN AI</p>
<h1 class="text-h2-mobile md:text-h1 text-on-surface mb-3 leading-tight">把 PDF 计划书,<br/>变成有温度的演示文稿</h1>
<p class="text-body text-text-secondary leading-relaxed">上传客户的保单、计划书或财务报表。我们的 AI 引擎将提取关键条款并准备结构化分析数据。</p>
<div class="flex items-center space-x-1 text-text-tertiary mt-3">
<span class="material-symbols-outlined text-sm">lock</span>
<span class="text-caption">端到端加密。数据不会用于模型训练。</span>
</div>
</div>
<!-- 三端口上传区 -->
<div class="flex flex-col gap-4">
<!-- 储蓄险 -->
<div class="bg-surface-container-lowest rounded-2xl border border-border-subtle/50 p-4">
<div class="flex items-center gap-2 mb-3">
<span class="text-sm font-bold text-on-surface">📗 储蓄险</span>
<span class="text-caption text-text-secondary">(选填)</span>
<select id="companySavings" class="ml-auto h-8 px-2 text-xs bg-surface-container-low border border-border-subtle rounded-lg">
<option value="">选择公司...</option>
</select>
</div>
<div id="dropzoneSavings" class="border-2 border-dashed border-border-subtle rounded-xl p-4 flex flex-col items-center justify-center text-center h-28 transition-colors duration-300 cursor-pointer hover:border-primary-container">
<span class="material-symbols-outlined text-primary-container text-2xl">cloud_upload</span>
<p class="text-sm text-text-secondary mt-1">点击上传储蓄计划书 (PDF)</p>
<input type="file" accept=".pdf" class="hidden">
</div>
<div id="fileSavings" class="hidden mt-2 flex items-center gap-2 text-sm"><span class="material-symbols-outlined text-status-success text-lg">check_circle</span><span id="fileSavingsName"></span></div>
</div>
<!-- 重疾险 -->
<div class="bg-surface-container-lowest rounded-2xl border border-border-subtle/50 p-4">
<div class="flex items-center gap-2 mb-3">
<span class="text-sm font-bold text-on-surface">📕 重疾险</span>
<span class="text-caption text-text-secondary">(选填)</span>
<select id="companyCi" class="ml-auto h-8 px-2 text-xs bg-surface-container-low border border-border-subtle rounded-lg">
<option value="">选择公司...</option>
</select>
</div>
<div id="dropzoneCi" class="border-2 border-dashed border-border-subtle rounded-xl p-4 flex flex-col items-center justify-center text-center h-28 transition-colors duration-300 cursor-pointer hover:border-primary-container">
<span class="material-symbols-outlined text-primary-container text-2xl">cloud_upload</span>
<p class="text-sm text-text-secondary mt-1">点击上传重疾计划书 (PDF)</p>
<input type="file" accept=".pdf" class="hidden">
</div>
<div id="fileCi" class="hidden mt-2 flex items-center gap-2 text-sm"><span class="material-symbols-outlined text-status-success text-lg">check_circle</span><span id="fileCiName"></span></div>
</div>
<!-- IUL -->
<div class="bg-surface-container-lowest rounded-2xl border border-border-subtle/50 p-4">
<div class="flex items-center gap-2 mb-3">
<span class="text-sm font-bold text-on-surface">📙 IUL 万用寿险</span>
<span class="text-caption text-text-secondary">(选填)</span>
<select id="companyIul" class="ml-auto h-8 px-2 text-xs bg-surface-container-low border border-border-subtle rounded-lg">
<option value="">选择公司...</option>
</select>
</div>
<div id="dropzoneIul" class="border-2 border-dashed border-border-subtle rounded-xl p-4 flex flex-col items-center justify-center text-center h-28 transition-colors duration-300 cursor-pointer hover:border-primary-container">
<span class="material-symbols-outlined text-primary-container text-2xl">cloud_upload</span>
<p class="text-sm text-text-secondary mt-1">点击上传IUL计划书 (PDF)</p>
<input type="file" accept=".pdf" class="hidden">
</div>
<div id="fileIul" class="hidden mt-2 flex items-center gap-2 text-sm"><span class="material-symbols-outlined text-status-success text-lg">check_circle</span><span id="fileIulName"></span></div>
</div>
<div class="bg-surface-container-lowest rounded-2xl shadow-sm p-3 border border-border-subtle/50">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-on-surface">已选 <span id="uploadFileCount">0</span> 份计划书</span>
<button id="uploadClearBtn" class="text-caption text-text-tertiary hover:text-status-error">清空</button>
</div>
<p class="text-caption text-text-tertiary mt-2 leading-relaxed">如遇到无法解析的计划书,请将原计划书发至<a href="mailto:bxwang65@gmail.com" class="text-primary-container hover:underline">bxwang65@gmail.com</a>,感谢您的使用与反馈</p>
</div>
</div>
</main>
<!-- 底部固定操作栏 -->
<nav class="bg-surface/90 backdrop-blur-md border-t border-border-subtle fixed bottom-0 left-0 w-full z-40 flex justify-center items-center px-margin-mobile py-4">
<div class="w-full max-w-[640px] flex items-center justify-between gap-3">
<div class="flex flex-col">
<span class="text-sm font-medium text-on-surface" id="uploadSummary">请先选择 PDF 文件</span>
</div>
<button id="uploadStartBtn" disabled
class="flex items-center justify-center bg-primary text-on-primary rounded-xl h-12 px-6 text-body font-semibold active:scale-[0.98] transition-transform shadow-md">
开始 AI 解析
<span class="material-symbols-outlined ml-2 text-[20px]">arrow_forward</span>
</button>
</div>
</nav>
</section>
<!-- ============================================================== -->
<!-- SCREEN 2: Parsing -->
<!-- ============================================================== -->
<section id="screen-parsing" class="screen pt-24 pb-[120px]">
<main class="px-margin-mobile py-stack-md w-full max-w-[640px] mx-auto flex flex-col items-center">
<!-- 进度环 -->
<div class="flex flex-col items-center mb-10 fade-enter">
<div class="relative w-28 h-28 mb-6">
<svg class="w-full h-full" viewBox="0 0 120 120">
<circle class="text-surface-container-high stroke-current" cx="60" cy="60" fill="transparent" r="54" stroke-width="8"></circle>
<circle class="text-primary-container stroke-current" id="parsingCircle"
cx="60" cy="60" fill="transparent" r="54"
stroke-dasharray="339.292" stroke-dashoffset="339.292"
stroke-linecap="round" stroke-width="8"></circle>
</svg>
<div class="absolute inset-0 flex flex-col items-center justify-center">
<span class="text-h2-mobile text-on-surface font-bold" id="parsingPercent">0%</span>
</div>
</div>
<div class="text-center">
<h2 class="text-h2-mobile text-on-surface mb-2 font-semibold" id="parsingHeadline">正在提取数据对象</h2>
<p class="text-body text-text-secondary" id="parsingSubtext">正在识别关键保险条款和数据表格...</p>
</div>
</div>
<!-- 文件队列 -->
<div class="w-full flex flex-col gap-2">
<h3 class="text-h3 text-on-surface mb-1 px-1">处理队列</h3>
<div id="parsingFileList" class="flex flex-col gap-2"></div>
</div>
</main>
<!-- 底部按钮 -->
<footer class="fixed bottom-0 left-0 w-full z-40 px-margin-mobile pt-3 pb-8 bg-surface/90 backdrop-blur-md border-t border-border-subtle">
<div class="max-w-[640px] mx-auto flex gap-3">
<button id="parsingCancelBtn" class="w-full h-12 rounded-xl border border-border-subtle bg-surface-container-lowest text-on-surface text-body-lg font-medium active:bg-surface-container-high transition-colors flex items-center justify-center">
取消
</button>
<button id="parsingContinueBtn" class="w-full hidden items-center justify-center bg-primary text-on-primary rounded-xl h-12 text-body-lg font-medium transition-all active:scale-95 duration-200 shadow-md">
<span>继续</span>
<span class="material-symbols-outlined ml-2">arrow_forward</span>
</button>
</div>
</footer>
</section>
<!-- SCREEN 4: Generate -->
<!-- ============================================================== -->
<section id="screen-generate" class="screen pt-20 pb-[120px]">
<main class="max-w-container-max mx-auto px-margin-mobile md:px-margin-desktop py-stack-md md:py-stack-lg">
<!-- 标题 -->
<div class="text-center mb-stack-lg">
<p class="text-[11px] font-semibold tracking-[0.1em] text-primary-container mb-2">STEP 4 OF 5</p>
<h1 class="text-h1 text-on-surface mb-2">为这份方案选一个样子</h1>
<p class="text-body text-text-secondary">风格和公司是 PPT 的两个核心维度, 选定后我们立刻开始生成</p>
</div>
<div class="flex flex-col md:flex-row gap-gutter max-w-[1100px] mx-auto">
<!-- 左: 风格 + 公司 -->
<div class="w-full md:w-[55%] flex flex-col gap-stack-lg">
<!-- 风格 -->
<section>
<div class="flex items-baseline justify-between mb-stack-md">
<div>
<p class="text-[11px] font-semibold tracking-[0.1em] text-text-tertiary mb-1">00 · VALIDATION</p>
<h2 class="text-h3 text-on-surface">数据校验</h2>
</div>
<span id="validationStatusBadge" class="text-caption text-text-secondary">检查中</span>
</div>
<div id="validationPanel" class="bg-surface-container-lowest border border-border-subtle rounded-xl p-4 shadow-sm">
<div id="validationSummary" class="text-body text-text-secondary">正在检查年龄、保费、利益表与提领数据...</div>
<div id="validationIssues" class="mt-3 flex flex-col gap-2"></div>
</div>
</section>
<section>
<div class="flex items-baseline justify-between mb-stack-md">
<div>
<p class="text-[11px] font-semibold tracking-[0.1em] text-primary-container mb-1">01 · STYLE</p>
<h2 class="text-h2 text-on-surface">选择风格</h2>
</div>
<span class="text-caption text-text-secondary">5 选 1</span>
</div>
<div id="styleGrid" class="grid grid-cols-3 md:grid-cols-3 gap-base"></div>
</section>
<!-- 公司 -->
<section class="hidden">
<div class="flex items-baseline justify-between mb-stack-md">
<div>
<p class="text-[11px] font-semibold tracking-[0.1em] text-primary-container mb-1">02 · COMPANY</p>
<h2 class="text-h2 text-on-surface">选择公司</h2>
</div>
<span class="text-caption text-text-secondary">决定首页公司介绍</span>
</div>
<div id="companyGrid" class="grid grid-cols-1 md:grid-cols-2 gap-3"></div>
</section>
<!-- 公司介绍补充 -->
<section>
<div class="flex items-baseline justify-between mb-stack-md">
<div>
<p class="text-[11px] font-semibold tracking-[0.1em] text-text-tertiary mb-1">03 · OPTIONAL</p>
<h2 class="text-h3 text-on-surface">补充公司介绍</h2>
</div>
<span id="companyInfoCount" class="text-caption text-text-tertiary">0 / 200</span>
</div>
<textarea id="companyInfoTextarea" maxlength="300" rows="3"
placeholder="例: 重点强调我们的百年历史和亚洲市场份额"
class="w-full px-4 py-3 bg-surface-container-lowest border border-border-subtle rounded-xl text-body text-on-surface focus:outline-none focus:border-primary-container focus:ring-2 focus:ring-primary-container/20 resize-none"></textarea>
</section>
<section>
<div class="flex items-baseline justify-between mb-stack-md">
<div>
<p class="text-[11px] font-semibold tracking-[0.1em] text-text-tertiary mb-1">04 · OUTPUT</p>
<h2 class="text-h3 text-on-surface">导出格式与质量</h2>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="flex flex-col gap-2">
<div class="text-caption text-text-secondary">格式</div>
<div class="flex gap-2">
<button data-format="pptx" class="flex-1 h-10 rounded-lg border border-border-subtle bg-surface-container-lowest text-on-surface text-body font-medium">PPTX</button>
<button data-format="pdf" class="flex-1 h-10 rounded-lg border border-border-subtle bg-surface-container-lowest text-on-surface text-body font-medium">PDF</button>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="text-caption text-text-secondary">质量</div>
<div class="flex gap-2">
<button data-quality="high" class="flex-1 h-10 rounded-lg border border-border-subtle bg-surface-container-lowest text-on-surface text-body font-medium">正式版</button>
<button data-quality="standard" class="flex-1 h-10 rounded-lg border border-border-subtle bg-surface-container-lowest text-on-surface text-body font-medium">快速版</button>
</div>
</div>
</div>
<!-- AI 对话建议 -->
<section class="mt-6">
<label class="flex items-start gap-3 cursor-pointer group">
<input type="checkbox" id="useAiSummary" class="mt-0.5 w-4 h-4 rounded border-border-subtle accent-primary-container">
<div>
<p class="text-sm font-semibold text-on-surface">使用 AI 对话建议</p>
<p class="text-[11px] text-text-tertiary">勾选后,总结页将根据聊天对话内容由 AI 生成,不勾选则使用标准模板</p>
</div>
</label>
</section>
</section>
</div>
<!-- 右: 预览 + 按钮 -->
<div class="w-full md:w-[45%]">
<div class="md:sticky md:top-24">
<div class="flex items-center justify-between mb-stack-md">
<h2 class="text-h2 text-on-surface">封面预览</h2>
<span class="text-caption text-text-tertiary">改完即变</span>
</div>
<div class="p-3 bg-surface-container-low/50 border border-border-subtle rounded-xl shadow-sm">
<div id="previewCanvas" class="aspect-video bg-primary-container rounded-lg shadow-md relative overflow-hidden flex flex-col justify-end p-6 transition-all duration-500">
<div class="absolute top-4 left-4 flex items-center gap-2 text-white/80">
<span class="material-symbols-outlined text-[16px]">shield</span>
<span class="text-[10px] font-bold tracking-[0.1em]">IP INSURANCE AI</span>
</div>
<div class="relative z-10 border-l-[3px] border-white/80 pl-4 mb-8 max-w-[80%]">
<div class="text-white/70 text-[10px] font-bold tracking-[0.1em] mb-1">WEALTH LEGACY PLAN II</div>
<div class="text-white text-h3 font-bold">把家族的财富,写进时间</div>
</div>
<div class="absolute bottom-4 right-4 flex items-center gap-2 bg-white/10 backdrop-blur-md py-1.5 px-3 rounded-full border border-white/20">
<div id="previewLogo" class="w-6 h-6 bg-white rounded-full flex items-center justify-center text-primary-container font-bold text-sm">A</div>
<div id="previewCompanyName" class="text-white text-caption font-medium">友邦保险集团</div>
</div>
</div>
</div>
<div class="mt-3 flex items-center gap-2 text-text-secondary text-caption justify-center">
<span class="material-symbols-outlined text-[16px]">info</span>
<span>预览反映标准布局,最终成品将应用完整格式</span>
</div>
<button id="startGenerateBtn" disabled
class="mt-5 w-full h-14 bg-primary text-on-primary rounded-xl text-body-lg font-semibold flex items-center justify-center gap-2 shadow-md active:scale-[0.98] transition-transform">
<span class="material-symbols-outlined text-[20px]">auto_awesome</span>
开始生成 PPT
</button>
<button id="generateBackBtn" class="mt-2 w-full h-10 text-text-secondary text-body font-medium hover:text-on-surface">返回对话</button>
</div>
</div>
</div>
</main>
</section>
<!-- ============================================================== -->
<!-- SCREEN 5: Result -->
<!-- ============================================================== -->
<section id="screen-result" class="screen pt-20 pb-16">
<main class="px-margin-mobile w-full max-w-[720px] mx-auto flex flex-col items-center">
<!-- 成功反馈 -->
<section class="flex flex-col items-center text-center mb-stack-lg w-full">
<div class="relative w-16 h-16 mb-stack-md flex items-center justify-center bg-surface-container-lowest rounded-full shadow-sm border border-border-subtle">
<svg class="w-8 h-8 text-status-success" fill="none" viewBox="0 0 52 52">
<circle cx="26" cy="26" r="24" stroke="currentColor" stroke-linecap="round" stroke-width="3"
style="stroke-dasharray:150;stroke-dashoffset:150;animation:drawRing 0.8s ease-out forwards"></circle>
<path d="M16 26.5L23.5 34L37 18" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"
style="stroke-dasharray:50;stroke-dashoffset:50;animation:drawCheck 0.5s ease-out 0.6s forwards"></path>
</svg>
</div>
<p class="text-[11px] font-semibold tracking-[0.1em] text-status-success mb-2" style="animation:popIn 0.5s cubic-bezier(0.175,0.885,0.32,1.275) 0.6s forwards;opacity:0">GENERATION COMPLETE</p>
<h1 class="text-h1 text-on-surface mb-stack-sm" style="animation:popIn 0.5s cubic-bezier(0.175,0.885,0.32,1.275) 0.8s forwards;opacity:0">方案已准备就绪</h1>
<p class="text-body text-text-secondary" id="resultSummary" style="animation:popIn 0.5s cubic-bezier(0.175,0.885,0.32,1.275) 0.9s forwards;opacity:0">已为您生成定制方案</p>
<!-- 关键数据条 -->
<div class="mt-stack-md grid grid-cols-2 gap-3 w-full" style="animation:popIn 0.5s cubic-bezier(0.175,0.885,0.32,1.275) 1s forwards;opacity:0">
<div class="bg-surface-container-lowest border border-border-subtle rounded-lg px-3 py-2 flex items-center gap-2 shadow-sm justify-center">
<span class="material-symbols-outlined text-text-tertiary text-[16px]">style</span>
<span class="text-caption text-text-secondary">风格</span>
<span class="text-caption font-semibold text-on-surface" id="resultStyleName">专业券商风</span>
</div>
<div class="bg-surface-container-lowest border border-border-subtle rounded-lg px-3 py-2 flex items-center gap-2 shadow-sm justify-center">
<span class="material-symbols-outlined text-text-tertiary text-[16px]">slideshow</span>
<span class="text-caption text-text-secondary">页数</span>
<span id="resultMetaValue" class="text-caption font-semibold text-on-surface">已完成</span>
</div>
</div>
</section>
<!-- PPT 预览 + 操作 -->
<div class="w-full flex flex-col gap-stack-lg" style="animation:popIn 0.5s cubic-bezier(0.175,0.885,0.32,1.275) 1.1s forwards;opacity:0">
<!-- 大预览 -->
<div class="aspect-video bg-surface-container-low rounded-xl shadow-md relative overflow-hidden border border-border-subtle">
<img id="resultPreviewImage" alt="PPT 预览" class="w-full h-full object-cover hidden" />
<div id="resultPreviewEmpty" class="absolute inset-0 flex items-center justify-center text-text-secondary text-body">预览生成后显示</div>
<div id="resultPreviewBadge" class="absolute top-4 right-4 bg-white/85 backdrop-blur-sm px-2 py-1 rounded-full text-caption text-on-surface border border-border-subtle hidden">01 / 01</div>
</div>
<!-- 缩略图 -->
<div id="resultThumbs" class="w-full flex gap-2 overflow-x-auto hide-scrollbar py-1"></div>
<!-- 下载操作 -->
<div class="w-full bg-surface-container-lowest rounded-[20px] p-5 shadow-md border border-border-subtle flex flex-col gap-4">
<div>
<h3 class="text-h3 text-on-surface mb-1">导出方案</h3>
<p class="text-caption text-text-secondary" id="resultFilename">plan.pptx</p>
</div>
<div class="flex flex-col gap-3">
<button id="resultDownloadBtn" class="h-12 w-full bg-primary-container hover:bg-brand-gold-hover text-white rounded-lg text-body font-semibold flex items-center justify-center gap-2 transition-all shadow-sm active:scale-95 duration-200">
<span class="material-symbols-outlined" style="font-variation-settings:'FILL' 1">file_download</span>
下载 .PPTX
</button>
<button id="resultDownloadMdBtn" style="display:none" class="h-12 w-full bg-surface-container-lowest border border-border-subtle hover:border-text-tertiary text-on-surface rounded-lg text-body font-semibold flex items-center justify-center gap-2 transition-all active:scale-95 duration-200">
<span class="material-symbols-outlined text-text-secondary">code</span>
导出为 Markdown
</button>
<button id="resultSummaryBtn" class="h-12 w-full bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700 text-white rounded-lg text-body font-semibold flex items-center justify-center gap-2 transition-all active:scale-95 duration-200 shadow-sm">
<span class="material-symbols-outlined" style="font-variation-settings:'FILL' 1">image</span>
生成保单摘要图
</button>
</div>
<div id="summaryExportArea" style="display:none;"></div>
<div class="h-px w-full bg-border-subtle"></div>
<div class="flex flex-col gap-1">
<button id="resultRegenerateBtn" class="text-left w-full px-3 py-3 rounded-md hover:bg-surface-container-low text-text-secondary hover:text-on-surface text-caption transition-colors flex items-center gap-2">
<span class="material-symbols-outlined text-[18px]">refresh</span> 调整后重新生成
</button>
<button id="resultNewBtn" class="text-left w-full px-3 py-3 rounded-md hover:bg-surface-container-low text-text-secondary hover:text-on-surface text-caption transition-colors flex items-center gap-2">
<span class="material-symbols-outlined text-[18px]">add</span> 新建一个方案
</button>
</div>
</div>
</div>
</main>
</section>
<style>
@keyframes drawRing { to { stroke-dashoffset: 0; } }
@keyframes drawCheck { to { stroke-dashoffset: 0; } }
@keyframes popIn { to { opacity: 1; transform: scale(1); } }
.hide-scrollbar::-webkit-scrollbar { display: none; }
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
</style>
<!-- html2canvas (用于导出摘要图) -->
<script src="/js/html2canvas.min.js"></script>
<!-- App 入口 (ES Modules) -->
<script type="module" src="/js/app.js"></script>
<!-- 浮动 AI 设置按钮 -->
<button onclick="openAiSettings()" id="aiSettingsFab"
class="fixed bottom-24 right-4 z-30 w-12 h-12 rounded-full bg-primary text-on-primary shadow-lg active:scale-95 transition-transform flex items-center justify-center"
title="AI 服务设置">
<span class="material-symbols-outlined text-[24px]">key</span>
</button>
<!-- AI 设置 Modal (DeepSeek / OpenAI / Gemini 自定义 key) -->
<div id="aiSettingsModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/40 backdrop-blur-sm" onclick="if(event.target===this) closeAiSettings()">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md mx-4 p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-h3 font-semibold text-on-surface">AI 服务设置</h3>
<button onclick="closeAiSettings()" class="text-text-secondary hover:text-on-surface">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<p class="text-caption text-text-secondary mb-4">
输入你自己的 AI 服务 API key, 让解析/对话/PPT 走你的 key。
留空则用服务端环境变量。无 key 时, 系统会回退到 signature fast-path (无需 LLM 也能解析常见储蓄险/重疾险)。
</p>
<div class="space-y-3">
<div>
<label class="text-caption text-text-secondary block mb-1">Provider</label>
<select id="aiSettingsProvider" class="w-full h-10 px-3 rounded-lg border border-border-subtle bg-surface-container-lowest text-body">
<option value="deepseek">DeepSeek-V4-Flash (推荐, 便宜, ¥0.13/份)</option>
<option value="minimax">MiniMax-M3 (便宜, 大 PDF 偶超时)</option>
<option value="kimi">Kimi-2.7 (历史最快, 数据最全)</option>
</select>
</div>
<div>
<label class="text-caption text-text-secondary block mb-1">API Key</label>
<input id="aiSettingsKey" type="password" placeholder="sk-..." autocomplete="off"
class="w-full h-10 px-3 rounded-lg border border-border-subtle bg-surface-container-lowest text-body focus:outline-none focus:border-brand-gold">
<p class="text-caption text-text-tertiary mt-1">仅存于本机 localStorage, 不上传服务端。</p>
</div>
<div id="aiSettingsStatus" class="hidden text-caption"></div>
</div>
<div class="flex justify-end gap-2 mt-6">
<button onclick="clearAiSettings()" class="px-4 h-10 rounded-lg text-caption text-status-error border border-status-error/30 hover:bg-status-error/5">清除</button>
<button onclick="saveAiSettings()" class="px-5 h-10 rounded-lg bg-primary text-on-primary text-caption font-medium active:scale-95 transition-transform">保存</button>
</div>
</div>
</div>
<script>
function openAiSettings() {
const m = document.getElementById('aiSettingsModal');
m.classList.remove('hidden');
m.classList.add('flex');
// 加载已存 key
const k = localStorage.getItem('userApiKey') || '';
const p = localStorage.getItem('userApiProvider') || 'deepseek';
document.getElementById('aiSettingsKey').value = k;
document.getElementById('aiSettingsProvider').value = p;
updateAiStatus(k);
}
function closeAiSettings() {
const m = document.getElementById('aiSettingsModal');
m.classList.add('hidden');
m.classList.remove('flex');
}
function saveAiSettings() {
const k = document.getElementById('aiSettingsKey').value.trim();
const p = document.getElementById('aiSettingsProvider').value;
if (k) {
localStorage.setItem('userApiKey', k);
localStorage.setItem('userApiProvider', p);
} else {
localStorage.removeItem('userApiKey');
}
updateAiStatus(k);
setTimeout(closeAiSettings, 800);
}
function clearAiSettings() {
localStorage.removeItem('userApiKey');
localStorage.removeItem('userApiProvider');
document.getElementById('aiSettingsKey').value = '';
document.getElementById('aiSettingsProvider').value = 'deepseek';
updateAiStatus('');
}
function updateAiStatus(k) {
const el = document.getElementById('aiSettingsStatus');
if (k) {
el.textContent = '✓ 已保存 key (前缀 ' + k.substring(0, 4) + '...)';
el.className = 'text-caption text-status-success';
} else {
el.textContent = '⚠ 未设置 key, 将使用服务端环境变量或 fast-path';
el.className = 'text-caption text-status-warning';
}
el.classList.remove('hidden');
}
// 暴露给全局 (因为 inline onclick 引用)
window.openAiSettings = openAiSettings;
window.closeAiSettings = closeAiSettings;
window.saveAiSettings = saveAiSettings;
window.clearAiSettings = clearAiSettings;
</script>
</body>
</html>