diff --git a/internal/llm/llm.go b/internal/llm/llm.go
index 7aa7bc7..031f88a 100644
--- a/internal/llm/llm.go
+++ b/internal/llm/llm.go
@@ -3,11 +3,17 @@ package llm
import (
"context"
"errors"
+ "log"
+ "strings"
+ "time"
"google.golang.org/genai"
)
-const GeminiModel = "gemini-1.5-flash"
+const (
+ GeminiModel = "gemini-1.5-flash"
+ maxRetries = 3
+)
type modelClient interface {
GenerateContent(ctx context.Context, model string, parts []*genai.Content, config *genai.GenerateContentConfig) (*genai.GenerateContentResponse, error)
@@ -33,15 +39,41 @@ func NewGeminiService(apiKey string) (*GeminiService, error) {
return &GeminiService{client: client.Models, modelName: GeminiModel}, nil
}
+func (s *GeminiService) generateContentWithRetry(ctx context.Context, prompt string, schema *genai.Schema) (*genai.GenerateContentResponse, error) {
+ var resp *genai.GenerateContentResponse
+ var err error
+
+ for i := 0; i < maxRetries; i++ {
+ resp, err = s.client.GenerateContent(ctx, s.modelName, genai.Text(prompt), &genai.GenerateContentConfig{
+ ResponseMIMEType: "application/json",
+ ResponseSchema: schema,
+ })
+ if err == nil {
+ return resp, nil
+ }
+
+ var apiError *genai.APIError
+ if errors.As(err, &apiError) {
+ log.Printf("LLM API error: Code=%d, Status=%s, Message=%s", apiError.Code, apiError.Status, apiError.Message)
+ if apiError.Code == 503 && strings.Contains(apiError.Message, "overloaded") {
+ log.Printf("Retrying LLM call (%d/%d)...", i+1, maxRetries-1)
+ time.Sleep(time.Second * time.Duration(i+1)) // Wait 1, 2 seconds
+ continue
+ }
+ }
+
+ return nil, err
+ }
+
+ return nil, err
+}
+
func (s *GeminiService) GenerateContent(ctx context.Context, prompt string, schema *genai.Schema) (string, error) {
if s.client == nil {
return "", errors.New("LLM service is disabled: no API key provided")
}
- resp, err := s.client.GenerateContent(ctx, s.modelName, genai.Text(prompt), &genai.GenerateContentConfig{
- ResponseMIMEType: "application/json",
- ResponseSchema: schema,
- })
+ resp, err := s.generateContentWithRetry(ctx, prompt, schema)
if err != nil {
return "", err
}
diff --git a/internal/templates/entries.gohtml b/internal/templates/entries.gohtml
index 62e5395..266ff33 100644
--- a/internal/templates/entries.gohtml
+++ b/internal/templates/entries.gohtml
@@ -29,7 +29,7 @@
Summary
- {{.Summary}}
+ {{textToHTML .Summary}}
diff --git a/internal/templates/funcs.go b/internal/templates/funcs.go
index 476fa71..f461bd5 100644
--- a/internal/templates/funcs.go
+++ b/internal/templates/funcs.go
@@ -13,7 +13,9 @@ var staticFS embed.FS
func FuncMap() template.FuncMap {
return template.FuncMap{
- "include": include,
+ "include": include,
+ "textToHTML": textToHTML,
+ "htmlEscape": htmlEscape,
}
}
@@ -32,3 +34,11 @@ func include(filename string) (any, error) {
return template.HTML(b), nil
}
}
+
+func textToHTML(s string) template.HTML {
+ return template.HTML(strings.ReplaceAll(template.HTMLEscapeString(s), "\n", "
"))
+}
+
+func htmlEscape(s string) template.HTML {
+ return template.HTML(s)
+}
\ No newline at end of file
diff --git a/internal/templates/templates.go b/internal/templates/templates.go
index 4caeddf..5ca0762 100644
--- a/internal/templates/templates.go
+++ b/internal/templates/templates.go
@@ -27,11 +27,7 @@ func init() {
archiveTemplateName := "entries.gohtml"
emailTemplateName := "email.gotxt"
- ArchiveTemplate, err = htmlTemplate.New(archiveTemplateName).Funcs(FuncMap()).Funcs(htmlTemplate.FuncMap{
- "htmlEscape": func(s string) htmlTemplate.HTML {
- return htmlTemplate.HTML(s)
- },
- }).ParseFS(embedFS, archiveTemplateName)
+ ArchiveTemplate, err = htmlTemplate.New(archiveTemplateName).Funcs(FuncMap()).ParseFS(embedFS, archiveTemplateName)
if err != nil {
log.Fatalf("Error parsing archive template: %v", err)