feat: 新增 AI 记账功能,包括流式聊天、洞察生成、语音转录和交易确认接口。

This commit is contained in:
2026-01-30 12:48:41 +08:00
parent 8bae0df1b6
commit c4d7825328
2 changed files with 350 additions and 0 deletions

View File

@@ -2,6 +2,7 @@ package handler
import (
"encoding/json"
"io"
"net/http"
"accounting-app/internal/service"
@@ -14,6 +15,61 @@ type AIHandler struct {
aiService *service.AIBookkeepingService
}
// StreamChat handles streaming chat messages
// POST /api/v1/ai/chat/stream
func (h *AIHandler) StreamChat(c *gin.Context) {
var req ChatRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": "Invalid request: " + err.Error(),
})
return
}
// Get user ID from context
userID := uint(1)
if id, exists := c.Get("user_id"); exists {
userID = id.(uint)
}
// Set headers for SSE
c.Writer.Header().Set("Content-Type", "text/event-stream")
c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Transfer-Encoding", "chunked")
c.Stream(func(w io.Writer) bool {
// Define the callback for chunks
onChunk := func(chunk string) {
// Sanitize chunk for SSE format (replace newlines to avoid breaking the stream protocol,
// or just send raw if client handles it. Usually data: <content>\n\n)
// For robustness, we JSON encode the data payload
dataMap := map[string]string{"text": chunk}
jsonData, _ := json.Marshal(dataMap)
// message event
c.SSEvent("message", string(jsonData))
}
// Call service
response, err := h.aiService.StreamProcessChat(c.Request.Context(), userID, req.SessionID, req.Message, onChunk)
if err != nil {
// Send error event
errMap := map[string]string{"error": err.Error()}
jsonErr, _ := json.Marshal(errMap)
c.SSEvent("error", string(jsonErr))
return false
}
// Send final result event with metadata
jsonResult, _ := json.Marshal(response)
c.SSEvent("result", string(jsonResult))
return false // Stop stream
})
}
// NewAIHandler creates a new AIHandler
func NewAIHandler(aiService *service.AIBookkeepingService) *AIHandler {
return &AIHandler{
@@ -47,6 +103,7 @@ func (h *AIHandler) RegisterRoutes(rg *gin.RouterGroup) {
ai := rg.Group("/ai")
{
ai.POST("/chat", h.Chat)
ai.POST("/chat/stream", h.StreamChat) // New streaming endpoint
ai.POST("/transcribe", h.Transcribe)
ai.POST("/confirm", h.Confirm)
ai.POST("/insight", h.Insight)