# frozen_string_literal: true RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } fab!(:post) fab!(:artifact) do AiArtifact.create!( user: Fabricate(:user), post: post, name: "Test Artifact", html: "
Original
", css: ".test { color: blue; }", js: "console.log('original');\nconsole.log('world');\nconsole.log('hello');", ) end before { SiteSetting.ai_bot_enabled = true } describe "#process" do it "correctly updates artifact using section markers" do responses = [<<~TXT.strip] [HTML]
Updated
[/HTML] [CSS] .test { color: red; } [/CSS] [JavaScript] console.log('updated'); console.log('world'); console.log('updated2'); [/JavaScript] TXT tool = nil DiscourseAi::Completions::Llm.with_prepared_responses(responses) do tool = described_class.new( { artifact_id: artifact.id, instructions: "Change the text to Updated and color to red", }, bot_user: bot_user, llm: llm_model.to_llm, persona_options: { "update_algorithm" => "full", }, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} expect(result[:status]).to eq("success") end version = artifact.versions.order(:version_number).last expect(version.html).to eq("
Updated
") expect(version.css).to eq(".test { color: red; }") expect(version.js).to eq(<<~JS.strip) console.log('updated'); console.log('world'); console.log('updated2'); JS expect(tool.custom_raw).to include("Change Description") expect(tool.custom_raw).to include("[details='View Changes']") expect(tool.custom_raw).to include("### HTML Changes") expect(tool.custom_raw).to include("### CSS Changes") expect(tool.custom_raw).to include("### JS Changes") expect(tool.custom_raw).to include("
"full", }, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} expect(result[:status]).to eq("success") end version = artifact.versions.order(:version_number).last expect(version.html).to eq("
Original
") expect(version.css).to eq(".test { color: blue; }") expect(version.js).to eq( "console.log('updated');\nconsole.log('world');\nconsole.log('hello');", ) end it "handles invalid section format" do responses = ["Invalid format without proper section markers"] DiscourseAi::Completions::Llm.with_prepared_responses(responses) do tool = described_class.new( { artifact_id: artifact.id, instructions: "Invalid update" }, bot_user: bot_user, llm: llm_model.to_llm, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} expect(result[:status]).to eq("success") # The strategy will just keep original content end end it "handles invalid artifact ID" do tool = described_class.new( { artifact_id: -1, instructions: "Update something" }, bot_user: bot_user, llm: llm_model.to_llm, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} expect(result[:status]).to eq("error") expect(result[:error]).to eq("Artifact not found") end it "preserves unchanged sections in the diff output" do responses = [<<~TXT.strip] [HTML]
Updated
[/HTML] TXT tool = nil DiscourseAi::Completions::Llm.with_prepared_responses(responses) do tool = described_class.new( { artifact_id: artifact.id, instructions: "Just update the HTML" }, bot_user: bot_user, llm: llm_model.to_llm, persona_options: { "update_algorithm" => "full", }, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) tool.invoke {} end version = artifact.versions.order(:version_number).last expect(version.css).to eq(artifact.css) expect(version.js).to eq(artifact.js) expect(tool.custom_raw).to include("### HTML Changes") expect(tool.custom_raw).not_to include("### CSS Changes") expect(tool.custom_raw).not_to include("### JavaScript Changes") end it "handles updates to specific versions" do # Create first version responses = [<<~TXT.strip] [HTML]
Version 1
[/HTML] TXT DiscourseAi::Completions::Llm.with_prepared_responses(responses) do described_class .new( { artifact_id: artifact.id, instructions: "Update to version 1" }, bot_user: bot_user, llm: llm_model.to_llm, persona_options: { "update_algorithm" => "full", }, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) .invoke {} end first_version = artifact.versions.order(:version_number).last responses = [<<~TXT.strip] [HTML]
Updated from version 1
[/HTML] TXT DiscourseAi::Completions::Llm.with_prepared_responses(responses) do tool = described_class.new( { artifact_id: artifact.id, version: first_version.version_number, instructions: "Update from version 1", }, bot_user: bot_user, llm: llm_model.to_llm, persona_options: { "update_algorithm" => "full", }, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} expect(result[:status]).to eq("success") end latest_version = artifact.versions.order(:version_number).last expect(latest_version.html).to eq("
Updated from version 1
") end end it "correctly updates artifact using diff strategy (partial diff)" do responses = [<<~TXT.strip] [HTML] nonsense <<<<<<< SEARCH
Original
=======
Updated
>>>>>>> REPLACE garbage llm injects [/HTML] [CSS] garbage llm injects <<<<<<< SEARCH .test { color: blue; } ======= .test { color: red; } >>>>>>> REPLACE nonsense [/CSS] [JavaScript] no changes [/JavaScript] LLMs like to say nonsense that we can ignore here as well TXT tool = nil DiscourseAi::Completions::Llm.with_prepared_responses(responses) do tool = described_class.new( { artifact_id: artifact.id, instructions: "Change the text to Updated and color to red" }, bot_user: bot_user, llm: llm_model.to_llm, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), persona_options: { "update_algorithm" => "diff", }, ) result = tool.invoke {} expect(result[:status]).to eq("success") end version = artifact.versions.order(:version_number).last expect(version.html).to eq("
Updated
") expect(version.css).to eq(".test { color: red; }") expect(version.js).to eq(<<~JS.strip) console.log('original'); console.log('world'); console.log('hello'); JS expect(tool.custom_raw).to include("Change Description") expect(tool.custom_raw).to include("[details='View Changes']") expect(tool.custom_raw).to include("### HTML Changes") expect(tool.custom_raw).to include("### CSS Changes") expect(tool.custom_raw).to include("
Original
=======
Updated
>>>>>>> REPLACE [/HTML] [CSS] <<<<<<< SEARCH .test { color: blue; } ======= .test { color: red; } >>>>>>> REPLACE [/CSS] [JavaScript] <<<<<<< SEARCH console.log('original'); console.log('world'); console.log('hello'); ======= console.log('updated'); console.log('world'); console.log('updated sam'); >>>>>>> REPLACE [/JavaScript] LLMs like to say nonsense that we can ignore here TXT tool = nil DiscourseAi::Completions::Llm.with_prepared_responses(responses) do tool = described_class.new( { artifact_id: artifact.id, instructions: "Change the text to Updated and color to red" }, bot_user: bot_user, llm: llm_model.to_llm, context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), persona_options: { "update_algorithm" => "diff", }, ) result = tool.invoke {} expect(result[:status]).to eq("success") end version = artifact.versions.order(:version_number).last expect(version.html).to eq("
Updated
") expect(version.css).to eq(".test { color: red; }") expect(version.js).to eq(<<~JS.strip) console.log('updated'); console.log('world'); console.log('updated sam'); JS expect(tool.custom_raw).to include("Change Description") expect(tool.custom_raw).to include("[details='View Changes']") expect(tool.custom_raw).to include("### HTML Changes") expect(tool.custom_raw).to include("### CSS Changes") expect(tool.custom_raw).to include("### JS Changes") expect(tool.custom_raw).to include("