Add GIPHY image search feature

This commit is contained in:
GHStaK
2026-03-24 16:02:49 +09:00
parent 494a54fa46
commit d63c467ef9
13 changed files with 1385 additions and 84 deletions
+76 -2
View File
@@ -20,6 +20,7 @@ import (
type GeminiService struct {
APIKey string
Model string
Client *http.Client
GenerateEndpoint string
TranslateEndpoint string
@@ -69,11 +70,15 @@ type QueryExpansion struct {
Querywords []string `json:"querywords"`
}
func NewGeminiService(apiKey string) *GeminiService {
func NewGeminiService(apiKey, model string) *GeminiService {
if strings.TrimSpace(model) == "" {
model = "gemini-2.5-flash"
}
return &GeminiService{
APIKey: apiKey,
Model: model,
Client: &http.Client{Timeout: 40 * time.Second},
GenerateEndpoint: "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
GenerateEndpoint: "https://generativelanguage.googleapis.com/v1beta/models/" + model + ":generateContent",
TranslateEndpoint: "https://translate.googleapis.com/translate_a/single",
visualCache: map[string]cachedVisualData{},
translationCache: map[string]cachedStringValue{},
@@ -99,6 +104,75 @@ func (g *GeminiService) ExpandQuery(query string) ([]string, error) {
return expanded, nil
}
func (g *GeminiService) ExpandImageQueries(query string) ([]string, error) {
trimmed := strings.TrimSpace(query)
if trimmed == "" {
return nil, fmt.Errorf("query is empty")
}
cacheKey := "image-expansion\n" + trimmed
if cached, ok := g.getCachedExpansion(cacheKey); ok {
g.debug("gemini:image_expand_cache_hit", map[string]any{"query": trimmed, "expanded": cached})
return cached, nil
}
fallback := buildFallbackImageQueries(trimmed, g.TranslateQuery(trimmed))
if g.APIKey == "" {
g.setCachedExpansion(cacheKey, fallback, 15*time.Minute)
return fallback, fmt.Errorf("gemini api key is not configured")
}
body := map[string]any{
"systemInstruction": map[string]any{
"parts": []map[string]string{{
"text": "Return exactly 5 concise English search queries for GIPHY image or GIF search. Respond with JSON only in this shape: {\"queries\":[\"...\",\"...\",\"...\",\"...\",\"...\"]}. Keep the queries meaning-preserving, practical, deduplicated, and concise.",
}},
},
"contents": []map[string]any{{
"parts": []map[string]string{{
"text": "User query: " + trimmed + "\nGenerate exactly 5 English search queries for GIPHY image or GIF search. Include a direct translation, a common phrasing, and only relevant related variants.",
}},
}},
"generationConfig": map[string]any{
"responseMimeType": "application/json",
"temperature": 0.2,
"maxOutputTokens": 160,
},
}
rawText, err := g.generateText(body)
if err != nil {
g.debug("gemini:image_expand_error", map[string]any{"query": trimmed, "error": err.Error()})
g.setCachedExpansion(cacheKey, fallback, 15*time.Minute)
return fallback, err
}
jsonText, err := extractJSONObject(rawText)
if err != nil {
g.debug("gemini:image_expand_parse_error", map[string]any{"query": trimmed, "error": err.Error()})
g.setCachedExpansion(cacheKey, fallback, 15*time.Minute)
return fallback, err
}
var payload struct {
Queries []string `json:"queries"`
}
if err := json.Unmarshal([]byte(jsonText), &payload); err != nil {
g.debug("gemini:image_expand_json_error", map[string]any{"query": trimmed, "error": err.Error(), "raw": truncateForError(rawText, 200)})
g.setCachedExpansion(cacheKey, fallback, 15*time.Minute)
return fallback, err
}
queries := normalizeImageExpansionQueries(payload.Queries)
if len(queries) != 5 {
err := fmt.Errorf("gemini image expansion returned %d queries", len(queries))
g.debug("gemini:image_expand_invalid_count", map[string]any{"query": trimmed, "queries": queries, "error": err.Error()})
g.setCachedExpansion(cacheKey, fallback, 15*time.Minute)
return fallback, err
}
g.setCachedExpansion(cacheKey, queries, 15*time.Minute)
g.debug("gemini:image_expand_success", map[string]any{"query": trimmed, "queries": queries})
return queries, nil
}
func (g *GeminiService) TranslateSummaryToKorean(text string) (string, error) {
trimmed := strings.TrimSpace(text)
if trimmed == "" {