Agent creation and editing support via UI
This commit is contained in:
1 parent
f8edd01c37
commit
0f7384cc1d
7 files changed
+267
-15
No files matched your search
@@ -7,6 +7,10 @@
|
||||
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||
|
||||
<!--link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.6.0/css/fontawesome.min.css" integrity="sha384-NvKbDTEnL+A8F/AA5Tc5kmMLSJHUO868P+lDtTpJIeQdGYaUIuLr4lVGOEA1OcMy" crossorigin="anonymous"-->
|
||||
|
||||
<!-- Checkout https://icons.getbootstrap.com, may be a good alternative -->
|
||||
|
||||
<title>Horde Chat</title>
|
||||
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
||||
@@ -243,7 +243,7 @@ export async function getChats() {
|
||||
if (response && response.chats) {
|
||||
cache.chats = response.chats.map(msg => new ChatInfo({
|
||||
id: msg.id,
|
||||
name: msg.name?.length > 0 ? msg.name : `[Chat ${msg.id}]`,
|
||||
name: msg.name || `<chat-${msg.id}>`,
|
||||
}));
|
||||
} else {
|
||||
throw new Error("Invalid response for get-chats: " + JSON.stringify(response));
|
||||
@@ -254,7 +254,7 @@ export async function getChats() {
|
||||
|
||||
export async function getChat(chatId) {
|
||||
const allChats = await getChats();
|
||||
return allChats.find(chat => chat.id === chatId) || null;
|
||||
return allChats.find(it => it.id == chatId);
|
||||
}
|
||||
|
||||
export async function getAgents() {
|
||||
@@ -263,7 +263,11 @@ export async function getAgents() {
|
||||
if (response && response.agents) {
|
||||
cache.agents = response.agents.map(msg => ({
|
||||
id: msg.id,
|
||||
name: msg.name?.length > 0 ? msg.name : `<agent-${msg.id}>`,
|
||||
name: msg.name || `<agent-${msg.id}>`,
|
||||
agentname: msg.agentname,
|
||||
description: msg.description,
|
||||
picture: msg.picture,
|
||||
kind: msg.kind,
|
||||
}));
|
||||
} else {
|
||||
throw new Error("Invalid response for get-agents: " + JSON.stringify(response));
|
||||
@@ -272,6 +276,11 @@ export async function getAgents() {
|
||||
return cache.agents;
|
||||
}
|
||||
|
||||
export async function getAgent(agentId) {
|
||||
const allAgents = await getAgents();
|
||||
return allAgents.find(it => it.id == agentId);
|
||||
}
|
||||
|
||||
export function invalidateCache({ chats=false, agents=false } = {}) {
|
||||
if (chats) cache.chats = null;
|
||||
if (agents) cache.agents = null;
|
||||
|
||||
@@ -33,7 +33,13 @@ const router = createRouter({
|
||||
path: `/chat/create`,
|
||||
component: () => import('../views/ChatCreate.vue')
|
||||
},
|
||||
{
|
||||
name: 'agent-create',
|
||||
path: `/agent/create`,
|
||||
component: () => import('../views/AgentCreate.vue')
|
||||
},
|
||||
objectView('chat', () => import('../views/Chat.vue')),
|
||||
objectView('agent', () => import('../views/Agent.vue')),
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<script setup>
|
||||
import {
|
||||
ref, watch,
|
||||
onMounted, onUnmounted,
|
||||
} from "vue";
|
||||
|
||||
import * as backend from '../backend';
|
||||
import * as ui from '../ui';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const agent = ref({});
|
||||
const kindInfo = ref({});
|
||||
const configFields = ref([]);
|
||||
|
||||
|
||||
// === State Management === //
|
||||
|
||||
async function loadAgentData (agentId) {
|
||||
const _agent = await backend.getAgent(agentId);
|
||||
if (!_agent) {
|
||||
ui.alert("Agent not found");
|
||||
return;
|
||||
}
|
||||
|
||||
agent.value = {
|
||||
id: _agent.id,
|
||||
name: _agent.name,
|
||||
agentname: _agent.agentname,
|
||||
description: _agent.description,
|
||||
picture: _agent.picture,
|
||||
};
|
||||
|
||||
backend.sendAction("get-agent-kind-info", {kind: _agent.kind}).then(res => {
|
||||
kindInfo.description = res.info.description;
|
||||
});
|
||||
|
||||
backend.sendAction('get-agent-config-fields', {agentId}).then(res => {
|
||||
configFields.value = res.fields.map(field => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
value: field.value,
|
||||
default: null,
|
||||
enabled: field.value != null,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// This won't fire when immediately, the mounted callback will take care
|
||||
watch(() => props.id, newId => {
|
||||
loadAgentData(newId);
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
loadAgentData(props.id);
|
||||
});
|
||||
|
||||
function updateIdentifier() {
|
||||
const value = prompt(`Enter a new identifier for ${agent.value.name}`);
|
||||
if (value) {
|
||||
backend.sendAction("update-agent", {
|
||||
agentId: agent.value.id,
|
||||
agentname: value,
|
||||
}).then(res => {
|
||||
ui.alert(res.error ?
|
||||
'Error updating agent identifier' :
|
||||
'Agent identifier updated'
|
||||
);
|
||||
if (!res.error) {
|
||||
agent.value.agentname = value;
|
||||
backend.invalidateCache({agents: true});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.alert("Cancelled identifier update");
|
||||
}
|
||||
}
|
||||
|
||||
function updateName() {
|
||||
const value = prompt(`Enter a new name for ${agent.value.name}`);
|
||||
if (value) {
|
||||
backend.sendAction("update-agent", {
|
||||
agentId: agent.value.id,
|
||||
agentname: value,
|
||||
}).then(res => {
|
||||
ui.alert(res.error ?
|
||||
'Error updating agent name' :
|
||||
'Agent name updated'
|
||||
);
|
||||
if (!res.error) {
|
||||
agent.value.name = value;
|
||||
backend.invalidateCache({agents: true});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.alert("Cancelled identifier update");
|
||||
}
|
||||
}
|
||||
async function saveChanges() {
|
||||
const dataObject = {};
|
||||
for (let field of configFields.value) {
|
||||
if (field.enabled) {
|
||||
dataObject[field.name] = field.value;
|
||||
}
|
||||
}
|
||||
|
||||
let res = await backend.sendAction("update-agent", {
|
||||
agentId: agent.value.id,
|
||||
data: dataObject,
|
||||
});
|
||||
|
||||
ui.alert(res.error ?
|
||||
'Error updating agent' :
|
||||
'Agent updated'
|
||||
);
|
||||
|
||||
if (!res.error) {
|
||||
backend.invalidateCache({agents: true});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>{{ agent.name || `(Agent #${id})` }}</h1>
|
||||
<section>
|
||||
<p>{{ description }}</p>
|
||||
<p>Agent ID: {{ id }}, Identifier: {{ agent.agentname }}</p>
|
||||
|
||||
<div>
|
||||
<button @click="updateIdentifier">Update Identifier</button>
|
||||
<button @click="updateName">Update Name</button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="saveChanges">
|
||||
<div v-for="(configField, index) in configFields" :key="index">
|
||||
<label>{{ configField.name }}</label>
|
||||
<input type="checkbox" v-model="configField.enabled" />
|
||||
<textarea v-model="configField.value" />
|
||||
</div>
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script setup>
|
||||
import {
|
||||
ref, computed, reactive,
|
||||
onMounted, onUnmounted,
|
||||
} from "vue";
|
||||
|
||||
import * as backend from '../backend';
|
||||
import * as ui from '../ui';
|
||||
|
||||
// List of agent class names
|
||||
const kinds = ref([]);
|
||||
const agent = ref({
|
||||
name: "",
|
||||
identifier: "",
|
||||
kind: "",
|
||||
});
|
||||
|
||||
async function submitAgent () {
|
||||
const payload = {
|
||||
'name': agent.value.name,
|
||||
'kind': agent.value.kind,
|
||||
};
|
||||
|
||||
if (!payload.kind) {
|
||||
ui.alert("You must select a kind");
|
||||
return;
|
||||
}
|
||||
|
||||
if (agent.value.identifier) {
|
||||
payload.identifier = agent.value.identifier;
|
||||
}
|
||||
|
||||
let response = await backend.sendAction("create-agent", payload);
|
||||
|
||||
if (response.error) {
|
||||
ui.alert(`Could not create agent\n${response.error}`);
|
||||
} else {
|
||||
ui.alert(`Created Agent "${payload.name}"`);
|
||||
backend.invalidateCache({agents: true});
|
||||
ui.goto(`/agent/${response.agentId}`);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
kinds.value = (await backend.sendAction("get-agent-kinds")).kinds;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>Create New Chat</h1>
|
||||
|
||||
<div class="main-container">
|
||||
<input v-model="agent.name" type="text" placeholder="name">
|
||||
<input v-model="agent.identifier" type="text" placeholder="identifier">
|
||||
<select v-model="agent.kind">
|
||||
<option v-for="k in kinds" :value="k">{{k}}</option>
|
||||
</select>
|
||||
|
||||
<button @click="submitAgent()">Create Agent</button>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.multi-select-box {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.multi-select-box div {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.multi-select-box div:last {
|
||||
border-bottom: none;
|
||||
}
|
||||
.multi-select-box div:hover {
|
||||
background-color: var(--color-background-mute);
|
||||
}
|
||||
.multi-select-box div.selected {
|
||||
background-color: var(--color-background-soft);
|
||||
}
|
||||
</style>
|
||||
@@ -66,10 +66,10 @@
|
||||
}
|
||||
|
||||
// This won't fire when called, the mounted callback will take care
|
||||
watch(() => props.id, newChatId => {
|
||||
watch(() => props.id, newId => {
|
||||
if (_isMounted) {
|
||||
unloadChatState();
|
||||
loadChatState(newChatId);
|
||||
loadChatState(newId);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
|
||||
// === UI Helper Functions === //
|
||||
|
||||
function requestRescroll ({forceBottom=false, bottomRelative=false}) {
|
||||
function requestRescroll ({forceBottom=false, bottomRelative=false}={}) {
|
||||
const elem = chatContainerElem.value;
|
||||
|
||||
const scroll = elem.scrollTop;
|
||||
@@ -166,13 +166,14 @@
|
||||
// Chat was fully scrolled down
|
||||
if (forceBottom || scroll == max) {
|
||||
elem.scrollTop = elem.scrollTopMax;
|
||||
} else if (bottomRelative) {
|
||||
// TODO: Keep scroll at the same distance from the bottom.
|
||||
// This will be used when the user scrolls to the top and previous messages are loaded
|
||||
} else {
|
||||
// Do nothing, scroll remains in place
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Something different has to be done when previous messages are added
|
||||
}
|
||||
|
||||
function msgClass (msg) {
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
import * as ui from '../ui';
|
||||
|
||||
// List of agents, with their id, name, and whether they're currently selected
|
||||
const agents = ref([
|
||||
{id: 1, name: "John"},
|
||||
{id: 2, name: "Steve"},
|
||||
]);
|
||||
const agents = ref([]);
|
||||
const title = ref("");
|
||||
|
||||
async function submitChat () {
|
||||
@@ -31,15 +28,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// === Initialize === //
|
||||
|
||||
onMounted(async () => {
|
||||
agents.value = (await backend.getAgents()).map(it => ({
|
||||
id: it.id,
|
||||
name: it.name,
|
||||
selected: false,
|
||||
}));
|
||||
// TODO: Get agent list
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in new issue
Block a user