FIX: Persona editor keeps dirty data after persisting a single field (#1219)

This commit is contained in:
Roman Rizzi 2025-03-26 15:36:01 -03:00 committed by GitHub
parent 371146cdfe
commit 1b05da704e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 57 additions and 17 deletions

View File

@ -32,8 +32,17 @@ export default class PersonaEditor extends Component {
@tracked isSaving = false; @tracked isSaving = false;
@tracked availableForcedTools = []; @tracked availableForcedTools = [];
dirtyFormData = null;
@cached @cached
get formData() { get formData() {
// This is to recover a dirty state after persisting a single form field.
// It's meant to be consumed only once.
if (this.dirtyFormData) {
const data = this.dirtyFormData;
this.dirtyFormData = null;
return data;
} else {
const data = this.args.model.toPOJO(); const data = this.args.model.toPOJO();
if (data.tools) { if (data.tools) {
@ -42,6 +51,7 @@ export default class PersonaEditor extends Component {
return data; return data;
} }
}
get chatPluginEnabled() { get chatPluginEnabled() {
return this.siteSettings.chat_enabled; return this.siteSettings.chat_enabled;
@ -144,15 +154,15 @@ export default class PersonaEditor extends Component {
} }
@action @action
async toggleEnabled(value, { set }) { async toggleEnabled(dirtyData, value, { set }) {
set("enabled", value); set("enabled", value);
await this.persistField("enabled", value); await this.persistField(dirtyData, "enabled", value);
} }
@action @action
async togglePriority(value, { set }) { async togglePriority(dirtyData, value, { set }) {
set("priority", value); set("priority", value);
await this.persistField("priority", value, true); await this.persistField(dirtyData, "priority", value, true);
} }
@action @action
@ -172,7 +182,7 @@ export default class PersonaEditor extends Component {
} }
@action @action
async removeUpload(form, currentUploads, upload) { async removeUpload(form, dirtyData, currentUploads, upload) {
const updatedUploads = currentUploads.filter( const updatedUploads = currentUploads.filter(
(file) => file.id !== upload.id (file) => file.id !== upload.id
); );
@ -180,7 +190,7 @@ export default class PersonaEditor extends Component {
form.set("rag_uploads", updatedUploads); form.set("rag_uploads", updatedUploads);
if (!this.args.model.isNew) { if (!this.args.model.isNew) {
await this.persistField("rag_uploads", updatedUploads); await this.persistField(dirtyData, "rag_uploads", updatedUploads);
} }
} }
@ -232,14 +242,16 @@ export default class PersonaEditor extends Component {
return updatedOptions; return updatedOptions;
} }
async persistField(field, newValue, sortPersonas) { async persistField(dirtyData, field, newValue, sortPersonas) {
this.args.model.set(field, newValue);
if (!this.args.model.isNew) { if (!this.args.model.isNew) {
const updatedDirtyData = Object.assign({}, dirtyData);
updatedDirtyData[field] = newValue;
try { try {
const args = {}; const args = {};
args[field] = newValue; args[field] = newValue;
this.dirtyFormData = updatedDirtyData;
await this.args.model.update(args); await this.args.model.update(args);
if (sortPersonas) { if (sortPersonas) {
this.#sortPersonas(); this.#sortPersonas();
@ -274,7 +286,7 @@ export default class PersonaEditor extends Component {
<form.Field <form.Field
@name="enabled" @name="enabled"
@title={{i18n "discourse_ai.ai_persona.enabled"}} @title={{i18n "discourse_ai.ai_persona.enabled"}}
@onSet={{this.toggleEnabled}} @onSet={{fn this.toggleEnabled data}}
as |field| as |field|
> >
<field.Toggle /> <field.Toggle />
@ -283,7 +295,7 @@ export default class PersonaEditor extends Component {
<form.Field <form.Field
@name="priority" @name="priority"
@title={{i18n "discourse_ai.ai_persona.priority"}} @title={{i18n "discourse_ai.ai_persona.priority"}}
@onSet={{this.togglePriority}} @onSet={{fn this.togglePriority data}}
@tooltip={{i18n "discourse_ai.ai_persona.priority_help"}} @tooltip={{i18n "discourse_ai.ai_persona.priority_help"}}
as |field| as |field|
> >
@ -499,7 +511,7 @@ export default class PersonaEditor extends Component {
@target={{data}} @target={{data}}
@targetName="AiPersona" @targetName="AiPersona"
@updateUploads={{fn this.updateUploads form}} @updateUploads={{fn this.updateUploads form}}
@onRemove={{fn this.removeUpload form field.value}} @onRemove={{fn this.removeUpload form data field.value}}
@allowImages={{@personas.resultSetMeta.settings.rag_images_enabled}} @allowImages={{@personas.resultSetMeta.settings.rag_images_enabled}}
/> />
</field.Custom> </field.Custom>

View File

@ -71,6 +71,34 @@ RSpec.describe "Admin AI persona configuration", type: :system, js: true do
expect(persona.name).not_to eq("Test Persona 1") expect(persona.name).not_to eq("Test Persona 1")
end end
it "enabling a persona doesn't reset other fields" do
persona = Fabricate(:ai_persona, enabled: false)
updated_name = "Update persona 1"
visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}/edit"
form.field("name").fill_in(updated_name)
form.field("enabled").toggle
try_until_success { expect(persona.reload.enabled).to eq(true) }
expect(form.field("name").value).to eq(updated_name)
end
it "toggling a persona's priority doesn't reset other fields" do
persona = Fabricate(:ai_persona, priority: false)
updated_name = "Update persona 1"
visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}/edit"
form.field("name").fill_in(updated_name)
form.field("priority").toggle
try_until_success { expect(persona.reload.priority).to eq(true) }
expect(form.field("name").value).to eq(updated_name)
end
it "can navigate the AI plugin with breadcrumbs" do it "can navigate the AI plugin with breadcrumbs" do
visit "/admin/plugins/discourse-ai/ai-personas" visit "/admin/plugins/discourse-ai/ai-personas"
expect(page).to have_css(".d-breadcrumbs") expect(page).to have_css(".d-breadcrumbs")