added error for roboflow
This commit is contained in:
137
coverage.out
137
coverage.out
@@ -1,55 +1,18 @@
|
||||
mode: set
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:15.13,26.12 7 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:26.12,29.17 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:29.17,31.4 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:35.2,41.45 5 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:41.45,43.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:45.2,45.35 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:22.31,23.48 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:23.48,25.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:28.28,37.2 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:39.38,41.21 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:41.21,43.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:43.8,44.34 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:44.34,47.4 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:48.3,48.51 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:50.2,50.13 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:53.81,62.2 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:26.26,30.2 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:32.44,62.55 13 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:62.55,63.46 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:63.46,66.74 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:66.74,68.5 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:74.2,74.11 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:78.39,80.2 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:33.53,35.2 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:37.67,58.19 5 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:58.19,60.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:62.5,63.19 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:63.19,65.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:67.5,72.19 5 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:72.19,74.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:75.5,78.72 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:78.72,80.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:82.5,82.35 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:82.35,84.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:86.5,86.52 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:25.57,27.2 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:29.77,32.19 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:32.19,34.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:35.5,43.19 5 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:43.19,45.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:48.5,48.49 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:48.49,50.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:53.5,58.19 4 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:58.19,60.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:62.5,67.19 4 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:67.19,69.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:70.5,74.72 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:74.72,76.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:79.5,80.53 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:80.53,82.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/robowflow.go:84.5,84.24 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:19.25,23.2 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:25.36,27.16 2 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:27.16,29.3 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:30.2,30.11 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:33.91,37.45 3 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:37.45,39.3 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:41.2,41.54 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:41.54,43.3 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:45.2,47.16 3 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:50.100,51.46 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:51.46,52.72 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:52.72,56.24 3 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:56.24,59.5 2 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:61.4,61.24 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger/application.go:25.13,31.62 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger/application.go:31.62,32.31 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger/application.go:32.31,33.27 1 0
|
||||
@@ -68,23 +31,67 @@ gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger/request.go:32.4
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger/request.go:36.4,36.80 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger/request.go:36.80,38.5 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger/request.go:38.10,40.5 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:19.25,23.2 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:25.36,27.16 2 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:27.16,29.3 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:30.2,30.11 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:33.91,37.45 3 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:37.45,39.3 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:41.2,41.54 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:41.54,43.3 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:45.2,47.16 3 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:50.100,51.46 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:51.46,52.72 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:52.72,56.24 3 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:56.24,59.5 2 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/limiter/limiter.go:61.4,61.24 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response/response.go:13.43,16.2 2 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response/response.go:18.38,19.63 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response/response.go:19.63,21.3 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response/response.go:22.2,22.12 1 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response/response.go:25.76,30.2 4 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response/response.go:32.100,35.2 2 1
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:15.13,26.12 7 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:26.12,29.17 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:29.17,31.4 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:35.2,41.45 5 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:41.45,43.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/cmd/junk2jive/main.go:45.2,45.35 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:38.28,42.2 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:45.65,49.61 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:49.61,53.3 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:56.2,56.64 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:56.64,59.3 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:62.2,63.16 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:63.16,66.3 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:68.2,72.16 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:72.16,75.3 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:78.2,79.38 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:83.77,90.16 4 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:90.16,92.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:95.2,100.16 4 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:100.16,102.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:103.2,106.38 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:106.38,109.3 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:112.2,113.69 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:113.69,115.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:117.2,117.23 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:121.62,123.50 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:123.50,125.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow/roboflow.go:126.2,126.16 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:22.31,23.48 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:23.48,25.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:28.28,37.2 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:39.38,41.21 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:41.21,43.3 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:43.8,44.34 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:44.34,47.4 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:48.3,48.51 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:50.2,50.13 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/config/config.go:53.81,62.2 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:33.53,35.2 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:37.67,58.19 5 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:58.19,60.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:62.5,63.19 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:63.19,65.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:67.5,72.19 5 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:72.19,74.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:75.5,78.72 3 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:78.72,80.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:82.5,82.35 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:82.35,84.6 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/services/openai.go:86.5,86.52 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:28.26,32.2 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:34.44,64.55 13 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:64.55,65.46 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:65.46,68.74 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:68.74,70.5 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:72.4,73.58 2 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:76.2,76.11 1 0
|
||||
gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/router/router.go:80.39,82.2 1 0
|
||||
|
||||
@@ -7,14 +7,16 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"encoding/base64"
|
||||
|
||||
"gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type Ollama struct {
|
||||
OllamaKey string
|
||||
Message string
|
||||
Image string
|
||||
OllamaKey string
|
||||
OllamaEndpoint string
|
||||
Image base64.Encoding
|
||||
}
|
||||
|
||||
// ChatMessage represents a message in the chat completion request
|
||||
@@ -36,69 +38,81 @@ type ChatCompletionRequest struct {
|
||||
Files []FileInfo `json:"files,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Load .env file if it exists
|
||||
_ = godotenv.Load()
|
||||
}
|
||||
|
||||
func NewOllama() *Ollama {
|
||||
// Get API endpoint from environment with default fallback
|
||||
endpoint := os.Getenv("OLLAMA_API_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = "http://localhost:3000/api/chat/completions"
|
||||
}
|
||||
|
||||
return &Ollama{
|
||||
OllamaKey: os.Getenv("OLLAMA_API_KEY"),
|
||||
OllamaKey: os.Getenv("OLLAMA_API_KEY"),
|
||||
OllamaEndpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Ollama) SendRequest(userMessage string, fileID string) (string, error) {
|
||||
// Prepare request body
|
||||
reqBody := ChatCompletionRequest{
|
||||
Model: "gemma3:12b",
|
||||
Messages: []ChatMessage{
|
||||
{Role: "user", Content: userMessage},
|
||||
},
|
||||
}
|
||||
// Prepare request body
|
||||
reqBody := ChatCompletionRequest{
|
||||
Model: "gemma3:12b",
|
||||
Messages: []ChatMessage{
|
||||
{Role: "user", Content: userMessage},
|
||||
},
|
||||
}
|
||||
|
||||
// Add file if fileID is provided
|
||||
if fileID != "" {
|
||||
reqBody.Files = []FileInfo{
|
||||
{Type: "file", ID: fileID},
|
||||
}
|
||||
}
|
||||
// Add file if fileID is provided
|
||||
if fileID != "" {
|
||||
reqBody.Files = []FileInfo{
|
||||
{Type: "file", ID: fileID},
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal the request body to JSON
|
||||
jsonBody, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
response.RespondWithError(nil, nil, http.StatusInternalServerError, "Error marshalling request", err)
|
||||
return "", fmt.Errorf("error marshalling request: %w", err)
|
||||
}
|
||||
// Marshal the request body to JSON
|
||||
jsonBody, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
// Remove the call to RespondWithError and just return the error
|
||||
return "", fmt.Errorf("error marshalling request: %w", err)
|
||||
}
|
||||
|
||||
// Create the HTTP request
|
||||
req, err := http.NewRequest("POST", "http://localhost:3000/api/chat/completions", bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
response.RespondWithError(nil, nil, http.StatusInternalServerError, "Error creating request", err)
|
||||
return "", fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
// Create the HTTP request
|
||||
req, err := http.NewRequest("POST", o.OllamaEndpoint, bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
// Remove the call to RespondWithError and just return the error
|
||||
return "", fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
// Add headers
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+o.OllamaKey)
|
||||
// Add headers
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+o.OllamaKey)
|
||||
|
||||
// Send the request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
response.RespondWithError(nil, nil, http.StatusInternalServerError, "Error sending request", err)
|
||||
return "", fmt.Errorf("error sending request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// Send the request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// Remove the call to RespondWithError and just return the error
|
||||
return "", fmt.Errorf("error sending request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read the response
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
response.RespondWithError(nil, nil, http.StatusInternalServerError, "Error reading response", err)
|
||||
return "", fmt.Errorf("error reading response: %w", err)
|
||||
}
|
||||
// Read the response
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
// Remove the call to RespondWithError and just return the error
|
||||
return "", fmt.Errorf("error reading response: %w", err)
|
||||
}
|
||||
|
||||
// Check for non-200 status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
response.RespondWithError(nil, nil, http.StatusInternalServerError, "API request failed", err)
|
||||
return "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
// Check for non-200 status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// Remove the call to RespondWithError and just return the error
|
||||
return "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func OllamaRequest(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -110,7 +124,7 @@ func OllamaRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&requestData)
|
||||
if err != nil {
|
||||
response.RespondWithError(w, r, http.StatusBadRequest, "Invalid request body", err)
|
||||
response.RespondWithError(w, r, http.StatusBadRequest, "Invalid request body", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -118,13 +132,12 @@ func OllamaRequest(w http.ResponseWriter, r *http.Request) {
|
||||
ollama := NewOllama()
|
||||
|
||||
// Send request to Ollama API
|
||||
apiResponse, err := ollama.SendRequest(requestData.Message, requestData.FileID)
|
||||
if err != nil {
|
||||
response.RespondWithError(w, r, http.StatusInternalServerError, "Error sending request to Ollama API", err)
|
||||
http.Error(w, fmt.Sprintf("Error from Ollama API: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
apiResponse, err := ollama.SendRequest(requestData.Message, requestData.FileID)
|
||||
if err != nil {
|
||||
response.RespondWithError(w, r, http.StatusInternalServerError, "Error sending request to Ollama API", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Return response
|
||||
response.RespondWithJSON(w, http.StatusOK, map[string]string{"response": apiResponse})
|
||||
// Return response
|
||||
response.RespondWithJSON(w, http.StatusOK, map[string]string{"response": apiResponse})
|
||||
}
|
||||
315
internal/ollama/ollama_test.go
Normal file
315
internal/ollama/ollama_test.go
Normal file
@@ -0,0 +1,315 @@
|
||||
package ollama
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response"
|
||||
)
|
||||
|
||||
// Mock HTTP client for testing
|
||||
type mockHTTPClient struct {
|
||||
DoFunc func(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
return m.DoFunc(req)
|
||||
}
|
||||
|
||||
// Helper function to patch the HTTP client for testing
|
||||
func patchHTTPClient(client interface{}) func() {
|
||||
originalClient := http.DefaultClient
|
||||
http.DefaultClient = client.(*http.Client)
|
||||
return func() {
|
||||
http.DefaultClient = originalClient
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewOllama tests the NewOllama function
|
||||
func TestNewOllama(t *testing.T) {
|
||||
// Save original env and restore after test
|
||||
originalKey := os.Getenv("OLLAMA_API_KEY")
|
||||
defer os.Setenv("OLLAMA_API_KEY", originalKey)
|
||||
|
||||
testKey := "test-api-key"
|
||||
os.Setenv("OLLAMA_API_KEY", testKey)
|
||||
|
||||
ollama := NewOllama()
|
||||
if ollama == nil {
|
||||
t.Fatal("Expected NewOllama to return a non-nil value")
|
||||
}
|
||||
|
||||
if ollama.OllamaKey != testKey {
|
||||
t.Errorf("Expected OllamaKey to be %q, got %q", testKey, ollama.OllamaKey)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendRequest tests the SendRequest method
|
||||
func TestSendRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userMessage string
|
||||
fileID string
|
||||
setupMock func() *mockHTTPClient
|
||||
expectedResult string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Successful request without file",
|
||||
userMessage: "Hello, world!",
|
||||
fileID: "",
|
||||
setupMock: func() *mockHTTPClient {
|
||||
return &mockHTTPClient{
|
||||
DoFunc: func(req *http.Request) (*http.Response, error) {
|
||||
// Verify request has correct content and headers
|
||||
if req.Header.Get("Content-Type") != "application/json" {
|
||||
t.Errorf("Expected Content-Type header to be application/json")
|
||||
}
|
||||
if req.Header.Get("Authorization") != "Bearer test-key" {
|
||||
t.Errorf("Expected Authorization header to be Bearer test-key")
|
||||
}
|
||||
|
||||
// Check request body
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
var reqBody ChatCompletionRequest
|
||||
if err := json.Unmarshal(body, &reqBody); err != nil {
|
||||
t.Errorf("Failed to unmarshal request body: %v", err)
|
||||
}
|
||||
if reqBody.Model != "gemma3:12b" {
|
||||
t.Errorf("Expected model to be gemma3:12b, got %s", reqBody.Model)
|
||||
}
|
||||
if len(reqBody.Messages) != 1 || reqBody.Messages[0].Content != "Hello, world!" {
|
||||
t.Errorf("Expected message content to be 'Hello, world!'")
|
||||
}
|
||||
if len(reqBody.Files) != 0 {
|
||||
t.Errorf("Expected no files, got %d", len(reqBody.Files))
|
||||
}
|
||||
|
||||
// Return successful response
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{"response": "Hello from Ollama!"}`)),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
},
|
||||
expectedResult: `{"response": "Hello from Ollama!"}`,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Successful request with file",
|
||||
userMessage: "Analyze this file",
|
||||
fileID: "file123",
|
||||
setupMock: func() *mockHTTPClient {
|
||||
return &mockHTTPClient{
|
||||
DoFunc: func(req *http.Request) (*http.Response, error) {
|
||||
// Check request body to ensure file was included
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
var reqBody ChatCompletionRequest
|
||||
json.Unmarshal(body, &reqBody)
|
||||
|
||||
if len(reqBody.Files) != 1 || reqBody.Files[0].ID != "file123" {
|
||||
t.Errorf("Expected file ID to be file123")
|
||||
}
|
||||
|
||||
// Return successful response
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{"response": "File analysis complete"}`)),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
},
|
||||
expectedResult: `{"response": "File analysis complete"}`,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "HTTP error response",
|
||||
userMessage: "Hello, world!",
|
||||
fileID: "",
|
||||
setupMock: func() *mockHTTPClient {
|
||||
return &mockHTTPClient{
|
||||
DoFunc: func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusBadRequest,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{"error": "Bad request"}`)),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
},
|
||||
expectedResult: "",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create mock client
|
||||
mockClient := tc.setupMock()
|
||||
|
||||
// Create a custom HTTP client and replace the default
|
||||
client := &http.Client{
|
||||
Transport: &mockTransport{mockClient: mockClient},
|
||||
}
|
||||
|
||||
// Initialize ollama with test key
|
||||
ollama := &Ollama{OllamaKey: "test-key"}
|
||||
|
||||
// Mock response.RespondWithError to prevent nil pointer dereference
|
||||
originalRespondWithError := response.RespondWithError
|
||||
response.RespondWithError = func(w http.ResponseWriter, r *http.Request, status int, message string, err error) {}
|
||||
defer func() { response.RespondWithError = originalRespondWithError }()
|
||||
|
||||
// Call the function
|
||||
result, err := ollama.SendRequest(tc.userMessage, tc.fileID)
|
||||
|
||||
// Check results
|
||||
if tc.expectError && err == nil {
|
||||
t.Error("Expected an error but got none")
|
||||
}
|
||||
|
||||
if !tc.expectError && err != nil {
|
||||
t.Errorf("Did not expect an error but got: %v", err)
|
||||
}
|
||||
|
||||
if !tc.expectError && result != tc.expectedResult {
|
||||
t.Errorf("Expected result %q, got %q", tc.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Custom transport that uses our mockClient
|
||||
type mockTransport struct {
|
||||
mockClient *mockHTTPClient
|
||||
}
|
||||
|
||||
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return m.mockClient.Do(req)
|
||||
}
|
||||
|
||||
// TestOllamaRequest tests the OllamaRequest HTTP handler
|
||||
func TestOllamaRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
requestBody string
|
||||
setupMock func() (*Ollama, func())
|
||||
expectedStatusCode int
|
||||
expectedResponse string
|
||||
}{
|
||||
{
|
||||
name: "Valid request",
|
||||
requestBody: `{"message": "Hello", "fileId": ""}`,
|
||||
setupMock: func() (*Ollama, func()) {
|
||||
// Create a mock Ollama that returns a successful response
|
||||
mockOllama := &Ollama{}
|
||||
|
||||
// Save the original NewOllama function and replace it
|
||||
originalNewOllama := NewOllama
|
||||
NewOllama = func() *Ollama {
|
||||
return mockOllama
|
||||
}
|
||||
|
||||
// Save the original SendRequest method and replace it
|
||||
originalSendRequest := mockOllama.SendRequest
|
||||
mockOllama.SendRequest = func(userMessage, fileID string) (string, error) {
|
||||
return `{"content": "Hello from Ollama!"}`, nil
|
||||
}
|
||||
|
||||
// Return the mock and a cleanup function
|
||||
return mockOllama, func() {
|
||||
NewOllama = originalNewOllama
|
||||
mockOllama.SendRequest = originalSendRequest
|
||||
}
|
||||
},
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedResponse: `{"response":{"content": "Hello from Ollama!"}}`,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON request",
|
||||
requestBody: `{"message": "Hello"`, // Missing closing brace
|
||||
setupMock: func() (*Ollama, func()) {
|
||||
mockOllama := &Ollama{}
|
||||
originalNewOllama := NewOllama
|
||||
NewOllama = func() *Ollama {
|
||||
return mockOllama
|
||||
}
|
||||
return mockOllama, func() {
|
||||
NewOllama = originalNewOllama
|
||||
}
|
||||
},
|
||||
expectedStatusCode: http.StatusBadRequest,
|
||||
expectedResponse: "", // We don't care about the exact error message
|
||||
},
|
||||
{
|
||||
name: "API error",
|
||||
requestBody: `{"message": "Error", "fileId": ""}`,
|
||||
setupMock: func() (*Ollama, func()) {
|
||||
mockOllama := &Ollama{}
|
||||
originalNewOllama := NewOllama
|
||||
NewOllama = func() *Ollama {
|
||||
return mockOllama
|
||||
}
|
||||
originalSendRequest := mockOllama.SendRequest
|
||||
mockOllama.SendRequest = func(userMessage, fileID string) (string, error) {
|
||||
return "", &httpError{message: "API error", statusCode: http.StatusInternalServerError}
|
||||
}
|
||||
return mockOllama, func() {
|
||||
NewOllama = originalNewOllama
|
||||
mockOllama.SendRequest = originalSendRequest
|
||||
}
|
||||
},
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedResponse: "", // We don't care about the exact error message
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Setup mock
|
||||
_, cleanup := tc.setupMock()
|
||||
defer cleanup()
|
||||
|
||||
// Create request and response recorder
|
||||
req := httptest.NewRequest("POST", "/api/ollama", strings.NewReader(tc.requestBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Call the handler
|
||||
OllamaRequest(w, req)
|
||||
|
||||
// Check status code
|
||||
if w.Code != tc.expectedStatusCode {
|
||||
t.Errorf("Expected status code %d, got %d", tc.expectedStatusCode, w.Code)
|
||||
}
|
||||
|
||||
// For successful responses, check the response body
|
||||
if tc.expectedStatusCode == http.StatusOK {
|
||||
// Remove whitespace for comparison
|
||||
expectedNoSpace := strings.ReplaceAll(tc.expectedResponse, " ", "")
|
||||
actualNoSpace := strings.ReplaceAll(w.Body.String(), " ", "")
|
||||
actualNoSpace = strings.ReplaceAll(actualNoSpace, "\n", "")
|
||||
|
||||
if expectedNoSpace != actualNoSpace {
|
||||
t.Errorf("Expected response %q, got %q", tc.expectedResponse, w.Body.String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper error type for testing
|
||||
type httpError struct {
|
||||
message string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (e *httpError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
127
internal/roboflow/roboflow.go
Normal file
127
internal/roboflow/roboflow.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package roboflow
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response"
|
||||
)
|
||||
|
||||
// ImageRequest represents the incoming JSON request with image data
|
||||
type ImageRequest struct {
|
||||
Image string `json:"image"` // Base64 encoded image
|
||||
}
|
||||
|
||||
// RoboflowResponse represents the structured response from Roboflow API
|
||||
type RoboflowResponse struct {
|
||||
Predictions []struct {
|
||||
Class string `json:"class"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
Width float64 `json:"width"`
|
||||
Height float64 `json:"height"`
|
||||
} `json:"predictions"`
|
||||
}
|
||||
|
||||
// Service represents a Roboflow service
|
||||
type Service struct {
|
||||
apiKey string
|
||||
}
|
||||
|
||||
// NewService creates a new Roboflow service instance
|
||||
func NewService() *Service {
|
||||
return &Service{
|
||||
apiKey: os.Getenv("ROBOFLOW_API_KEY"),
|
||||
}
|
||||
}
|
||||
|
||||
// HandleImageRequest processes the JSON image request
|
||||
func HandleImageRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Decode the JSON request
|
||||
var req ImageRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
response.RespondWithError(w, r, http.StatusBadRequest, "Invalid JSON request: %v", err)
|
||||
http.Error(w, "Invalid JSON request: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if image was provided
|
||||
if err := json.Unmarshal([]byte(req.Image), &req); err != nil {
|
||||
response.RespondWithError(w, r, http.StatusBadRequest, "No image provided in request", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode base64 image
|
||||
imageData, err := base64.StdEncoding.DecodeString(req.Image)
|
||||
if err != nil {
|
||||
response.RespondWithError(w, r, http.StatusBadRequest, "Invalid base64 image: %v ", err)
|
||||
return
|
||||
}
|
||||
|
||||
service := NewService()
|
||||
|
||||
// Process the image with Roboflow
|
||||
responser, err := service.AnalyzeImage(imageData)
|
||||
if err != nil {
|
||||
response.RespondWithError(w, r, http.StatusInternalServerError, "Error processing image: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Return JSON response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(responser)
|
||||
}
|
||||
|
||||
// AnalyzeImage sends image bytes to Roboflow API
|
||||
func (s *Service) AnalyzeImage(imageData []byte) (*RoboflowResponse, error) {
|
||||
// Base64 encode the image data
|
||||
base64Data := base64.StdEncoding.EncodeToString(imageData)
|
||||
|
||||
// Create the request
|
||||
url := fmt.Sprintf("https://serverless.roboflow.com/taco-puuof/1?api_key=%s", s.apiKey)
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBufferString(base64Data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Set appropriate headers
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// Send the request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check response status
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
var response RoboflowResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// GetDetectedObjects extracts the class names from the response
|
||||
func GetDetectedObjects(response *RoboflowResponse) []string {
|
||||
var objects []string
|
||||
for _, prediction := range response.Predictions {
|
||||
objects = append(objects, prediction.Class)
|
||||
}
|
||||
return objects
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/logger"
|
||||
"gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/ollama"
|
||||
"gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/response"
|
||||
"gitea.miguelmuniz.com/rogueking/junk2jive-server/internal/roboflow"
|
||||
)
|
||||
|
||||
// Router encapsulates the Chi router and its dependencies
|
||||
@@ -69,7 +70,7 @@ func SetupRouter(origins []string) *Router {
|
||||
})
|
||||
|
||||
subRouter.Post("/text", ollama.OllamaRequest)
|
||||
// subRouter.Post("/visual", handlers.VisualAIHandler())
|
||||
subRouter.Post("/visual", roboflow.HandleImageRequest)
|
||||
})
|
||||
})
|
||||
return &r
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type RoboflowService struct {
|
||||
apiKey string
|
||||
}
|
||||
|
||||
type RoboflowResponse struct {
|
||||
Predictions []struct {
|
||||
Class string `json:"class"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
} `json:"predictions"`
|
||||
}
|
||||
|
||||
func NewRoboflowService(apiKey string) *RoboflowService {
|
||||
return &RoboflowService{apiKey: apiKey}
|
||||
}
|
||||
|
||||
func (s *RoboflowService) DetectObjects(imagePath string) ([]string, error) {
|
||||
// Open the file
|
||||
file, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Create a new multipart writer
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
// Create a form file field
|
||||
part, err := writer.CreateFormFile("file", filepath.Base(imagePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Copy the file content to the form field
|
||||
if _, err = io.Copy(part, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Close the writer to finalize the form
|
||||
writer.Close()
|
||||
|
||||
// Create the request
|
||||
url := fmt.Sprintf("https://detect.roboflow.com/taco-puuof/1?api_key=%s&confidence=60&overlap=30", s.apiKey)
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
// Send the request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Parse the response
|
||||
var response RoboflowResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract object classes
|
||||
var objects []string
|
||||
for _, prediction := range response.Predictions {
|
||||
objects = append(objects, prediction.Class)
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
BIN
j2znlrcs.png
Normal file
BIN
j2znlrcs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Reference in New Issue
Block a user