《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 分布
    • 思考如何用颜色/大小标识不同类别或被问到的频率
posted @ 2025-12-17 11:42  XiaoZhengTou  阅读(1)  评论(0)    收藏  举报