modified: db/db.go modified: handlers/dashboard.go modified: main.go modified: services/openrouter.go modified: templates/dashboard.html
120 lines
3.3 KiB
Go
120 lines
3.3 KiB
Go
package services
|
|
|
|
import (
|
|
bytes "bytes"
|
|
json "encoding/json"
|
|
io "io"
|
|
http "net/http"
|
|
os "os"
|
|
"whatsapp-bot/db" // Ensure this matches your module name
|
|
)
|
|
|
|
type Message struct {
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
type OpenRouterResponse struct {
|
|
Choices []struct {
|
|
Message struct {
|
|
Content string `json:"content"`
|
|
ToolCalls []struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Function struct {
|
|
Name string `json:"name"`
|
|
Arguments string `json:"arguments"`
|
|
} `json:"function"`
|
|
} `json:"tool_calls"`
|
|
} `json:"message"`
|
|
} `json:"choices"`
|
|
}
|
|
|
|
func GetAIResponse(chatHistory []Message) (string, error) {
|
|
apiKey := os.Getenv("OPENROUTER_API_KEY")
|
|
url := "https://openrouter.ai/api/v1/chat/completions"
|
|
|
|
// 1. Build the full message list with a System Prompt
|
|
fullMessages := append([]Message{
|
|
{
|
|
Role: "system",
|
|
Content: "You are a helpful Chilean business assistant. You can book appointments. If a user wants to schedule something, use the create_appointment tool. Always be concise and polite.",
|
|
},
|
|
}, chatHistory...)
|
|
|
|
// 2. Define the tools the AI can use
|
|
payload := map[string]interface{}{
|
|
"model": "stepfun/step-3.5-flash:free",
|
|
"messages": fullMessages,
|
|
"tools": []map[string]interface{}{
|
|
{
|
|
"type": "function",
|
|
"function": map[string]interface{}{
|
|
"name": "create_appointment",
|
|
"description": "Schedules a new appointment in the database",
|
|
"parameters": map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"customer_phone": map[string]string{"type": "string", "description": "The user's phone number"},
|
|
"date": map[string]string{"type": "string", "description": "The date and time in YYYY-MM-DD HH:MM format"},
|
|
},
|
|
"required": []string{"customer_phone", "date"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
jsonData, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
|
req.Header.Set("Authorization", "Bearer "+apiKey)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "Error from OpenRouter: " + string(bodyBytes), nil
|
|
}
|
|
|
|
var result OpenRouterResponse
|
|
if err := json.Unmarshal(bodyBytes, &result); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(result.Choices) > 0 {
|
|
aiMsg := result.Choices[0].Message
|
|
|
|
// 3. Handle Tool Calls (The "Logic" part)
|
|
if len(aiMsg.ToolCalls) > 0 {
|
|
tc := aiMsg.ToolCalls[0].Function
|
|
if tc.Name == "create_appointment" {
|
|
var args struct {
|
|
Phone string `json:"customer_phone"`
|
|
Date string `json:"date"`
|
|
}
|
|
// Unmarshal the AI-generated arguments
|
|
json.Unmarshal([]byte(tc.Arguments), &args)
|
|
|
|
// Save to DB using your helper
|
|
err := db.SaveAppointment(args.Phone, args.Date)
|
|
if err != nil {
|
|
return "I tried to book it, but the database hates me: " + err.Error(), nil
|
|
}
|
|
return "✅ [SYSTEM] Appointment automatically booked for " + args.Phone + " at " + args.Date, nil
|
|
}
|
|
}
|
|
|
|
// 4. Return plain text if no tool was called
|
|
if aiMsg.Content != "" {
|
|
return aiMsg.Content, nil
|
|
}
|
|
}
|
|
|
|
return "The AI is giving me the silent treatment, seki.", nil
|
|
}
|