FEATURE: Improve admin plugin UI and use new plugins show route (#512)
This commit changes Discourse AI's admin plugin page to use the new plugin show route. The UI for persona editing has also been improved for consistency, and other plugin UIs will follow suit: Settings for the plugin are now listed in the plugin UI and can be changed from there directly after core PR discourse/discourse#26154 is merged. See also: * https://github.com/discourse/discourse/pull/26024 * https://github.com/discourse/discourse/pull/26154 * https://github.com/discourse/discourse/pull/26254
This commit is contained in:
parent
5cac47a30a
commit
fb0d56324f
|
@ -1,10 +1,10 @@
|
||||||
export default {
|
export default {
|
||||||
resource: "admin.adminPlugins",
|
resource: "admin.adminPlugins.show",
|
||||||
|
|
||||||
path: "/plugins",
|
path: "/plugins",
|
||||||
|
|
||||||
map() {
|
map() {
|
||||||
this.route("discourse-ai", function () {
|
this.route("discourse-ai", { path: "/" }, function () {
|
||||||
this.route("ai-personas", function () {
|
this.route("ai-personas", function () {
|
||||||
this.route("new");
|
this.route("new");
|
||||||
this.route("show", { path: "/:id" });
|
this.route("show", { path: "/:id" });
|
|
@ -8,6 +8,7 @@ import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||||
import { LinkTo } from "@ember/routing";
|
import { LinkTo } from "@ember/routing";
|
||||||
import { later } from "@ember/runloop";
|
import { later } from "@ember/runloop";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
|
import BackButton from "discourse/components/back-button";
|
||||||
import DButton from "discourse/components/d-button";
|
import DButton from "discourse/components/d-button";
|
||||||
import Textarea from "discourse/components/d-textarea";
|
import Textarea from "discourse/components/d-textarea";
|
||||||
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
||||||
|
@ -58,7 +59,7 @@ export default class PersonaEditor extends Component {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
this.args.personas.addObject(this.args.model);
|
this.args.personas.addObject(this.args.model);
|
||||||
this.router.transitionTo(
|
this.router.transitionTo(
|
||||||
"adminPlugins.discourse-ai.ai-personas.show",
|
"adminPlugins.show.discourse-ai.ai-personas.show",
|
||||||
this.args.model
|
this.args.model
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -109,7 +110,7 @@ export default class PersonaEditor extends Component {
|
||||||
return this.args.model.destroyRecord().then(() => {
|
return this.args.model.destroyRecord().then(() => {
|
||||||
this.args.personas.removeObject(this.args.model);
|
this.args.personas.removeObject(this.args.model);
|
||||||
this.router.transitionTo(
|
this.router.transitionTo(
|
||||||
"adminPlugins.discourse-ai.ai-personas.index"
|
"adminPlugins.show.discourse-ai.ai-personas.index"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -180,6 +181,10 @@ export default class PersonaEditor extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<BackButton
|
||||||
|
@route="adminPlugins.show.discourse-ai.ai-personas"
|
||||||
|
@label="discourse_ai.ai_persona.back"
|
||||||
|
/>
|
||||||
<form
|
<form
|
||||||
class="form-horizontal ai-persona-editor"
|
class="form-horizontal ai-persona-editor"
|
||||||
{{didUpdate this.updateModel @model.id}}
|
{{didUpdate this.updateModel @model.id}}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { fn } from "@ember/helper";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
import { LinkTo } from "@ember/routing";
|
import { LinkTo } from "@ember/routing";
|
||||||
|
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { cook } from "discourse/lib/text";
|
import { cook } from "discourse/lib/text";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import AiPersonaEditor from "./ai-persona-editor";
|
import AiPersonaEditor from "./ai-persona-editor";
|
||||||
|
|
||||||
|
@ -21,45 +27,93 @@ export default class AiPersonaListEditor extends Component {
|
||||||
return this._noPersonaText;
|
return this._noPersonaText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async toggleEnabled(persona) {
|
||||||
|
const oldValue = persona.enabled;
|
||||||
|
const newValue = !oldValue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
persona.set("enabled", newValue);
|
||||||
|
await persona.save();
|
||||||
|
} catch (err) {
|
||||||
|
persona.set("enabled", oldValue);
|
||||||
|
popupAjaxError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ai-persona-list-editor__header">
|
<section class="ai-persona-list-editor__current admin-detail pull-left">
|
||||||
<h3>{{I18n.t "discourse_ai.ai_persona.title"}}</h3>
|
|
||||||
{{#unless @currentPersona.isNew}}
|
|
||||||
<LinkTo
|
|
||||||
@route="adminPlugins.discourse-ai.ai-personas.new"
|
|
||||||
class="btn btn-primary"
|
|
||||||
>
|
|
||||||
{{icon "plus"}}
|
|
||||||
<span>{{I18n.t "discourse_ai.ai_persona.new"}}</span>
|
|
||||||
</LinkTo>
|
|
||||||
{{/unless}}
|
|
||||||
</div>
|
|
||||||
<div class="content-list ai-persona-list-editor">
|
|
||||||
<ul>
|
|
||||||
{{#each @personas as |persona|}}
|
|
||||||
<li
|
|
||||||
class={{concatClass
|
|
||||||
(if persona.enabled "" "diabled")
|
|
||||||
(if persona.priority "priority")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LinkTo
|
|
||||||
@route="adminPlugins.discourse-ai.ai-personas.show"
|
|
||||||
current-when="true"
|
|
||||||
@model={{persona}}
|
|
||||||
>{{persona.name}}
|
|
||||||
</LinkTo>
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<section class="ai-persona-list-editor__current content-body">
|
|
||||||
{{#if @currentPersona}}
|
{{#if @currentPersona}}
|
||||||
<AiPersonaEditor @model={{@currentPersona}} @personas={{@personas}} />
|
<AiPersonaEditor @model={{@currentPersona}} @personas={{@personas}} />
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="ai-persona-list-editor__empty">
|
<div class="ai-persona-list-editor__header">
|
||||||
{{this.noPersonaText}}
|
<h3>{{i18n "discourse_ai.ai_persona.short_title"}}</h3>
|
||||||
|
{{#unless @currentPersona.isNew}}
|
||||||
|
<LinkTo
|
||||||
|
@route="adminPlugins.show.discourse-ai.ai-personas.new"
|
||||||
|
class="btn btn-small btn-primary"
|
||||||
|
>
|
||||||
|
{{icon "plus"}}
|
||||||
|
<span>{{I18n.t "discourse_ai.ai_persona.new"}}</span>
|
||||||
|
</LinkTo>
|
||||||
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="ai-persona-list-editor__empty">
|
||||||
|
<details class="details__boxed">
|
||||||
|
<summary>{{i18n
|
||||||
|
"discourse_ai.ai_persona.what_are_personas"
|
||||||
|
}}</summary>
|
||||||
|
{{this.noPersonaText}}
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="content-list ai-persona-list-editor">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{i18n "discourse_ai.ai_persona.name"}}</th>
|
||||||
|
<th>{{i18n "discourse_ai.ai_persona.enabled"}}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each @personas as |persona|}}
|
||||||
|
<tr
|
||||||
|
data-persona-id={{persona.id}}
|
||||||
|
class={{concatClass
|
||||||
|
"ai-persona-list__row"
|
||||||
|
(if persona.priority "priority")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<div class="ai-persona-list__name-with-description">
|
||||||
|
<div class="ai-persona-list__name">
|
||||||
|
<strong>
|
||||||
|
{{persona.name}}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div class="ai-persona-list__description">
|
||||||
|
{{persona.description}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{persona.enabled}}
|
||||||
|
{{on "click" (fn this.toggleEnabled persona)}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<LinkTo
|
||||||
|
@route="adminPlugins.show.discourse-ai.ai-personas.show"
|
||||||
|
@model={{persona}}
|
||||||
|
class="btn btn-text btn-small"
|
||||||
|
>{{i18n "discourse_ai.ai_persona.edit"}} </LinkTo>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { inject as service } from "@ember/service";
|
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
|
||||||
router: service(),
|
|
||||||
beforeModel() {
|
|
||||||
this.router.transitionTo("adminPlugins.discourse-ai.ai-personas");
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -2,6 +2,6 @@ import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
model() {
|
model() {
|
||||||
return this.modelFor("adminPlugins.discourse-ai.ai-personas");
|
return this.modelFor("adminPlugins.show.discourse-ai.ai-personas");
|
||||||
},
|
},
|
||||||
});
|
});
|
|
@ -1,10 +1,10 @@
|
||||||
|
import { AUTO_GROUPS } from "discourse/lib/constants";
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
async model() {
|
async model() {
|
||||||
const record = this.store.createRecord("ai-persona");
|
const record = this.store.createRecord("ai-persona");
|
||||||
// TL0
|
record.set("allowed_group_ids", [AUTO_GROUPS.trust_level_0.id]);
|
||||||
record.set("allowed_group_ids", [10]);
|
|
||||||
return record;
|
return record;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ export default DiscourseRoute.extend({
|
||||||
this._super(controller, model);
|
this._super(controller, model);
|
||||||
controller.set(
|
controller.set(
|
||||||
"allPersonas",
|
"allPersonas",
|
||||||
this.modelFor("adminPlugins.discourse-ai.ai-personas")
|
this.modelFor("adminPlugins.show.discourse-ai.ai-personas")
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
|
@ -2,7 +2,9 @@ import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
async model(params) {
|
async model(params) {
|
||||||
const allPersonas = this.modelFor("adminPlugins.discourse-ai.ai-personas");
|
const allPersonas = this.modelFor(
|
||||||
|
"adminPlugins.show.discourse-ai.ai-personas"
|
||||||
|
);
|
||||||
const id = parseInt(params.id, 10);
|
const id = parseInt(params.id, 10);
|
||||||
return allPersonas.findBy("id", id);
|
return allPersonas.findBy("id", id);
|
||||||
},
|
},
|
||||||
|
@ -11,7 +13,7 @@ export default DiscourseRoute.extend({
|
||||||
this._super(controller, model);
|
this._super(controller, model);
|
||||||
controller.set(
|
controller.set(
|
||||||
"allPersonas",
|
"allPersonas",
|
||||||
this.modelFor("adminPlugins.discourse-ai.ai-personas")
|
this.modelFor("adminPlugins.show.discourse-ai.ai-personas")
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default class DiscourseAiAiPersonasRoute extends DiscourseRoute {
|
||||||
async model() {
|
model() {
|
||||||
return this.store.findAll("ai-persona");
|
return this.store.findAll("ai-persona");
|
||||||
},
|
}
|
||||||
});
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
|
export default class DiscourseAiIndexRoute extends DiscourseRoute {
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
beforeModel() {
|
||||||
|
return this.router.transitionTo(
|
||||||
|
"adminPlugins.show.discourse-ai.ai-personas.index"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<AiPersonaListEditor @personas={{this.model}} />
|
|
@ -0,0 +1 @@
|
||||||
|
<AiPersonaListEditor @personas={{this.model}} />
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { PLUGIN_NAV_MODE_TOP } from "discourse/lib/admin-plugin-config-nav";
|
||||||
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "discourse-ai-admin-plugin-configuration-nav",
|
||||||
|
|
||||||
|
initialize(container) {
|
||||||
|
const currentUser = container.lookup("service:current-user");
|
||||||
|
if (!currentUser || !currentUser.admin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
withPluginApi("1.1.0", (api) => {
|
||||||
|
api.addAdminPluginConfigurationNav("discourse-ai", PLUGIN_NAV_MODE_TOP, [
|
||||||
|
{
|
||||||
|
label: "admin.plugins.change_settings_short",
|
||||||
|
route: "adminPlugins.show.settings",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "discourse_ai.ai_persona.short_title",
|
||||||
|
route: "adminPlugins.show.discourse-ai.ai-personas",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,10 +1,19 @@
|
||||||
|
.admin-contents .ai-persona-list-editor {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ai-persona-list-editor {
|
.ai-persona-list-editor {
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin: 0 0 1em 0;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__current {
|
&__current {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
.ai-persona-editor {
|
||||||
|
&__system_prompt,
|
||||||
|
&__description,
|
||||||
|
.select-kit.multi-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,7 +114,9 @@ en:
|
||||||
select_option: "Select an option..."
|
select_option: "Select an option..."
|
||||||
|
|
||||||
ai_persona:
|
ai_persona:
|
||||||
|
back: Back
|
||||||
name: Name
|
name: Name
|
||||||
|
edit: Edit
|
||||||
description: Description
|
description: Description
|
||||||
no_llm_selected: "No language model selected"
|
no_llm_selected: "No language model selected"
|
||||||
max_context_posts: "Max Context Posts"
|
max_context_posts: "Max Context Posts"
|
||||||
|
@ -129,12 +131,13 @@ en:
|
||||||
system_prompt: System Prompt
|
system_prompt: System Prompt
|
||||||
save: Save
|
save: Save
|
||||||
saved: AI Persona Saved
|
saved: AI Persona Saved
|
||||||
enabled: Enabled
|
enabled: "Enabled?"
|
||||||
commands: Enabled Commands
|
commands: Enabled Commands
|
||||||
allowed_groups: Allowed Groups
|
allowed_groups: Allowed Groups
|
||||||
confirm_delete: Are you sure you want to delete this persona?
|
confirm_delete: Are you sure you want to delete this persona?
|
||||||
new: New
|
new: "New Persona"
|
||||||
title: "AI Personas"
|
title: "AI Personas"
|
||||||
|
short_title: "Personas"
|
||||||
delete: Delete
|
delete: Delete
|
||||||
temperature: Temperature
|
temperature: Temperature
|
||||||
temperature_help: Temperature to use for the LLM, increase to increase creativity (leave empty to use model default, generally a value from 0.0 to 2.0)
|
temperature_help: Temperature to use for the LLM, increase to increase creativity (leave empty to use model default, generally a value from 0.0 to 2.0)
|
||||||
|
@ -143,16 +146,15 @@ en:
|
||||||
priority: Priority
|
priority: Priority
|
||||||
priority_help: Priority personas are displayed to users at the top of the persona list. If multiple personas have priority, they will be sorted alphabetically.
|
priority_help: Priority personas are displayed to users at the top of the persona list. If multiple personas have priority, they will be sorted alphabetically.
|
||||||
command_options: "Command Options"
|
command_options: "Command Options"
|
||||||
|
what_are_personas: "What are AI Personas?"
|
||||||
no_persona_selected: |
|
no_persona_selected: |
|
||||||
## What are AI Personas?
|
|
||||||
|
|
||||||
AI Personas are a powerful feature that allows you to customize the behavior of the AI engine in your Discourse forum. They act as a 'system message' that guides the AI's responses and interactions, helping to create a more personalized and engaging user experience.
|
AI Personas are a powerful feature that allows you to customize the behavior of the AI engine in your Discourse forum. They act as a 'system message' that guides the AI's responses and interactions, helping to create a more personalized and engaging user experience.
|
||||||
|
|
||||||
## Why use AI Personas?
|
#### Why use AI Personas?
|
||||||
|
|
||||||
With AI Personas, you can tailor the AI's behavior to better fit the context and tone of your forum. Whether you want the AI to be more formal for a professional setting, more casual for a community forum, or even embody a specific character for a role-playing game, AI Personas give you the flexibility to do so.
|
With AI Personas, you can tailor the AI's behavior to better fit the context and tone of your forum. Whether you want the AI to be more formal for a professional setting, more casual for a community forum, or even embody a specific character for a role-playing game, AI Personas give you the flexibility to do so.
|
||||||
|
|
||||||
## Group-Specific Access to AI Personas
|
#### Group-Specific Access to AI Personas
|
||||||
|
|
||||||
Moreover, you can set it up so that certain user groups have access to specific personas. This means you can have different AI behaviors for different sections of your forum, further enhancing the diversity and richness of your community's interactions.
|
Moreover, you can set it up so that certain user groups have access to specific personas. This means you can have different AI behaviors for different sections of your forum, further enhancing the diversity and richness of your community's interactions.
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,6 @@ Discourse::Application.routes.draw do
|
||||||
:constraints => StaffConstraint.new
|
:constraints => StaffConstraint.new
|
||||||
|
|
||||||
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
|
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
|
||||||
get "/", to: redirect("/admin/plugins/discourse-ai/ai-personas")
|
|
||||||
|
|
||||||
resources :ai_personas,
|
resources :ai_personas,
|
||||||
only: %i[index create show update destroy],
|
only: %i[index create show update destroy],
|
||||||
path: "ai-personas",
|
path: "ai-personas",
|
||||||
|
|
|
@ -17,6 +17,7 @@ register_asset "stylesheets/modules/ai-helper/common/ai-helper.scss"
|
||||||
|
|
||||||
register_asset "stylesheets/modules/ai-bot/common/bot-replies.scss"
|
register_asset "stylesheets/modules/ai-bot/common/bot-replies.scss"
|
||||||
register_asset "stylesheets/modules/ai-bot/common/ai-persona.scss"
|
register_asset "stylesheets/modules/ai-bot/common/ai-persona.scss"
|
||||||
|
register_asset "stylesheets/modules/ai-bot/mobile/ai-persona.scss", :mobile
|
||||||
|
|
||||||
register_asset "stylesheets/modules/embeddings/common/semantic-related-topics.scss"
|
register_asset "stylesheets/modules/embeddings/common/semantic-related-topics.scss"
|
||||||
register_asset "stylesheets/modules/embeddings/common/semantic-search.scss"
|
register_asset "stylesheets/modules/embeddings/common/semantic-search.scss"
|
||||||
|
@ -38,7 +39,7 @@ after_initialize do
|
||||||
require_relative "discourse_automation/llm_triage"
|
require_relative "discourse_automation/llm_triage"
|
||||||
require_relative "discourse_automation/llm_report"
|
require_relative "discourse_automation/llm_report"
|
||||||
|
|
||||||
add_admin_route "discourse_ai.title", "discourse-ai"
|
add_admin_route("discourse_ai.title", "discourse-ai", { use_new_show_route: true })
|
||||||
|
|
||||||
[
|
[
|
||||||
DiscourseAi::Embeddings::EntryPoint.new,
|
DiscourseAi::Embeddings::EntryPoint.new,
|
||||||
|
|
Loading…
Reference in New Issue