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 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>
|
<title>Horde Chat</title>
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ export async function getChats() {
|
|||||||
if (response && response.chats) {
|
if (response && response.chats) {
|
||||||
cache.chats = response.chats.map(msg => new ChatInfo({
|
cache.chats = response.chats.map(msg => new ChatInfo({
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
name: msg.name?.length > 0 ? msg.name : `[Chat ${msg.id}]`,
|
name: msg.name || `<chat-${msg.id}>`,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Invalid response for get-chats: " + JSON.stringify(response));
|
throw new Error("Invalid response for get-chats: " + JSON.stringify(response));
|
||||||
@@ -254,7 +254,7 @@ export async function getChats() {
|
|||||||
|
|
||||||
export async function getChat(chatId) {
|
export async function getChat(chatId) {
|
||||||
const allChats = await getChats();
|
const allChats = await getChats();
|
||||||
return allChats.find(chat => chat.id === chatId) || null;
|
return allChats.find(it => it.id == chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAgents() {
|
export async function getAgents() {
|
||||||
@@ -263,7 +263,11 @@ export async function getAgents() {
|
|||||||
if (response && response.agents) {
|
if (response && response.agents) {
|
||||||
cache.agents = response.agents.map(msg => ({
|
cache.agents = response.agents.map(msg => ({
|
||||||
id: msg.id,
|
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 {
|
} else {
|
||||||
throw new Error("Invalid response for get-agents: " + JSON.stringify(response));
|
throw new Error("Invalid response for get-agents: " + JSON.stringify(response));
|
||||||
@@ -272,6 +276,11 @@ export async function getAgents() {
|
|||||||
return cache.agents;
|
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 } = {}) {
|
export function invalidateCache({ chats=false, agents=false } = {}) {
|
||||||
if (chats) cache.chats = null;
|
if (chats) cache.chats = null;
|
||||||
if (agents) cache.agents = null;
|
if (agents) cache.agents = null;
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ const router = createRouter({
|
|||||||
path: `/chat/create`,
|
path: `/chat/create`,
|
||||||
component: () => import('../views/ChatCreate.vue')
|
component: () => import('../views/ChatCreate.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'agent-create',
|
||||||
|
path: `/agent/create`,
|
||||||
|
component: () => import('../views/AgentCreate.vue')
|
||||||
|
},
|
||||||
objectView('chat', () => import('../views/Chat.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
|
// This won't fire when called, the mounted callback will take care
|
||||||
watch(() => props.id, newChatId => {
|
watch(() => props.id, newId => {
|
||||||
if (_isMounted) {
|
if (_isMounted) {
|
||||||
unloadChatState();
|
unloadChatState();
|
||||||
loadChatState(newChatId);
|
loadChatState(newId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
|
|
||||||
// === UI Helper Functions === //
|
// === UI Helper Functions === //
|
||||||
|
|
||||||
function requestRescroll ({forceBottom=false, bottomRelative=false}) {
|
function requestRescroll ({forceBottom=false, bottomRelative=false}={}) {
|
||||||
const elem = chatContainerElem.value;
|
const elem = chatContainerElem.value;
|
||||||
|
|
||||||
const scroll = elem.scrollTop;
|
const scroll = elem.scrollTop;
|
||||||
@@ -166,13 +166,14 @@
|
|||||||
// Chat was fully scrolled down
|
// Chat was fully scrolled down
|
||||||
if (forceBottom || scroll == max) {
|
if (forceBottom || scroll == max) {
|
||||||
elem.scrollTop = elem.scrollTopMax;
|
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 {
|
} else {
|
||||||
// Do nothing, scroll remains in place
|
// Do nothing, scroll remains in place
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Something different has to be done when previous messages are added
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function msgClass (msg) {
|
function msgClass (msg) {
|
||||||
|
|||||||
@@ -8,10 +8,7 @@
|
|||||||
import * as ui from '../ui';
|
import * as ui from '../ui';
|
||||||
|
|
||||||
// List of agents, with their id, name, and whether they're currently selected
|
// List of agents, with their id, name, and whether they're currently selected
|
||||||
const agents = ref([
|
const agents = ref([]);
|
||||||
{id: 1, name: "John"},
|
|
||||||
{id: 2, name: "Steve"},
|
|
||||||
]);
|
|
||||||
const title = ref("");
|
const title = ref("");
|
||||||
|
|
||||||
async function submitChat () {
|
async function submitChat () {
|
||||||
@@ -31,15 +28,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Initialize === //
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
agents.value = (await backend.getAgents()).map(it => ({
|
agents.value = (await backend.getAgents()).map(it => ({
|
||||||
id: it.id,
|
id: it.id,
|
||||||
name: it.name,
|
name: it.name,
|
||||||
selected: false,
|
selected: false,
|
||||||
}));
|
}));
|
||||||
// TODO: Get agent list
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user