diff --git a/resources/views/ai_config.html b/resources/views/ai_config.html
index b558ec0..cfd9017 100644
--- a/resources/views/ai_config.html
+++ b/resources/views/ai_config.html
@@ -89,7 +89,8 @@
'bg-green-100 text-green-700': item.provider === 'openai',
'bg-orange-100 text-orange-700': item.provider === 'anthropic',
'bg-blue-100 text-blue-700': item.provider === 'ollama',
- 'bg-gray-100 text-gray-700': !['qwen','openai','anthropic','ollama'].includes(item.provider)
+ 'bg-yellow-100 text-yellow-700': item.provider === 'gemini',
+ 'bg-gray-100 text-gray-700': !['qwen','openai','anthropic','ollama','gemini'].includes(item.provider)
}" x-text="item.provider">
|
@@ -143,17 +144,18 @@
-
@@ -170,15 +172,19 @@
Interna (red Coolify): http://10.0.1.15:11434/v1 — Pública: https://ollama.u-s.app/v1
+
+ Usa https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent?alt=sse. Solo necesitas el API Key.
+
diff --git a/rest/controllers/api/ia_controller.go b/rest/controllers/api/ia_controller.go
index b5f1c55..1090844 100644
--- a/rest/controllers/api/ia_controller.go
+++ b/rest/controllers/api/ia_controller.go
@@ -49,6 +49,27 @@ type openaiChatResp struct {
Choices []openaiChatChoice `json:"choices"`
}
+type geminiPart struct {
+ Text string `json:"text"`
+}
+
+type geminiContent struct {
+ Parts []geminiPart `json:"parts"`
+}
+
+type geminiReq struct {
+ Contents []geminiContent `json:"contents"`
+}
+
+type geminiCandidate struct {
+ Content geminiContent `json:"content"`
+ FinishReason *string `json:"finishReason"`
+}
+
+type geminiResp struct {
+ Candidates []geminiCandidate `json:"candidates"`
+}
+
func GeneraTextoStream(c *fiber.Ctx) error {
var data DataIa
if err := c.BodyParser(&data); err != nil {
@@ -77,9 +98,19 @@ func GeneraTextoStream(c *fiber.Ctx) error {
var endpoint string
var bodyReader io.Reader
useOpenAI := provider != "ollama" || hasV1
+ useGemini := provider == "gemini"
- // OpenAI-compatible (openai, anthropic, qwen, o llameo con /v1)
- if !useOpenAI {
+ if useGemini {
+ model := data.Model
+ endpoint = fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:streamGenerateContent?alt=sse&key=%s", model, config.ApiKey)
+ gReq := geminiReq{
+ Contents: []geminiContent{
+ {Parts: []geminiPart{{Text: data.Prompt}}},
+ },
+ }
+ jsonData, _ := json.Marshal(gReq)
+ bodyReader = bytes.NewBuffer(jsonData)
+ } else if !useOpenAI {
// Native Ollama API
endpoint = baseURL + "/api/generate"
jsonData, _ := json.Marshal(data)
@@ -108,7 +139,9 @@ func GeneraTextoStream(c *fiber.Ctx) error {
req.Header.Set("Content-Type", "application/json")
// Auth
- if provider == "ollama" && config.ApiKey != "" && config.ApiKey != "ollama" {
+ if useGemini {
+ // API key ya va en la URL
+ } else if provider == "ollama" && config.ApiKey != "" && config.ApiKey != "ollama" {
req.SetBasicAuth("ollama", config.ApiKey)
} else if config.ApiKey != "" {
req.Header.Set("Authorization", "Bearer "+config.ApiKey)
@@ -138,15 +171,38 @@ func GeneraTextoStream(c *fiber.Ctx) error {
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
defer resp.Body.Close()
- dec := json.NewDecoder(resp.Body)
- for {
- if useOpenAI {
+ scanner := bufio.NewScanner(resp.Body)
+ scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if line == "" {
+ continue
+ }
+ if useGemini {
+ if !strings.HasPrefix(line, "data: ") {
+ continue
+ }
+ var msg geminiResp
+ if err := json.Unmarshal([]byte(line[6:]), &msg); err != nil {
+ continue
+ }
+ if len(msg.Candidates) > 0 && len(msg.Candidates[0].Content.Parts) > 0 {
+ text := msg.Candidates[0].Content.Parts[0].Text
+ if text != "" {
+ fmt.Fprint(w, text)
+ w.Flush()
+ }
+ if msg.Candidates[0].FinishReason != nil {
+ break
+ }
+ }
+ } else if useOpenAI {
var msg openaiChatResp
- if err := dec.Decode(&msg); err != nil {
+ if err := json.Unmarshal([]byte(line), &msg); err != nil {
if err != io.EOF {
log.Printf("[IA] Error decodificando respuesta OpenAI: %v", err)
}
- break
+ continue
}
if len(msg.Choices) == 0 {
continue
@@ -161,11 +217,8 @@ func GeneraTextoStream(c *fiber.Ctx) error {
}
} else {
var msg respuestaOllama
- if err := dec.Decode(&msg); err != nil {
- if err != io.EOF {
- log.Printf("[IA] Error decodificando respuesta Ollama: %v", err)
- }
- break
+ if err := json.Unmarshal([]byte(line), &msg); err != nil {
+ continue
}
if msg.Error != "" {
log.Printf("[IA] Ollama error: %s", msg.Error)