import Component from "@glimmer/component"; import { cached, tracked } from "@glimmer/tracking"; import { Input } from "@ember/component"; import { on } from "@ember/modifier"; import { action } from "@ember/object"; import didInsert from "@ember/render-modifiers/modifiers/did-insert"; import didUpdate from "@ember/render-modifiers/modifiers/did-update"; import { LinkTo } from "@ember/routing"; import { later } from "@ember/runloop"; import { inject as service } from "@ember/service"; import BackButton from "discourse/components/back-button"; import DButton from "discourse/components/d-button"; import Textarea from "discourse/components/d-textarea"; import DToggleSwitch from "discourse/components/d-toggle-switch"; import Avatar from "discourse/helpers/bound-avatar-template"; import { popupAjaxError } from "discourse/lib/ajax-error"; import Group from "discourse/models/group"; import I18n from "discourse-i18n"; import AdminUser from "admin/models/admin-user"; import ComboBox from "select-kit/components/combo-box"; import GroupChooser from "select-kit/components/group-chooser"; import DTooltip from "float-kit/components/d-tooltip"; import AiCommandSelector from "./ai-command-selector"; import AiLlmSelector from "./ai-llm-selector"; import AiPersonaCommandOptions from "./ai-persona-command-options"; import PersonaRagUploader from "./persona-rag-uploader"; export default class PersonaEditor extends Component { @service router; @service store; @service dialog; @service toasts; @service siteSettings; @tracked allGroups = []; @tracked isSaving = false; @tracked editingModel = null; @tracked showDelete = false; @tracked maxPixelsValue = null; @tracked ragIndexingStatuses = null; @tracked showIndexingOptions = false; @action updateModel() { this.editingModel = this.args.model.workingCopy(); this.showDelete = !this.args.model.isNew && !this.args.model.system; this.maxPixelsValue = this.findClosestPixelValue( this.editingModel.vision_max_pixels ); } @action toggleIndexingOptions(event) { this.showIndexingOptions = !this.showIndexingOptions; event.preventDefault(); event.stopPropagation(); } findClosestPixelValue(pixels) { let value = "high"; this.maxPixelValues.forEach((info) => { if (pixels === info.pixels) { value = info.id; } }); return value; } @cached get maxPixelValues() { const l = (key) => I18n.t(`discourse_ai.ai_persona.vision_max_pixel_sizes.${key}`); return [ { id: "low", name: l("low"), pixels: 65536 }, { id: "medium", name: l("medium"), pixels: 262144 }, { id: "high", name: l("high"), pixels: 1048576 }, ]; } get indexingOptionsText() { return this.showIndexingOptions ? I18n.t("discourse_ai.ai_persona.hide_indexing_options") : I18n.t("discourse_ai.ai_persona.show_indexing_options"); } @action async updateAllGroups() { this.allGroups = await Group.findAll(); } @action async save() { const isNew = this.args.model.isNew; this.isSaving = true; const backupModel = this.args.model.workingCopy(); this.args.model.setProperties(this.editingModel); try { await this.args.model.save(); this.#sortPersonas(); if (isNew && this.args.model.rag_uploads.length === 0) { this.args.personas.addObject(this.args.model); this.router.transitionTo( "adminPlugins.show.discourse-ai.ai-personas.show", this.args.model ); } else { this.toasts.success({ data: { message: I18n.t("discourse_ai.ai_persona.saved") }, duration: 2000, }); } } catch (e) { this.args.model.setProperties(backupModel); popupAjaxError(e); } finally { later(() => { this.isSaving = false; }, 1000); } } get showTemperature() { return this.editingModel?.temperature || !this.editingModel?.system; } get showTopP() { return this.editingModel?.top_p || !this.editingModel?.system; } get adminUser() { return AdminUser.create(this.editingModel?.user); } get mappedDefaultLlm() { return this.editingModel?.default_llm || "blank"; } set mappedDefaultLlm(value) { if (value === "blank") { this.editingModel.default_llm = null; } else { this.editingModel.default_llm = value; } } @action onChangeMaxPixels(value) { const entry = this.maxPixelValues.findBy("id", value); if (!entry) { return; } this.maxPixelsValue = value; this.editingModel.vision_max_pixels = entry.pixels; } @action delete() { return this.dialog.confirm({ message: I18n.t("discourse_ai.ai_persona.confirm_delete"), didConfirm: () => { return this.args.model.destroyRecord().then(() => { this.args.personas.removeObject(this.args.model); this.router.transitionTo( "adminPlugins.show.discourse-ai.ai-personas.index" ); }); }, }); } @action updateAllowedGroups(ids) { this.editingModel.set("allowed_group_ids", ids); } @action async toggleEnabled() { await this.toggleField("enabled"); } @action async togglePriority() { await this.toggleField("priority", true); } @action async toggleMentionable() { await this.toggleField("mentionable"); } @action async toggleVisionEnabled() { await this.toggleField("vision_enabled"); } @action async createUser() { try { let user = await this.args.model.createUser(); this.editingModel.set("user", user); this.editingModel.set("user_id", user.id); } catch (e) { popupAjaxError(e); } } @action addUpload(upload) { const newUpload = upload; newUpload.status = "uploaded"; newUpload.statusText = I18n.t("discourse_ai.ai_persona.uploads.uploaded"); this.editingModel.rag_uploads.addObject(newUpload); } @action removeUpload(upload) { this.editingModel.rag_uploads.removeObject(upload); this.save(); } async toggleField(field, sortPersonas) { this.args.model.set(field, !this.args.model[field]); this.editingModel.set(field, this.args.model[field]); if (!this.args.model.isNew) { try { const args = {}; args[field] = this.args.model[field]; await this.args.model.update(args); if (sortPersonas) { this.#sortPersonas(); } } catch (e) { popupAjaxError(e); } } } #sortPersonas() { const sorted = this.args.personas.toArray().sort((a, b) => { if (a.priority && !b.priority) { return -1; } else if (!a.priority && b.priority) { return 1; } else { return a.name.localeCompare(b.name); } }); this.args.personas.clear(); this.args.personas.setObjects(sorted); }