feat: 新增语音转文字(ASR)功能
- 后端:新增 /api/audio/transcribe 接口,调用 Ollama whisper 进行语音识别 - 前端:新增 AudioTranscribePage.vue 页面,支持 MP3/WAV/M4A/OGG/FLAC 等格式 - 注册路由、工具卡片、智能助手欢迎语更新 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"eai_agentplatform/backend/internal/middleware"
|
||||
"eai_agentplatform/backend/internal/model"
|
||||
"eai_agentplatform/backend/internal/store"
|
||||
"eai_agentplatform/backend/internal/web"
|
||||
)
|
||||
|
||||
// —— 积分规则(游戏化成长值,集中一处便于调整,见 docs/changelog.md 待确认项)——
|
||||
const (
|
||||
ptFirstCompany = 10 // 首次浏览公司介绍
|
||||
ptFirstProduct = 2 // 首次浏览单个产品
|
||||
ptFirstCourse = 5 // 首次浏览单门课程
|
||||
ptSelfTestSubmit = 5 // 自测提交一次
|
||||
ptFormalPass = 50 // 正式考试通过
|
||||
ptMistakeResolved = 3 // 错题标记已掌握
|
||||
)
|
||||
|
||||
// awardPoints 记录积分流水并累加到 user.learning_points(points<=0 直接忽略,避免负分刷分)。
|
||||
func awardPoints(userID uint, eventType string, points int, refType string, refID uint) {
|
||||
if userID == 0 || points <= 0 {
|
||||
return
|
||||
}
|
||||
store.DB.Create(&model.PointEvent{
|
||||
UserID: userID,
|
||||
EventType: eventType,
|
||||
Points: points,
|
||||
RefType: refType,
|
||||
RefID: refID,
|
||||
})
|
||||
store.DB.Model(&model.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("learning_points", gorm.Expr("learning_points + ?", points))
|
||||
}
|
||||
|
||||
// awardFirstView 首次浏览公司/产品/课程加分(由 RecordLearningProgress 幂等触发)。
|
||||
func awardFirstView(userID uint, itemType string, itemID uint) {
|
||||
switch itemType {
|
||||
case "company":
|
||||
awardPoints(userID, "first_company", ptFirstCompany, itemType, itemID)
|
||||
case "product":
|
||||
awardPoints(userID, "first_product", ptFirstProduct, itemType, itemID)
|
||||
case "course":
|
||||
awardPoints(userID, "first_course", ptFirstCourse, itemType, itemID)
|
||||
}
|
||||
}
|
||||
|
||||
// MyPoints GET /api/points/me —— 我的积分 + 最近积分流水
|
||||
func MyPoints(c *gin.Context) {
|
||||
u := middleware.CurrentUser(c)
|
||||
if u == nil {
|
||||
web.Fail(c, web.NewAuthError("未登录"))
|
||||
return
|
||||
}
|
||||
var total int
|
||||
store.DB.Raw(`SELECT COALESCE(SUM(points), 0) FROM point_event WHERE user_id = ?`, u.ID).Scan(&total)
|
||||
var events []model.PointEvent
|
||||
store.DB.Where("user_id = ?", u.ID).Order("created_at DESC, id DESC").Limit(50).Find(&events)
|
||||
web.OK(c, gin.H{"total": total, "events": events})
|
||||
}
|
||||
|
||||
// Leaderboard GET /api/points/leaderboard —— 学习积分排行榜(全员,含我的名次)
|
||||
func Leaderboard(c *gin.Context) {
|
||||
u := middleware.CurrentUser(c)
|
||||
var users []model.User
|
||||
store.DB.Where("role = ? AND status = ?", "employee", "active").
|
||||
Order("learning_points DESC, id ASC").Find(&users)
|
||||
|
||||
out := make([]gin.H, 0, len(users))
|
||||
myRank := 0
|
||||
for i, usr := range users {
|
||||
out = append(out, gin.H{
|
||||
"rank": i + 1,
|
||||
"user_id": usr.ID,
|
||||
"full_name": usr.FullName,
|
||||
"department": usr.Department,
|
||||
"learning_points": usr.LearningPoints,
|
||||
"is_me": u != nil && usr.ID == u.ID,
|
||||
})
|
||||
if u != nil && usr.ID == u.ID {
|
||||
myRank = i + 1
|
||||
}
|
||||
}
|
||||
web.OK(c, gin.H{"items": out, "my_rank": myRank, "total": len(out), "updated_at": time.Now()})
|
||||
}
|
||||
Reference in New Issue
Block a user