Adds a comprehensive quota management system for LLM models that allows: - Setting per-group (applied per user in the group) token and usage limits with configurable durations - Tracking and enforcing token/usage limits across user groups - Quota reset periods (hourly, daily, weekly, or custom) - Admin UI for managing quotas with real-time updates This system provides granular control over LLM API usage by allowing admins to define limits on both total tokens and number of requests per group. Supports multiple concurrent quotas per model and automatically handles quota resets. Co-authored-by: Keegan George <[email protected]>
658 lines
20 KiB
Plaintext
658 lines
20 KiB
Plaintext
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 { 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 AiForcedToolStrategySelector from "./ai-forced-tool-strategy-selector";
|
|
import AiLlmSelector from "./ai-llm-selector";
|
|
import AiPersonaToolOptions from "./ai-persona-tool-options";
|
|
import AiToolSelector from "./ai-tool-selector";
|
|
import RagOptions from "./rag-options";
|
|
import RagUploader from "./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 selectedTools = [];
|
|
@tracked selectedToolNames = [];
|
|
@tracked forcedToolNames = [];
|
|
@tracked hasDefaultLlm = false;
|
|
|
|
get chatPluginEnabled() {
|
|
return this.siteSettings.chat_enabled;
|
|
}
|
|
|
|
get allowForceTools() {
|
|
return !this.editingModel?.system && this.selectedToolNames.length > 0;
|
|
}
|
|
|
|
get hasForcedTools() {
|
|
return this.forcedToolNames.length > 0;
|
|
}
|
|
|
|
@action
|
|
forcedToolsChanged(tools) {
|
|
this.forcedToolNames = tools;
|
|
this.editingModel.forcedTools = this.forcedToolNames;
|
|
}
|
|
|
|
@action
|
|
toolsChanged(tools) {
|
|
this.selectedTools = this.args.personas.resultSetMeta.tools.filter((tool) =>
|
|
tools.includes(tool.id)
|
|
);
|
|
this.selectedToolNames = tools.slice();
|
|
|
|
this.forcedToolNames = this.forcedToolNames.filter(
|
|
(tool) => this.editingModel.tools.indexOf(tool) !== -1
|
|
);
|
|
|
|
this.editingModel.tools = this.selectedToolNames;
|
|
this.editingModel.forcedTools = this.forcedToolNames;
|
|
}
|
|
|
|
@action
|
|
updateModel() {
|
|
this.editingModel = this.args.model.workingCopy();
|
|
this.hasDefaultLlm = !!this.editingModel.default_llm;
|
|
this.showDelete = !this.args.model.isNew && !this.args.model.system;
|
|
this.maxPixelsValue = this.findClosestPixelValue(
|
|
this.editingModel.vision_max_pixels
|
|
);
|
|
|
|
this.selectedToolNames = this.editingModel.tools || [];
|
|
this.selectedTools = this.args.personas.resultSetMeta.tools.filter((tool) =>
|
|
this.selectedToolNames.includes(tool.id)
|
|
);
|
|
this.forcedToolNames = this.editingModel.forcedTools || [];
|
|
}
|
|
|
|
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(`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 },
|
|
];
|
|
}
|
|
|
|
@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-personas.edit",
|
|
this.args.model
|
|
);
|
|
} else {
|
|
this.toasts.success({
|
|
data: { message: i18n("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 mappedQuestionConsolidatorLlm() {
|
|
return this.editingModel?.question_consolidator_llm || "blank";
|
|
}
|
|
|
|
set mappedQuestionConsolidatorLlm(value) {
|
|
if (value === "blank") {
|
|
this.editingModel.question_consolidator_llm = null;
|
|
} else {
|
|
this.editingModel.question_consolidator_llm = value;
|
|
}
|
|
}
|
|
|
|
get mappedDefaultLlm() {
|
|
return this.editingModel?.default_llm || "blank";
|
|
}
|
|
|
|
set mappedDefaultLlm(value) {
|
|
if (value === "blank") {
|
|
this.editingModel.default_llm = null;
|
|
this.hasDefaultLlm = false;
|
|
} else {
|
|
this.editingModel.default_llm = value;
|
|
this.hasDefaultLlm = true;
|
|
}
|
|
}
|
|
|
|
@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("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-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 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
|
|
updateUploads(uploads) {
|
|
this.editingModel.rag_uploads = uploads;
|
|
}
|
|
|
|
@action
|
|
removeUpload(upload) {
|
|
this.editingModel.rag_uploads.removeObject(upload);
|
|
if (!this.args.model.isNew) {
|
|
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);
|
|
}
|
|
|
|
<template>
|
|
<BackButton
|
|
@route="adminPlugins.show.discourse-ai-personas"
|
|
@label="discourse_ai.ai_persona.back"
|
|
/>
|
|
<form
|
|
class="form-horizontal ai-persona-editor"
|
|
{{didUpdate this.updateModel @model.id}}
|
|
{{didInsert this.updateModel @model.id}}
|
|
{{didInsert this.updateAllGroups @model.id}}
|
|
>
|
|
<div class="control-group">
|
|
<DToggleSwitch
|
|
class="ai-persona-editor__enabled"
|
|
@state={{@model.enabled}}
|
|
@label="discourse_ai.ai_persona.enabled"
|
|
{{on "click" this.toggleEnabled}}
|
|
/>
|
|
</div>
|
|
<div class="control-group ai-persona-editor__priority">
|
|
<DToggleSwitch
|
|
class="ai-persona-editor__priority"
|
|
@state={{@model.priority}}
|
|
@label="discourse_ai.ai_persona.priority"
|
|
{{on "click" this.togglePriority}}
|
|
/>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.priority_help"}}
|
|
/>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.name"}}</label>
|
|
<Input
|
|
class="ai-persona-editor__name"
|
|
@type="text"
|
|
@value={{this.editingModel.name}}
|
|
disabled={{this.editingModel.system}}
|
|
/>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.description"}}</label>
|
|
<Textarea
|
|
class="ai-persona-editor__description"
|
|
@value={{this.editingModel.description}}
|
|
disabled={{this.editingModel.system}}
|
|
/>
|
|
</div>
|
|
{{#if this.editingModel.user}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.default_llm"}}</label>
|
|
<AiLlmSelector
|
|
class="ai-persona-editor__llms"
|
|
@value={{this.mappedDefaultLlm}}
|
|
@llms={{@personas.resultSetMeta.llms}}
|
|
/>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.default_llm_help"}}
|
|
/>
|
|
</div>
|
|
{{#if this.hasDefaultLlm}}
|
|
<div class="control-group">
|
|
<label>
|
|
<Input
|
|
@type="checkbox"
|
|
@checked={{this.editingModel.force_default_llm}}
|
|
/>
|
|
{{i18n "discourse_ai.ai_persona.force_default_llm"}}</label>
|
|
</div>
|
|
{{/if}}
|
|
{{/if}}
|
|
{{#unless @model.isNew}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.user"}}</label>
|
|
{{#if this.editingModel.user}}
|
|
<a
|
|
class="avatar"
|
|
href={{this.editingModel.user.path}}
|
|
data-user-card={{this.editingModel.user.username}}
|
|
>
|
|
{{Avatar this.editingModel.user.avatar_template "small"}}
|
|
</a>
|
|
<LinkTo @route="adminUser" @model={{this.adminUser}}>
|
|
{{this.editingModel.user.username}}
|
|
</LinkTo>
|
|
{{else}}
|
|
<DButton
|
|
@action={{this.createUser}}
|
|
class="ai-persona-editor__create-user"
|
|
>
|
|
{{i18n "discourse_ai.ai_persona.create_user"}}
|
|
</DButton>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.create_user_help"}}
|
|
/>
|
|
{{/if}}
|
|
</div>
|
|
{{/unless}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.tools"}}</label>
|
|
<AiToolSelector
|
|
class="ai-persona-editor__tools"
|
|
@value={{this.selectedToolNames}}
|
|
@disabled={{this.editingModel.system}}
|
|
@tools={{@personas.resultSetMeta.tools}}
|
|
@onChange={{this.toolsChanged}}
|
|
/>
|
|
</div>
|
|
{{#if this.allowForceTools}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.forced_tools"}}</label>
|
|
<AiToolSelector
|
|
class="ai-persona-editor__forced_tools"
|
|
@value={{this.forcedToolNames}}
|
|
@tools={{this.selectedTools}}
|
|
@onChange={{this.forcedToolsChanged}}
|
|
/>
|
|
</div>
|
|
{{#if this.hasForcedTools}}
|
|
<div class="control-group">
|
|
<label>{{i18n
|
|
"discourse_ai.ai_persona.forced_tool_strategy"
|
|
}}</label>
|
|
<AiForcedToolStrategySelector
|
|
class="ai-persona-editor__forced_tool_strategy"
|
|
@value={{this.editingModel.forced_tool_count}}
|
|
/>
|
|
</div>
|
|
{{/if}}
|
|
{{/if}}
|
|
{{#unless this.editingModel.system}}
|
|
<AiPersonaToolOptions
|
|
@persona={{this.editingModel}}
|
|
@tools={{this.selectedToolNames}}
|
|
@allTools={{@personas.resultSetMeta.tools}}
|
|
/>
|
|
{{/unless}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.allowed_groups"}}</label>
|
|
<GroupChooser
|
|
@value={{this.editingModel.allowed_group_ids}}
|
|
@content={{this.allGroups}}
|
|
@onChange={{this.updateAllowedGroups}}
|
|
/>
|
|
</div>
|
|
<div class="control-group">
|
|
<label for="ai-persona-editor__system_prompt">{{i18n
|
|
"discourse_ai.ai_persona.system_prompt"
|
|
}}</label>
|
|
<Textarea
|
|
class="ai-persona-editor__system_prompt"
|
|
@value={{this.editingModel.system_prompt}}
|
|
disabled={{this.editingModel.system}}
|
|
/>
|
|
</div>
|
|
<div class="control-group ai-persona-editor__allow_personal_messages">
|
|
<label>
|
|
<Input
|
|
@type="checkbox"
|
|
@checked={{this.editingModel.allow_personal_messages}}
|
|
/>
|
|
{{i18n "discourse_ai.ai_persona.allow_personal_messages"}}</label>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n
|
|
"discourse_ai.ai_persona.allow_personal_messages_help"
|
|
}}
|
|
/>
|
|
</div>
|
|
{{#if this.editingModel.user}}
|
|
<div class="control-group ai-persona-editor__allow_topic_mentions">
|
|
<label>
|
|
<Input
|
|
@type="checkbox"
|
|
@checked={{this.editingModel.allow_topic_mentions}}
|
|
/>
|
|
{{i18n "discourse_ai.ai_persona.allow_topic_mentions"}}</label>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n
|
|
"discourse_ai.ai_persona.allow_topic_mentions_help"
|
|
}}
|
|
/>
|
|
</div>
|
|
{{#if this.chatPluginEnabled}}
|
|
<div
|
|
class="control-group ai-persona-editor__allow_chat_direct_messages"
|
|
>
|
|
<label>
|
|
<Input
|
|
@type="checkbox"
|
|
@checked={{this.editingModel.allow_chat_direct_messages}}
|
|
/>
|
|
{{i18n
|
|
"discourse_ai.ai_persona.allow_chat_direct_messages"
|
|
}}</label>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n
|
|
"discourse_ai.ai_persona.allow_chat_direct_messages_help"
|
|
}}
|
|
/>
|
|
</div>
|
|
<div
|
|
class="control-group ai-persona-editor__allow_chat_channel_mentions"
|
|
>
|
|
<label>
|
|
<Input
|
|
@type="checkbox"
|
|
@checked={{this.editingModel.allow_chat_channel_mentions}}
|
|
/>
|
|
{{i18n
|
|
"discourse_ai.ai_persona.allow_chat_channel_mentions"
|
|
}}</label>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n
|
|
"discourse_ai.ai_persona.allow_chat_channel_mentions_help"
|
|
}}
|
|
/>
|
|
</div>
|
|
{{/if}}
|
|
{{/if}}
|
|
<div class="control-group ai-persona-editor__tool-details">
|
|
<label>
|
|
<Input @type="checkbox" @checked={{this.editingModel.tool_details}} />
|
|
{{i18n "discourse_ai.ai_persona.tool_details"}}</label>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.tool_details_help"}}
|
|
/>
|
|
</div>
|
|
<div class="control-group ai-persona-editor__vision_enabled">
|
|
<label>
|
|
<Input
|
|
@type="checkbox"
|
|
@checked={{this.editingModel.vision_enabled}}
|
|
/>
|
|
{{i18n "discourse_ai.ai_persona.vision_enabled"}}</label>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.vision_enabled_help"}}
|
|
/>
|
|
</div>
|
|
{{#if this.editingModel.vision_enabled}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.vision_max_pixels"}}</label>
|
|
<ComboBox
|
|
@value={{this.maxPixelsValue}}
|
|
@content={{this.maxPixelValues}}
|
|
@onChange={{this.onChangeMaxPixels}}
|
|
/>
|
|
</div>
|
|
{{/if}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.max_context_posts"}}</label>
|
|
<Input
|
|
@type="number"
|
|
lang="en"
|
|
class="ai-persona-editor__max_context_posts"
|
|
@value={{this.editingModel.max_context_posts}}
|
|
/>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.max_context_posts_help"}}
|
|
/>
|
|
</div>
|
|
{{#if this.showTemperature}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.temperature"}}</label>
|
|
<Input
|
|
@type="number"
|
|
class="ai-persona-editor__temperature"
|
|
step="any"
|
|
lang="en"
|
|
@value={{this.editingModel.temperature}}
|
|
disabled={{this.editingModel.system}}
|
|
/>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.temperature_help"}}
|
|
/>
|
|
</div>
|
|
{{/if}}
|
|
{{#if this.showTopP}}
|
|
<div class="control-group">
|
|
<label>{{i18n "discourse_ai.ai_persona.top_p"}}</label>
|
|
<Input
|
|
@type="number"
|
|
step="any"
|
|
lang="en"
|
|
class="ai-persona-editor__top_p"
|
|
@value={{this.editingModel.top_p}}
|
|
disabled={{this.editingModel.system}}
|
|
/>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n "discourse_ai.ai_persona.top_p_help"}}
|
|
/>
|
|
</div>
|
|
{{/if}}
|
|
{{#if this.siteSettings.ai_embeddings_enabled}}
|
|
<div class="control-group">
|
|
<RagUploader
|
|
@target={{this.editingModel}}
|
|
@updateUploads={{this.updateUploads}}
|
|
@onRemove={{this.removeUpload}}
|
|
/>
|
|
</div>
|
|
<RagOptions @model={{this.editingModel}}>
|
|
<div class="control-group">
|
|
<label>{{i18n
|
|
"discourse_ai.ai_persona.rag_conversation_chunks"
|
|
}}</label>
|
|
<Input
|
|
@type="number"
|
|
step="any"
|
|
lang="en"
|
|
class="ai-persona-editor__rag_conversation_chunks"
|
|
@value={{this.editingModel.rag_conversation_chunks}}
|
|
/>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n
|
|
"discourse_ai.ai_persona.rag_conversation_chunks_help"
|
|
}}
|
|
/>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>{{i18n
|
|
"discourse_ai.ai_persona.question_consolidator_llm"
|
|
}}</label>
|
|
<AiLlmSelector
|
|
class="ai-persona-editor__llms"
|
|
@value={{this.mappedQuestionConsolidatorLlm}}
|
|
@llms={{@personas.resultSetMeta.llms}}
|
|
/>
|
|
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n
|
|
"discourse_ai.ai_persona.question_consolidator_llm_help"
|
|
}}
|
|
/>
|
|
</div>
|
|
</RagOptions>
|
|
{{/if}}
|
|
<div class="control-group ai-persona-editor__action_panel">
|
|
<DButton
|
|
class="btn-primary ai-persona-editor__save"
|
|
@action={{this.save}}
|
|
@disabled={{this.isSaving}}
|
|
>{{i18n "discourse_ai.ai_persona.save"}}</DButton>
|
|
{{#if this.showDelete}}
|
|
<DButton
|
|
@action={{this.delete}}
|
|
class="btn-danger ai-persona-editor__delete"
|
|
>
|
|
{{i18n "discourse_ai.ai_persona.delete"}}
|
|
</DButton>
|
|
{{/if}}
|
|
</div>
|
|
</form>
|
|
</template>
|
|
}
|