feat: 新增 AI 记账功能,包括流式聊天、洞察生成、语音转录和交易确认接口。
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user