feat: 拆解单文件 Demo 为 Vite + React 前端工程

- 保留根目录 index.html 作为 v1 单文件原型(不参与构建)
- app/ 下新建 Vite + React 工程(vite.config root 指向 app/,两版互不干扰)
- 状态收敛到 useAppStore(表单/消息/串行确认/一键发品 + 4 条异步编排流)
- 生成卡片重构为结构化数据驱动渲染,按钮为真实 React 事件
- 6 大表单模块卡片化;Agent 意图解析与偏好学习抽为独立 lib
- 功能与视觉与 v1 完全一致,浏览器端全流程回归通过
This commit is contained in:
2026-09-16 13:37:58 +08:00
parent 609e0afb29
commit 01dcd83987
29 changed files with 3872 additions and 19 deletions
+63
View File
@@ -0,0 +1,63 @@
import { useStore } from '../store/context'
/** 生成卡片:head / lines / foot / tips,foot 按钮为真实 React 事件 */
export default function GenCardView({ gen, confirm }) {
const { applyGen, regen, showToast, serialConfirm, serialRegen, countdown } = useStore()
const { card } = gen
const serial = !!confirm
const body = (
<div className="gen-card">
<div className="gc-head">{card.head}</div>
<div className="gc-body">
{card.lines.map((l, i) => <span key={i} className="line" dangerouslySetInnerHTML={{ __html: l }} />)}
</div>
{card.foot && (
<div className="gc-foot">
<button
className="gc-btn"
onClick={() => card.foot.apply ? applyGen(card.foot.apply) : showToast('SEO 词已复制(演示)')}
>{card.foot.label}</button>
{card.foot.regen && <button className="gc-btn ghost" onClick={() => regen(gen.type)}>🔄 换一版</button>}
</div>
)}
{card.tips.length > 0 && (
<div className="tips-row">
{card.tips.map((t, i) => <span key={i} className={'tip-pill ' + t.cls}>{t.text}</span>)}
</div>
)}
</div>
)
/* 串行流程:外层包 serial-card,底部附确认栏 */
if (serial) {
return (
<div className="serial-card">
<div dangerouslySetInnerHTML={{ __html: gen.pre }} />
{body}
{confirm.status === 'confirmed' ? (
<div className="ag-confirm-bar" style={{ opacity: 0.55 }}>
<span style={{ color: '#22c55e', fontSize: 12, fontWeight: 600 }}>✓ 已确认,填入表单中…</span>
</div>
) : (
<div className="ag-confirm-bar">
<button className="ag-cf-btn" onClick={serialConfirm}>
{card.foot?.apply ? '✅ 确认填入' : '✅ 确认'}
</button>
<button className="ag-regen-btn" onClick={serialRegen}>🔄 换一版</button>
{confirm.mode
? <span className="ag-cd">{countdown}s 后自动确认</span>
: <span className="ag-cd manual">⏸ 等待您确认…</span>}
</div>
)}
</div>
)
}
return (
<div>
<div dangerouslySetInnerHTML={{ __html: gen.pre }} />
{body}
</div>
)
}