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)