Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions src/yetibot/core/commands/bameme.clj
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ Make the image funny, bold, and immediately recognizable as a meme.")
[{:keys [match]}]
(if (gemini/configured?)
(try
(let [prompt (build-meme-prompt match)]
(info "bameme: generating meme for:" match)
(if-let [image (gemini/generate-image prompt meme-system-prompt)]
(let [id (store-image! image)
base-url (gemini/yetibot-base-url)
image-url (format "%s/generated-images/%s.png" base-url id)]
(info "bameme: meme generated successfully, serving at" image-url)
{:result/value image-url
:result/data {:id id :prompt match :url image-url}})
{:result/error "No meme was generated. Try a different prompt."}))
(let [prompt (build-meme-prompt match)
_ (info "bameme: generating meme for:" match)
image (gemini/generate-image prompt meme-system-prompt)
id (store-image! image)
base-url (gemini/yetibot-base-url)
image-url (format "%s/generated-images/%s.png" base-url id)]
(info "bameme: meme generated successfully, serving at" image-url)
{:result/value image-url
:result/data {:id id :prompt match :url image-url}})
(catch Exception e
(error "bameme: Gemini meme generation error:" (.getMessage e))
{:result/error (str "Meme generation failed: " (.getMessage e))}))
Expand Down
17 changes: 8 additions & 9 deletions src/yetibot/core/commands/banana.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@
(if (gemini/configured?)
(try
(info "banana: generating image for prompt:" match)
(if-let [image (gemini/generate-image
(str "Generate an image: " match))]
(let [id (store-image! image)
base-url (gemini/yetibot-base-url)
image-url (format "%s/generated-images/%s.png" base-url id)]
(info "banana: image generated successfully, serving at" image-url)
{:result/value image-url
:result/data {:id id :prompt match :url image-url}})
{:result/error "No image was generated. Try a different prompt."})
(let [image (gemini/generate-image
(str "Generate an image: " match))
id (store-image! image)
base-url (gemini/yetibot-base-url)
image-url (format "%s/generated-images/%s.png" base-url id)]
(info "banana: image generated successfully, serving at" image-url)
{:result/value image-url
:result/data {:id id :prompt match :url image-url}})
(catch Exception e
(error "banana: Gemini image generation error:" (.getMessage e))
{:result/error (str "Image generation failed: " (.getMessage e))}))
Expand Down
64 changes: 60 additions & 4 deletions src/yetibot/core/util/gemini.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
(:require [clj-http.client :as client]
[clojure.data.json :as json]
[clojure.spec.alpha :as s]
[clojure.string :as string]
[taoensso.timbre :refer [info warn error]]
[yetibot.core.config :refer [get-config]]
[yetibot.core.db.image-budget :as image-budget])
Expand Down Expand Up @@ -142,6 +143,49 @@
:mime-type (:mimeType inline-data)}))
parts)))

(defn- extract-api-error
"Extract a human-readable error message from a Gemini API error response body."
[body]
(try
(let [parsed (if (string? body)
(json/read-str body :key-fn keyword)
body)
error-obj (:error parsed)
message (:message error-obj)
status (:status error-obj)
code (:code error-obj)]
(if message
(str (when code (str "(" code ") "))
(when status (str status ": "))
message)
(str parsed)))
(catch Exception _
(if (string? body) body (str body)))))

(defn- extract-block-reason
"Extract block/filter reason from a Gemini API response that returned no image."
[response-body]
(let [block-reason (get-in response-body [:promptFeedback :blockReason])
finish-reason (get-in response-body [:candidates 0 :finishReason])
safety-ratings (or (get-in response-body [:promptFeedback :safetyRatings])
(get-in response-body [:candidates 0 :safetyRatings]))
flagged (when safety-ratings
(->> safety-ratings
(filter #(#{"HIGH" "MEDIUM"} (:probability %)))
(map :category)))]
(cond
block-reason
(str "Prompt blocked by Gemini: " block-reason
(when (seq flagged)
(str " (flagged categories: " (string/join ", " flagged) ")")))

(and finish-reason (not= finish-reason "STOP"))
(str "Generation stopped: " finish-reason
(when (seq flagged)
(str " (flagged categories: " (string/join ", " flagged) ")")))

:else nil)))

(defn generate-image
"Call the Gemini API to generate an image from a text prompt.
Accepts an optional system-instruction string for guiding generation style.
Expand All @@ -163,10 +207,22 @@
{:content-type :json
:body (json/write-str body)
:as :json
:throw-exceptions true})]
(when-let [image (extract-image (:body response))]
(record-image-generated!)
image))))
:throw-exceptions false})
status (:status response)]
(when-not (<= 200 status 299)
(let [error-msg (extract-api-error (:body response))]
(error "gemini: API error" status "-" error-msg)
(throw (ex-info (str "Gemini API error: " error-msg)
{:type :gemini-api-error
:status status}))))
(if-let [image (extract-image (:body response))]
(do (record-image-generated!)
image)
(let [reason (extract-block-reason (:body response))]
(throw (ex-info (or reason
"No image was generated. Try a different prompt.")
{:type :no-image-generated
:response-body (:body response)})))))))

(defn yetibot-base-url []
(or (:value (get-config string? [:url]))
Expand Down