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
133 lines
3.4 KiB
Go
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
|
|
}
|