package ai import ( "context" "fmt" "gitea.oblak.solutions/dimitar/gitFlow/internal/config" "gitea.oblak.solutions/dimitar/gitFlow/internal/httpclient" "gitea.oblak.solutions/dimitar/gitFlow/pkg/status" ) const defaultOllamaBaseURL = "http://localhost:11434" // OllamaProvider talks to a local Ollama server. No API key is needed. type OllamaProvider struct { model string baseURL string http *httpclient.Client } // NewOllama builds an Ollama provider. base_url and model fall back to // sensible defaults when unset in the configuration. func NewOllama(cfg config.AIConfig) *OllamaProvider { baseURL := cfg.BaseURL if baseURL == "" { baseURL = defaultOllamaBaseURL } model := cfg.Model if model == "" { model = "llama3.2" } return &OllamaProvider{model: model, baseURL: baseURL, http: httpclient.New()} } // Name implements Provider. func (p *OllamaProvider) Name() string { return "ollama" } type ollamaRequest struct { Model string `json:"model"` Messages []chatMessage `json:"messages"` Format string `json:"format"` // "json" forces structured output Stream bool `json:"stream"` } type ollamaResponse struct { Message chatMessage `json:"message"` Error string `json:"error"` } // Suggest implements Provider. func (p *OllamaProvider) Suggest(ctx context.Context, result status.ScanResult) ([]Suggestion, error) { req := ollamaRequest{ Model: p.model, Messages: []chatMessage{ {Role: "system", Content: "You are gitflow, a concise Git repository health assistant. You reply with JSON only."}, {Role: "user", Content: BuildPrompt(result)}, }, Format: "json", Stream: false, } var resp ollamaResponse if err := p.http.PostJSON(ctx, p.baseURL+"/api/chat", nil, req, &resp); err != nil { return nil, err } if resp.Error != "" { return nil, fmt.Errorf("ai: ollama: %s", resp.Error) } return parseSuggestions(resp.Message.Content) }