# frozen_string_literal: true RSpec.describe DiscourseAi::Personas::Tools::EditImage do fab!(:gpt_35_turbo) { Fabricate(:llm_model, name: "gpt-3.5-turbo") } before do SiteSetting.ai_bot_enabled = true toggle_enabled_bots(bots: [gpt_35_turbo]) SiteSetting.ai_openai_api_key = "abc" end let(:image_upload) do UploadCreator.new( File.open(Rails.root.join("spec/fixtures/images/smallest.png")), "smallest.png", ).create_for(Discourse.system_user.id) end let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(gpt_35_turbo.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{gpt_35_turbo.id}") } let(:progress_blk) { Proc.new {} } let(:prompt) { "add a rainbow in the background" } let(:edit_image) do described_class.new( { image_urls: [image_upload.short_url], prompt: prompt }, llm: llm, bot_user: bot_user, ) end let(:base64_image) do "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" end describe "#process" do it "can reject generation of images and return a proper error to llm" do error_message = { error: { message: "Your request was rejected as a result of our safety system. Your request may contain content that is not allowed by our safety system.", type: "user_error", param: nil, code: "moderation_blocked", }, } WebMock.stub_request(:post, "https://api.openai.com/v1/images/edits").to_return( status: 400, body: error_message.to_json, ) info = edit_image.invoke(&progress_blk).to_json expect(info).to include("Your request was rejected as a result of our safety system.") expect(edit_image.chain_next_response?).to eq(true) end it "can edit an image with the GPT image model" do data = [{ b64_json: base64_image, revised_prompt: "image with rainbow added in background" }] # Stub the OpenAI API call WebMock .stub_request(:post, "https://api.openai.com/v1/images/edits") .with do |request| # The request is multipart/form-data, so we can't easily parse the body # Just check that the request was made to the right endpoint expect(request.headers["Content-Type"]).to include("multipart/form-data") true end .to_return(status: 200, body: { data: data }.to_json) info = edit_image.invoke(&progress_blk).to_json expect(JSON.parse(info)).to eq( { "prompt" => "image with rainbow added in background", "url" => "upload://pv9zsrM93Jz3U8xELTJCPYU2DD0.png", }, ) expect(edit_image.custom_raw).to include("upload://") expect(edit_image.custom_raw).to include("![image with rainbow added in background]") end it "handles custom API endpoint" do SiteSetting.ai_openai_image_edit_url = "https://custom-api.example.com/images/edit" data = [{ b64_json: base64_image, revised_prompt: "image with rainbow added" }] # Stub the custom API endpoint WebMock .stub_request(:post, SiteSetting.ai_openai_image_edit_url) .with do |request| expect(request.headers["Content-Type"]).to include("multipart/form-data") true end .to_return(status: 200, body: { data: data }.to_json) info = edit_image.invoke(&progress_blk).to_json expect(JSON.parse(info)).to eq( { "prompt" => "image with rainbow added", "url" => "upload://pv9zsrM93Jz3U8xELTJCPYU2DD0.png", }, ) end end end