package services import ( "bytes" "encoding/json" "errors" "fmt" "io" "lost-and-found/internal/models" "lost-and-found/internal/repositories" "net/http" "os" "strings" "gorm.io/gorm" ) type AIService struct { db *gorm.DB chatRepo *repositories.ChatRepository itemRepo *repositories.ItemRepository lostItemRepo *repositories.LostItemRepository groqAPIKey string groqModel string } func NewAIService(db *gorm.DB) *AIService { model := os.Getenv("GROQ_MODEL") if model == "" { model = "llama-3.3-70b-versatile" // Default model } return &AIService{ db: db, chatRepo: repositories.NewChatRepository(db), itemRepo: repositories.NewItemRepository(db), lostItemRepo: repositories.NewLostItemRepository(db), groqAPIKey: os.Getenv("GROQ_API_KEY"), groqModel: model, } } type ChatRequest struct { Message string `json:"message" binding:"required"` } // Groq API Request Structure type GroqRequest struct { Model string `json:"model"` Messages []GroqMessage `json:"messages"` Temperature float64 `json:"temperature,omitempty"` MaxTokens int `json:"max_tokens,omitempty"` TopP float64 `json:"top_p,omitempty"` Stream bool `json:"stream"` } type GroqMessage struct { Role string `json:"role"` Content string `json:"content"` } // Groq API Response Structure type GroqResponse struct { ID string `json:"id"` Object string `json:"object"` Created int64 `json:"created"` Model string `json:"model"` Choices []struct { Index int `json:"index"` Message struct { Role string `json:"role"` Content string `json:"content"` } `json:"message"` FinishReason string `json:"finish_reason"` } `json:"choices"` Usage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` } `json:"usage"` } func (s *AIService) ProcessChat(userID uint, message string) (*models.ChatMessage, error) { // Build context from user data context, err := s.buildUserContext(userID, message) if err != nil { return nil, err } // Detect intent intent := s.detectIntent(message) // Build prompt with context systemPrompt := s.buildSystemPrompt() userPrompt := s.buildUserPrompt(message, context, intent) // Call Groq API response, err := s.callGroqAPI(systemPrompt, userPrompt) if err != nil { return nil, err } // Save to database chat := &models.ChatMessage{ UserID: userID, Message: message, Response: response, Intent: intent, ConfidenceScore: 85.0, } if err := s.chatRepo.Create(chat); err != nil { return nil, err } return chat, nil } func (s *AIService) buildUserContext(userID uint, message string) (string, error) { var context strings.Builder // Get user's lost items lostItems, _, _ := s.lostItemRepo.FindByUser(userID, 1, 5) if len(lostItems) > 0 { context.WriteString("\nšŸ“‹ Barang yang dilaporkan hilang:\n") for _, item := range lostItems { context.WriteString(fmt.Sprintf("- %s (%s) - Status: %s\n", item.Name, item.Category.Name, item.Status)) } } // Search for relevant found items if user is looking for something if strings.Contains(strings.ToLower(message), "cari") || strings.Contains(strings.ToLower(message), "temukan") { items, _, _ := s.itemRepo.FindAll(1, 5, "unclaimed", "", message) if len(items) > 0 { context.WriteString("\nšŸ” Barang ditemukan yang relevan:\n") for _, item := range items { context.WriteString(fmt.Sprintf("- ID: %d, %s (%s) - Lokasi: %s\n", item.ID, item.Name, item.Category.Name, item.Location)) } } } return context.String(), nil } func (s *AIService) detectIntent(message string) string { msgLower := strings.ToLower(message) searchKeywords := []string{"cari", "temukan", "ada", "lihat", "ditemukan"} reportKeywords := []string{"hilang", "kehilangan", "lapor", "laporkan"} claimKeywords := []string{"klaim", "ambil", "punya saya", "milik saya"} for _, kw := range searchKeywords { if strings.Contains(msgLower, kw) { return models.IntentSearchItem } } for _, kw := range reportKeywords { if strings.Contains(msgLower, kw) { return models.IntentReportLost } } for _, kw := range claimKeywords { if strings.Contains(msgLower, kw) { return models.IntentClaimHelp } } return models.IntentGeneral } func (s *AIService) buildSystemPrompt() string { return `Kamu adalah asisten AI untuk sistem Lost & Found kampus bernama "FindItBot". Tugasmu adalah membantu mahasiswa dan staff dengan: 1. šŸ” Mencari barang yang hilang/ditemukan 2. šŸ“ Memandu proses pelaporan barang hilang 3. āœ… Menjelaskan proses klaim barang 4. ā“ Menjawab pertanyaan umum tentang sistem Aturan penting: - Jawab dengan ramah, profesional, dan membantu - Gunakan Bahasa Indonesia yang jelas - Jika ada data barang yang relevan, sebutkan ID dan detailnya - Untuk pelaporan, tanyakan: nama barang, kategori, lokasi, tanggal hilang, deskripsi - Untuk klaim, jelaskan proses verifikasi yang diperlukan - Gunakan emoji yang sesuai untuk memperjelas informasi - Prioritaskan informasi dari konteks yang diberikan Contoh respons yang baik: "šŸ” Saya menemukan 2 barang yang mungkin cocok: 1. ID: 123 - Dompet Kulit (Kategori: Wallet) - Ditemukan di Perpustakaan 2. ID: 124 - Dompet Hitam (Kategori: Wallet) - Ditemukan di Kantin Apakah salah satu dari ini milik Anda? Anda bisa klaim dengan menyebutkan ID barangnya."` } func (s *AIService) buildUserPrompt(message, context, intent string) string { var prompt strings.Builder if context != "" { prompt.WriteString("KONTEKS PENGGUNA:\n") prompt.WriteString(context) prompt.WriteString("\n\n") } prompt.WriteString(fmt.Sprintf("INTENT TERDETEKSI: %s\n\n", intent)) prompt.WriteString(fmt.Sprintf("PERTANYAAN: %s\n\n", message)) prompt.WriteString("Berikan respons yang membantu berdasarkan konteks di atas.") return prompt.String() } func (s *AIService) callGroqAPI(systemPrompt, userPrompt string) (string, error) { if s.groqAPIKey == "" { return "", errors.New("GROQ_API_KEY not configured") } url := "https://api.groq.com/openai/v1/chat/completions" reqBody := GroqRequest{ Model: s.groqModel, Messages: []GroqMessage{ { Role: "system", Content: systemPrompt, }, { Role: "user", Content: userPrompt, }, }, Temperature: 0.7, MaxTokens: 1024, TopP: 0.95, Stream: false, } jsonData, err := json.Marshal(reqBody) if err != nil { return "", fmt.Errorf("failed to marshal request: %v", err) } req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { return "", fmt.Errorf("failed to create request: %v", err) } req.Header.Set("Authorization", "Bearer "+s.groqAPIKey) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("failed to call Groq API: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response: %v", err) } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("Groq API error (status %d): %s", resp.StatusCode, string(body)) } var groqResp GroqResponse if err := json.Unmarshal(body, &groqResp); err != nil { return "", fmt.Errorf("failed to parse response: %v", err) } if len(groqResp.Choices) == 0 { return "", errors.New("no response from Groq API") } return groqResp.Choices[0].Message.Content, nil } func (s *AIService) GetChatHistory(userID uint, limit int) ([]models.ChatMessageResponse, error) { chats, err := s.chatRepo.GetUserChatHistory(userID, limit) if err != nil { return nil, err } var responses []models.ChatMessageResponse for _, chat := range chats { responses = append(responses, chat.ToResponse()) } return responses, nil } func (s *AIService) ClearChatHistory(userID uint) error { return s.chatRepo.DeleteUserHistory(userID) }