Files
pj0235-eai_agentplatform/eai_agentplatform/backend-go/internal/api/profile.go
T
eaiadminandClaude Code 0455f064ac feat: 新增语音转文字(ASR)功能
- 后端:新增 /api/audio/transcribe 接口,调用 Ollama whisper 进行语音识别
- 前端:新增 AudioTranscribePage.vue 页面,支持 MP3/WAV/M4A/OGG/FLAC 等格式
- 注册路由、工具卡片、智能助手欢迎语更新

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-14 00:53:36 +08:00

163 lines
4.2 KiB
Go

package api
import (
"encoding/json"
"github.com/gin-gonic/gin"
"eai_agentplatform/backend/internal/middleware"
"eai_agentplatform/backend/internal/model"
"eai_agentplatform/backend/internal/store"
"eai_agentplatform/backend/internal/web"
)
// domainLabels 能力雷达维度(正式考试按域聚合的掌握度)。
var domainLabels = map[string]string{
"company": "公司认知",
"product": "产品知识",
"sales": "销售能力",
}
// domainOrder 雷达图固定排序。
var domainOrder = []string{"company", "product", "sales"}
// detailWrap 正式考试答题明细(与 ExamSubmit 持久化的 detail_json 结构对齐)。
type detailWrap struct {
Questions []struct {
QuestionID uint `json:"question_id"`
IsCorrect bool `json:"is_correct"`
} `json:"questions"`
}
// MyProfile GET /api/my/profile —— 学员学习档案:能力雷达 + 学习统计 + 成绩趋势 + 积分。
func MyProfile(c *gin.Context) {
u := middleware.CurrentUser(c)
if u == nil {
web.Fail(c, web.NewAuthError("未登录"))
return
}
// 学习进度(公司/产品/课程浏览)
var lps []model.LearningProgress
store.DB.Where("user_id = ?", u.ID).Find(&lps)
companyViewed := false
productViewed, courseViewed := 0, 0
for _, lp := range lps {
switch lp.ItemType {
case "company":
companyViewed = true
case "product":
productViewed++
case "course":
courseViewed++
}
}
// 错题统计
var mistakeTotal, mistakeResolved int64
store.DB.Model(&model.MistakeRecord{}).Where("user_id = ?", u.ID).Count(&mistakeTotal)
store.DB.Model(&model.MistakeRecord{}).Where("user_id = ? AND resolved = ?", u.ID, true).Count(&mistakeResolved)
// 自测次数(自测不落 exam_record,改由积分流水统计)
var selfTestCount int64
store.DB.Model(&model.PointEvent{}).Where("user_id = ? AND event_type = ?", u.ID, "self_test").Count(&selfTestCount)
// 正式考试记录 + 按域能力聚合
var recs []model.ExamRecord
store.DB.Where("user_id = ?", u.ID).Order("submitted_at ASC").Find(&recs)
// 题目 → 域映射(用于从答题明细反推各域掌握度)
var questions []model.Question
store.DB.Select("id", "domain").Find(&questions)
qDomain := map[uint]string{}
for _, q := range questions {
qDomain[q.ID] = q.Domain
}
type domainAgg struct {
Correct int `json:"correct"`
Total int `json:"total"`
}
perDomain := map[string]*domainAgg{}
formalPassed := 0
trend := make([]gin.H, 0, len(recs))
for _, r := range recs {
if r.Passed {
formalPassed++
}
trend = append(trend, gin.H{
"exam_name": r.ExamName,
"score": r.Score,
"total_score": r.TotalScore,
"passed": r.Passed,
"submitted_at": r.SubmittedAt,
})
var dw detailWrap
if err := json.Unmarshal([]byte(r.DetailJSON), &dw); err != nil {
continue
}
for _, qd := range dw.Questions {
domain := qDomain[qd.QuestionID]
if domain == "" {
continue
}
a := perDomain[domain]
if a == nil {
a = &domainAgg{}
perDomain[domain] = a
}
a.Total++
if qd.IsCorrect {
a.Correct++
}
}
}
domains := make([]gin.H, 0, len(domainOrder))
for _, d := range domainOrder {
a := perDomain[d]
if a == nil {
a = &domainAgg{}
}
mastery := 0.0
if a.Total > 0 {
mastery = round1(float64(a.Correct) * 100 / float64(a.Total))
}
domains = append(domains, gin.H{
"domain": d,
"label": domainLabels[d],
"correct": a.Correct,
"total": a.Total,
"mastery": mastery,
})
}
// 薄弱点:有作答记录中掌握度最低的域
weakDomain := ""
weakMastery := 100.0
for _, d := range domains {
if d["total"].(int) > 0 && d["mastery"].(float64) < weakMastery {
weakMastery = d["mastery"].(float64)
weakDomain = d["domain"].(string)
}
}
web.OK(c, gin.H{
"learning_points": u.LearningPoints,
"stats": gin.H{
"company_viewed": companyViewed,
"product_viewed": productViewed,
"course_viewed": courseViewed,
"self_test_count": selfTestCount,
"formal_count": len(recs),
"formal_passed": formalPassed,
"mistake_count": mistakeTotal,
"mistake_resolved": mistakeResolved,
},
"domains": domains,
"weak_domain": weakDomain,
"recent_scores": trend,
})
}