Sam 52a7dd2a4b
FEATURE: optional tool detail blocks (#662)
This is a rather huge refactor with 1 new feature (tool details can
be suppressed)

Previously we use the name "Command" to describe "Tools", this unifies
all the internal language and simplifies the code.

We also amended the persona UI to use less DToggles which aligns
with our design guidelines.

Co-authored-by: Martin Brennan <martin@discourse.org>
2024-06-11 18:14:14 +10:00

152 lines
3.6 KiB
JavaScript

import { tracked } from "@glimmer/tracking";
import { ajax } from "discourse/lib/ajax";
import RestModel from "discourse/models/rest";
const CREATE_ATTRIBUTES = [
"id",
"name",
"description",
"tools",
"system_prompt",
"allowed_group_ids",
"enabled",
"system",
"priority",
"top_p",
"temperature",
"user_id",
"mentionable",
"default_llm",
"user",
"max_context_posts",
"vision_enabled",
"vision_max_pixels",
"rag_uploads",
"rag_chunk_tokens",
"rag_chunk_overlap_tokens",
"rag_conversation_chunks",
"question_consolidator_llm",
"allow_chat",
"tool_details",
];
const SYSTEM_ATTRIBUTES = [
"id",
"allowed_group_ids",
"enabled",
"system",
"priority",
"user_id",
"mentionable",
"default_llm",
"user",
"max_context_posts",
"vision_enabled",
"vision_max_pixels",
"rag_uploads",
"rag_chunk_tokens",
"rag_chunk_overlap_tokens",
"rag_conversation_chunks",
"question_consolidator_llm",
"allow_chat",
"tool_details",
];
class ToolOption {
@tracked value = null;
}
export default class AiPersona extends RestModel {
// this code is here to convert the wire schema to easier to work with object
// on the wire we pass in/out tools as an Array.
// [[ToolName, {option1: value, option2: value}], ToolName2, ToolName3]
// So we rework this into a "tools" property and nested toolOptions
init(properties) {
if (properties.tools) {
properties.tools = properties.tools.map((tool) => {
if (typeof tool === "string") {
return tool;
} else {
let [toolId, options] = tool;
for (let optionId in options) {
if (!options.hasOwnProperty(optionId)) {
continue;
}
this.getToolOption(toolId, optionId).value = options[optionId];
}
return toolId;
}
});
}
super.init(properties);
this.tools = properties.tools;
}
async createUser() {
const result = await ajax(
`/admin/plugins/discourse-ai/ai-personas/${this.id}/create-user.json`,
{
type: "POST",
}
);
this.user = result.user;
this.user_id = this.user.id;
return this.user;
}
getToolOption(toolId, optionId) {
this.toolOptions ||= {};
this.toolOptions[toolId] ||= {};
return (this.toolOptions[toolId][optionId] ||= new ToolOption());
}
populateToolOptions(attrs) {
if (!attrs.tools) {
return;
}
let toolsWithOptions = [];
attrs.tools.forEach((toolId) => {
if (typeof toolId !== "string") {
toolId = toolId[0];
}
if (this.toolOptions && this.toolOptions[toolId]) {
let options = this.toolOptions[toolId];
let optionsWithValues = {};
for (let optionId in options) {
if (!options.hasOwnProperty(optionId)) {
continue;
}
let option = options[optionId];
optionsWithValues[optionId] = option.value;
}
toolsWithOptions.push([toolId, optionsWithValues]);
} else {
toolsWithOptions.push(toolId);
}
});
attrs.tools = toolsWithOptions;
}
updateProperties() {
let attrs = this.system
? this.getProperties(SYSTEM_ATTRIBUTES)
: this.getProperties(CREATE_ATTRIBUTES);
attrs.id = this.id;
this.populateToolOptions(attrs);
return attrs;
}
createProperties() {
let attrs = this.getProperties(CREATE_ATTRIBUTES);
this.populateToolOptions(attrs);
return attrs;
}
workingCopy() {
let attrs = this.getProperties(CREATE_ATTRIBUTES);
this.populateToolOptions(attrs);
return AiPersona.create(attrs);
}
}