Files
whatsapp-bot/services/openrouter.go
SekiDesu0 e256fcb073 modified: .env
new file:   __debug_bin.exe
	modified:   bot.db
	modified:   db/db.go
	modified:   go.mod
	new file:   handlers/auth.go
	modified:   handlers/dashboard.go
	new file:   handlers/saas.go
	modified:   handlers/webhook.go
	modified:   main.go
	new file:   saas_bot.db
	modified:   services/openrouter.go
	new file:   services/types.go
	modified:   services/whatsapp.go
	new file:   static/style.css
	modified:   templates/dashboard.html
	new file:   templates/landing.html
	new file:   templates/login.html
	new file:   templates/register.html
	deleted:    types/types.go
2026-03-02 00:38:05 -03:00

133 lines
3.4 KiB
Go

package services
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"whatsapp-bot/db"
)
// NOTA: struct Message ya NO está aquí, está en types.go
// StreamAIResponse handles the streaming connection
func StreamAIResponse(userID int, userMessage string, systemPrompt string, onToken func(string)) (string, error) {
apiKey := os.Getenv("OPENROUTER_API_KEY")
url := "https://openrouter.ai/api/v1/chat/completions"
messages := []map[string]string{
{"role": "system", "content": systemPrompt},
{"role": "user", "content": userMessage},
}
payload := map[string]interface{}{
"model": "arcee-ai/trinity-large-preview:free", //stepfun/step-3.5-flash:free
"messages": messages,
"stream": true,
"tools": []map[string]interface{}{
{
"type": "function",
"function": map[string]interface{}{
"name": "create_appointment",
"description": "Schedules a new appointment. ONLY use when you have date, time, and phone.",
"parameters": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"customer_phone": map[string]string{"type": "string"},
"date": map[string]string{"type": "string"},
},
"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")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
var fullContentBuffer strings.Builder
var toolCallBuffer strings.Builder
var toolName string
isToolCall := false
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "data: ") {
continue
}
jsonStr := strings.TrimPrefix(line, "data: ")
if jsonStr == "[DONE]" {
break
}
var chunk struct {
Choices []struct {
Delta struct {
Content string `json:"content"`
ToolCalls []struct {
Function struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
} `json:"function"`
} `json:"tool_calls"`
} `json:"delta"`
} `json:"choices"`
}
json.Unmarshal([]byte(jsonStr), &chunk)
if len(chunk.Choices) > 0 {
delta := chunk.Choices[0].Delta
if delta.Content != "" {
fullContentBuffer.WriteString(delta.Content)
if onToken != nil {
onToken(delta.Content)
}
}
if len(delta.ToolCalls) > 0 {
isToolCall = true
if delta.ToolCalls[0].Function.Name != "" {
toolName = delta.ToolCalls[0].Function.Name
}
toolCallBuffer.WriteString(delta.ToolCalls[0].Function.Arguments)
}
}
}
// EXECUTE TOOL
if isToolCall && toolName == "create_appointment" {
var args struct {
Phone string `json:"customer_phone"`
Date string `json:"date"`
}
if err := json.Unmarshal([]byte(toolCallBuffer.String()), &args); err == nil {
err := db.SaveAppointment(userID, args.Phone, args.Date)
resultMsg := ""
if err != nil {
resultMsg = "\n(System: Failed to book appointment)"
} else {
resultMsg = fmt.Sprintf("\n✅ Appointment Confirmed: %s", args.Date)
}
fullContentBuffer.WriteString(resultMsg)
if onToken != nil {
onToken(resultMsg)
}
}
}
return fullContentBuffer.String(), nil
}