《60天AI学习计划启动 | Day 48: 前端标注/纠错 UI(用户修正 AI 答案并回流)》
Day 48:前端标注/纠错 UI(用户修正 AI 答案并回流)
学习目标
- 理解 点赞/点踩、文本反馈、完整纠错这三种反馈层级
- 设计 一份「原问题 + 原回答 + 用户修订」的反馈数据结构
- 实现 一个最小可用的“纠错编辑 + 提交”前端组件
核心要点
-
反馈粒度:
- 轻量:👍 / 👎
- 中等:文本说明「哪里不好 / 期望是什么」
- 重度:用户直接改出“更好的回答”(后端可用于训练/评估)
-
反馈数据结构(关键字段):
export interface CorrectionFeedback { id: string question: string originalAnswer: string correctedAnswer: string reason?: string createdAt: number userId?: string }
实战作业(附代码)
作业 1:定义反馈类型
export interface CorrectionFeedback {
id: string
question: string
originalAnswer: string
correctedAnswer: string
reason?: string
createdAt: number
userId?: string
}
作业 2:纠错编辑组件(React)
import React, { useState } from 'react'
import type { CorrectionFeedback } from './types'
interface Props {
question: string
answer: string
userId?: string
}
export const AnswerWithCorrection: React.FC<Props> = ({ question, answer, userId }) => {
const [showEdit, setShowEdit] = useState(false)
const [text, setText] = useState(answer)
const [reason, setReason] = useState('')
const [submitting, setSubmitting] = useState(false)
const [submitted, setSubmitted] = useState(false)
const submitCorrection = async () => {
if (!text.trim() || text.trim() === answer.trim()) {
alert('修改后内容不能为空且需有变化')
return
}
setSubmitting(true)
const payload: CorrectionFeedback = {
id: crypto.randomUUID(),
question,
originalAnswer: answer,
correctedAnswer: text.trim(),
reason: reason.trim(),
createdAt: Date.now(),
userId
}
try {
await fetch('/api/feedback/correction', {
method: 'POST',
headers: { 'Content-Type':'application/json' },
body: JSON.stringify(payload)
})
setSubmitted(true)
setShowEdit(false)
} catch {
alert('提交失败,请稍后再试')
} finally {
setSubmitting(false)
}
}
return (
<div style={{ border:'1px solid #eee', padding:8, marginTop:8 }}>
<div>
<strong>AI 回答:</strong>
<div style={{ whiteSpace:'pre-wrap' }}>{answer}</div>
</div>
{submitted && <div style={{ color:'green', fontSize:12 }}>已提交纠错,感谢反馈</div>}
{!showEdit ? (
<button onClick={() => setShowEdit(true)} style={{ marginTop:4 }}>
我来改一个更好的回答
</button>
) : (
<div style={{ marginTop:4 }}>
<textarea
rows={5}
style={{ width:'100%' }}
value={text}
onChange={e => setText(e.target.value)}
/>
<textarea
rows={2}
style={{ width:'100%', marginTop:4 }}
placeholder="简单说明你修改的理由(可选)"
value={reason}
onChange={e => setReason(e.target.value)}
/>
<button onClick={submitCorrection} disabled={submitting}>
{submitting ? '提交中...' : '提交纠错'}
</button>
<button onClick={() => { setShowEdit(false); setText(answer) }} style={{ marginLeft:8 }}>
取消
</button>
</div>
)}
</div>
)
}
明日学习计划预告(Day 49)
- 主题:Embedding 可视化(降维 + 散点/聚类展示)
- 方向:
- 设计一个简单的前端视图:用 2D 点展示文本/文档的 Embedding 分布
- 思考如何用颜色/大小标识不同类别或被问到的频率

浙公网安备 33010602011771号