From c5473bc2667784f493ba76f01e28925e5f23ddc5 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 31 Jan 2025 11:34:14 -0600 Subject: [PATCH 1/4] refine multi-select --- src/lib/common/MultiSelect.svelte | 65 +++++++++------ src/lib/helpers/http.js | 1 + src/lib/helpers/types/agentTypes.js | 1 + src/lib/services/agent-service.js | 22 +++-- src/lib/services/api-endpoints.js | 1 + src/routes/page/agent/+page.svelte | 82 +++++++++++++++---- src/routes/page/agent/router/+page.svelte | 11 +-- .../page/agent/router/routing-flow.svelte | 31 ++++--- 8 files changed, 143 insertions(+), 71 deletions(-) diff --git a/src/lib/common/MultiSelect.svelte b/src/lib/common/MultiSelect.svelte index d324e2f8..7926a80e 100644 --- a/src/lib/common/MultiSelect.svelte +++ b/src/lib/common/MultiSelect.svelte @@ -23,6 +23,9 @@ /** @type {string} */ export let selectedText = ''; + /** @type {string[]} */ + export let selectedKeys; + /** @type {string} */ export let containerClasses = ""; @@ -78,6 +81,43 @@ }); }); + $: { + innerOptions = innerOptions.map(x => { + x.checked = !!selectedKeys?.includes(x.key); + return {...x}; + }); + refOptions = refOptions.map(x => { + x.checked = !!selectedKeys?.includes(x.key); + return {...x}; + }); + changeDisplayText(); + } + + $: { + if (options.length > refOptions.length) { + const curKeys = refOptions.map(x => x.key); + const newOptions = options.filter(x => !curKeys.includes(x.key)).map(x => { + return { + key: x.key, + value: x.value, + checked: false + }; + }); + + innerOptions = [ + ...innerOptions, + ...newOptions + ]; + + refOptions = [ + ...refOptions, + ...newOptions + ]; + + changeDisplayText(); + } + } + async function toggleOptionList() { showOptionList = !showOptionList; @@ -226,31 +266,6 @@ } } } - - $: { - if (options.length > refOptions.length) { - const curKeys = refOptions.map(x => x.key); - const newOptions = options.filter(x => !curKeys.includes(x.key)).map(x => { - return { - key: x.key, - value: x.value, - checked: false - }; - }); - - innerOptions = [ - ...innerOptions, - ...newOptions - ]; - - refOptions = [ - ...refOptions, - ...newOptions - ]; - - changeDisplayText(); - } - } diff --git a/src/lib/helpers/http.js b/src/lib/helpers/http.js index d86e1e8a..06f52516 100644 --- a/src/lib/helpers/http.js +++ b/src/lib/helpers/http.js @@ -89,6 +89,7 @@ function skipLoader(config) { new RegExp('http(s*)://(.*?)/role/options', 'g'), new RegExp('http(s*)://(.*?)/role/(.*?)/details', 'g'), new RegExp('http(s*)://(.*?)/user/(.*?)/details', 'g'), + new RegExp('http(s*)://(.*?)/agent/labels', 'g'), ]; if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) { diff --git a/src/lib/helpers/types/agentTypes.js b/src/lib/helpers/types/agentTypes.js index 61912e5a..e540b47d 100644 --- a/src/lib/helpers/types/agentTypes.js +++ b/src/lib/helpers/types/agentTypes.js @@ -30,6 +30,7 @@ * @property {string[]?} [types] * @property {string[]?} [agentNames] * @property {string} [similarName] + * @property {string[]?} [labels] * @property {boolean} [isPublic] * @property {boolean} [disabled] * @property {string[]?} [agentIds] diff --git a/src/lib/services/agent-service.js b/src/lib/services/agent-service.js index af3d4b74..baebdde7 100644 --- a/src/lib/services/agent-service.js +++ b/src/lib/services/agent-service.js @@ -19,15 +19,9 @@ export async function getSettings() { */ export async function getAgents(filter, checkAuth = false) { let url = endpoints.agentListUrl; - const response = await axios.get(url, { - params: { - ...filter, - checkAuth : checkAuth - }, - paramsSerializer: { - dots: true, - indexes: null, - } + const response = await axios.post(url, { + filter: filter, + checkAuth : checkAuth }); return response.data; } @@ -100,4 +94,14 @@ export async function getAgentRuleOptions() { const url = endpoints.agentRuleOptionsUrl; const response = await axios.get(url); return response.data; +} + +/** + * Get agent labels + * @returns {Promise} + */ +export async function getAgentLabels() { + const url = endpoints.agentLabelsUrl; + const response = await axios.get(url); + return response.data; } \ No newline at end of file diff --git a/src/lib/services/api-endpoints.js b/src/lib/services/api-endpoints.js index af872143..4befedde 100644 --- a/src/lib/services/api-endpoints.js +++ b/src/lib/services/api-endpoints.js @@ -35,6 +35,7 @@ export const endpoints = { agentCreateUrl: `${host}/agent`, agentUtilityOptionsUrl: `${host}/agent/utility/options`, agentRuleOptionsUrl: `${host}/rule/triggers`, + agentLabelsUrl: `${host}/agent/labels`, // agent task agentTaskListUrl: `${host}/agent/tasks`, diff --git a/src/routes/page/agent/+page.svelte b/src/routes/page/agent/+page.svelte index 03a6ef43..97ff5437 100644 --- a/src/routes/page/agent/+page.svelte +++ b/src/routes/page/agent/+page.svelte @@ -5,7 +5,7 @@ import HeadTitle from '$lib/common/HeadTitle.svelte'; import CardAgent from './card-agent.svelte'; import LoadingToComplete from '$lib/common/LoadingToComplete.svelte'; - import { createAgent, getAgents } from '$lib/services/agent-service.js'; + import { createAgent, getAgentLabels, getAgents } from '$lib/services/agent-service.js'; import { myInfo } from '$lib/services/auth-service'; import PlainPagination from '$lib/common/PlainPagination.svelte'; import { _ } from 'svelte-i18n' @@ -43,16 +43,22 @@ /** @type {any} */ let unsubscriber; - let agentTypeOptions = Object.entries(AgentType).map(([k, v]) => ( - { key: k, value: v } + const agentTypeOptions = Object.entries(AgentType).map(([k, v]) => ( + { key: v, value: v } )); + /** @type {{ key: string, value: string }[]} */ + let agentLabelOptions = []; + /** @type {string[]} */ let selectedAgentTypes = []; + /** @type {string[]} */ + let selectedAgentLabels = []; onMount(async () => { user = await myInfo(); getPagedAgents(); + getAgentLabelOptions(); unsubscriber = globalEventStore.subscribe((/** @type {import('$commonTypes').GlobalEvent} */ event) => { if (event.name !== GlobalEvent.Search) return; @@ -82,6 +88,14 @@ }); } + function getAgentLabelOptions() { + return getAgentLabels().then(res => { + agentLabelOptions = res?.map(x => ({ key: x, value: x })) || []; + }).catch(() => { + agentLabelOptions = []; + }); + } + function createNewAgent() { // @ts-ignore Swal.fire({ @@ -147,23 +161,44 @@ getPagedAgents(); } - - /** - * @param {any} e - */ + /** @param {any} e */ function selectAgentTypeOption(e) { // @ts-ignore - selectedAgentTypes = e.detail.selecteds?.map(x => x.value) || []; + selectedAgentTypes = e.detail.selecteds?.map(x => x.key) || []; + } + + /** @param {any} e */ + function selectAgentLabelOption(e) { + // @ts-ignore + selectedAgentLabels = e.detail.selecteds?.map(x => x.key) || []; + } + + function search() { + refreshFilter(); + initFilterPager(); + getPagedAgents(); + } + + function reset() { + selectedAgentTypes = []; + selectedAgentLabels = []; } - function searchAgents() { + function refreshFilter() { filter = { ...filter, types: selectedAgentTypes?.length > 0 ? selectedAgentTypes : null, + labels: selectedAgentLabels?.length > 0 ? selectedAgentLabels : null, pager: initFilter.pager }; - getPagedAgents(); + } + + function initFilterPager() { + filter = { + ...filter, + pager: { page: firstPage, size: pageSize, count: 0 }, + }; } @@ -182,24 +217,39 @@
{}} + searchMode + selectedKeys={selectedAgentLabels} + options={agentLabelOptions} + on:select={e => selectAgentLabelOption(e)} /> selectAgentTypeOption(e)} /> +
diff --git a/src/routes/page/agent/router/+page.svelte b/src/routes/page/agent/router/+page.svelte index 081c6d23..ac1fb495 100644 --- a/src/routes/page/agent/router/+page.svelte +++ b/src/routes/page/agent/router/+page.svelte @@ -6,6 +6,7 @@ import Breadcrumb from '$lib/common/Breadcrumb.svelte'; import HeadTitle from '$lib/common/HeadTitle.svelte'; import { getAgents } from '$lib/services/agent-service.js'; + import { AgentType } from '$lib/helpers/enums'; import RoutingFlow from './routing-flow.svelte' const params = $page.url.searchParams; @@ -22,7 +23,7 @@ const filter = { pager: { page: 1, size: 10, count: 0 }, disabled: false, - types: ["routing"] + types: [AgentType.Routing] }; onMount(async () => { @@ -31,9 +32,9 @@ }); async function getRouter() { - // if (!!targetAgentId) { - // filter.agentIds = [targetAgentId]; - // } + if (!!targetAgentId) { + filter.agentIds = [targetAgentId]; + } const response = await getAgents(filter); if (response.items?.length > 0) { routers = response.items; @@ -66,7 +67,7 @@ handleUserNodeSelected()} on:routerNodeSelected={(e) => handleRouterNodeSelected(e.detail.agent)} on:agentNodeSelected={(e) => handleAgentNodeSelected(e.detail.agent)}/> diff --git a/src/routes/page/agent/router/routing-flow.svelte b/src/routes/page/agent/router/routing-flow.svelte index 1e259078..cdb47f86 100644 --- a/src/routes/page/agent/router/routing-flow.svelte +++ b/src/routes/page/agent/router/routing-flow.svelte @@ -28,20 +28,20 @@ /** @type {import('$agentTypes').AgentModel[]} */ export let routers; - /** @type {string?} */ - export let targetAgentId = ''; + /** @type {boolean} */ + export let singleMode = false; /** @type {Drawflow} */ let editor; const dispatch = createEventDispatcher(); $: { - disabled = !targetAgentId; + // disabled = !!targetAgentId; } onMount(async () => { - const response = await getAgents(filter); - agents = response?.items || []; + // const response = await getAgents(filter); + // agents = response?.items || []; const container = document.getElementById("drawflow"); if (container) { @@ -49,13 +49,13 @@ editor.reroute = true; editor.reroute_fix_curvature = true; editor.start(); - editor.on('nodeCreated', function(id) { + editor.on('nodeCreated', id => { let node = editor.getNodeFromId(id); node.data.nid = id; console.log(`Node created ${id} ${node.data.agent}`); agentNodes.push(node.data); }); - editor.on('nodeSelected', function(id) { + editor.on('nodeSelected', id => { console.log("Node selected " + id); // emit event }); @@ -130,13 +130,12 @@ id: agent.id, agent: agent.name }; - let nid = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); - const routers = agentNodes.filter(x => x.type === 'routing' && x.profiles?.includes('planning') && getPlannerName(x) === agent.name); + const nid = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); + const routers = agentNodes.filter(x => x.type === AgentType.Routing && x.profiles?.includes('planning') && getPlannerName(x) === agent.name); routers.forEach(r => { editor.addConnection(r.nid, nid, `output_1`, `input_1`); }); - posY += nodeSpaceY; }); @@ -146,13 +145,13 @@ let profiles = []; const chatTestLinkHtml = agent.is_public ? - `` : - ''; + `` : ''; let html = `${agent.name}${chatTestLinkHtml}`; - if (agent.type == "static") { + if (agent.type == AgentType.Static) { const taskLinkHtml = ``; html += taskLinkHtml; } + if (agent.profiles.length > 0) { profiles = agent.profiles; html += `
` + profiles.join(', '); @@ -167,7 +166,7 @@ agent: agent.name, profiles: profiles }; - let nid = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); + const nid = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); // connect by profile if (profiles.length > 0) { @@ -175,7 +174,7 @@ profiles.forEach((/** @type {string} */ profile) => { if (profile == 'planning') return; - agentNodes.filter(ag => ag.type == "routing").forEach(r => { + agentNodes.filter(ag => ag.type == AgentType.Routing).forEach(r => { if (r.profiles.find((/** @type {string} */ p) => p == profile)) { editor.addConnection(r.nid, nid, `output_1`, `input_1`); } else { @@ -254,7 +253,7 @@ types.push(AgentType.Task); } if (includeStaticAgent) { - types.push(AgentType.Static) + types.push(AgentType.Static); } return types; } From dcbd3b004311f1a7db84208879d25ef95f94994d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 31 Jan 2025 14:28:39 -0600 Subject: [PATCH 2/4] refine routing flow --- src/routes/page/agent/router/+page.svelte | 2 +- .../page/agent/router/routing-flow.svelte | 171 +++++++++++------- 2 files changed, 108 insertions(+), 65 deletions(-) diff --git a/src/routes/page/agent/router/+page.svelte b/src/routes/page/agent/router/+page.svelte index ac1fb495..f6873945 100644 --- a/src/routes/page/agent/router/+page.svelte +++ b/src/routes/page/agent/router/+page.svelte @@ -67,7 +67,7 @@ handleUserNodeSelected()} on:routerNodeSelected={(e) => handleRouterNodeSelected(e.detail.agent)} on:agentNodeSelected={(e) => handleAgentNodeSelected(e.detail.agent)}/> diff --git a/src/routes/page/agent/router/routing-flow.svelte b/src/routes/page/agent/router/routing-flow.svelte index cdb47f86..fa003db5 100644 --- a/src/routes/page/agent/router/routing-flow.svelte +++ b/src/routes/page/agent/router/routing-flow.svelte @@ -10,7 +10,6 @@ let includePlannerAgent = false; let includeTaskAgent = false; let includeStaticAgent = false; - let disabled = false; /** @type {any[]} */ let agents = []; @@ -20,28 +19,26 @@ /** @type {import('$agentTypes').AgentFilter} */ const filter = { - pager: { page: 1, size: 20, count: 0 }, + pager: { page: 1, size: 100, count: 0 }, disabled: false, - types: includeTaskAgent ? [AgentType.Task] : ["none"] + types: [AgentType.Planning, AgentType.Task] }; /** @type {import('$agentTypes').AgentModel[]} */ export let routers; /** @type {boolean} */ - export let singleMode = false; + export let viewMode = false; /** @type {Drawflow} */ let editor; const dispatch = createEventDispatcher(); - - $: { - // disabled = !!targetAgentId; - } onMount(async () => { - // const response = await getAgents(filter); - // agents = response?.items || []; + if (viewMode) { + const response = await getAgents(filter); + agents = response?.items || []; + } const container = document.getElementById("drawflow"); if (container) { @@ -74,7 +71,7 @@ nodeSpaceY * (agents.length + 1) / 2; // add end-user node - let userNodeId = editor.addNode('user', 0, 1, posX, posY, 'user', + const userNodeId = editor.addNode('user', 0, 1, posX, posY, 'user', { id: "", profiles: [], @@ -83,7 +80,8 @@ // add router node posX += nodeSpaceX; - let routerPosY = nodeSpaceY * (agents.length > 0 ? agents.length : 1) / (routers.length > 0 ? routers.length : 1); + let routerPosY = viewMode ? posY : nodeSpaceY * (agents.length > 0 ? agents.length : 1) / (routers.length > 0 ? routers.length : 1); + routers.forEach(router => { /** @type {string[]} */ let profiles = []; @@ -108,7 +106,8 @@ if (router.is_host) { html =`${html}`; } - let nodeId = editor.addNode('router', 1, 1, posX, routerPosY, 'router', data, `${html}`, false);; + + const nodeId = editor.addNode('router', 1, 1, posX, routerPosY, 'router', data, `${html}`, false);; // connect user and router editor.addConnection(userNodeId, nodeId, `output_1`, `input_1`); routerPosY += nodeSpaceY + 50; @@ -117,9 +116,11 @@ const plannerAgents = agents.filter(x => x.type === 'planning'); const otherAgnets = agents.filter(x => x.type !== 'planning'); - posY = 100; + posY = 120; posX += nodeSpaceX; plannerAgents.forEach(agent => { + /**@type {any} */ + let nodeId = null; let html = `${agent.name}`; if (agent.profiles.length > 0) { @@ -131,17 +132,28 @@ agent: agent.name }; - const nid = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); const routers = agentNodes.filter(x => x.type === AgentType.Routing && x.profiles?.includes('planning') && getPlannerName(x) === agent.name); + if (!viewMode || routers.length > 0) { + nodeId = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); + } + routers.forEach(r => { - editor.addConnection(r.nid, nid, `output_1`, `input_1`); + if (nodeId) { + editor.addConnection(r.nid, nodeId, `output_1`, `input_1`); + } }); - posY += nodeSpaceY; + + if (!!nodeId) { + posY += nodeSpaceY; + } }); - posY = 100; - posX += nodeSpaceX; - otherAgnets.forEach(agent => { + posY = viewMode ? posY + 30 : 120; + posX += viewMode ? 0 : nodeSpaceX; + otherAgnets.forEach(agent => { + /**@type {any} */ + let nodeId = null; + /**@type {any[]} */ let profiles = []; const chatTestLinkHtml = agent.is_public ? @@ -166,51 +178,71 @@ agent: agent.name, profiles: profiles }; - const nid = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); - - // connect by profile - if (profiles.length > 0) { - // match profile - profiles.forEach((/** @type {string} */ profile) => { - if (profile == 'planning') return; - - agentNodes.filter(ag => ag.type == AgentType.Routing).forEach(r => { - if (r.profiles.find((/** @type {string} */ p) => p == profile)) { - editor.addConnection(r.nid, nid, `output_1`, `input_1`); - } else { - // editor.removeNodeInput(nid, "input_2"); - // editor.addConnection(userNodeId, nid, `output_1`, `input_1`); - } + + + if (viewMode && profiles.length > 0) { + const filteredProfiles = profiles.filter((/** @type {string} */ profile) => profile !== 'planning'); + const foundNodes = agentNodes.filter(ag => ag.type == AgentType.Routing + && !!ag.profiles?.some((/** @type {any} */ p) => filteredProfiles.includes(p))); + + if (foundNodes.length > 0) { + nodeId = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); + filteredProfiles.forEach((/** @type {string} */ profile) => { + foundNodes.forEach(r => { + if (r.profiles.find((/** @type {string} */ p) => p == profile)) { + editor.addConnection(r.nid, nodeId, `output_1`, `input_1`); + } + }); }); - }); - } else { - // profile is empty - /*agentNodes.filter(ag => ag.type == "routing" && ag.profiles.length == 0) - .forEach(r => { - editor.addConnection(r.nid, nid, `output_1`, `input_1`); - });*/ - - editor.addConnection(userNodeId, nid, `output_1`, `input_1`); + } + } else if (!viewMode) { + nodeId = editor.addNode('agent', 1, 0, posX, posY, 'enabled-node', data, html, false); + + // connect by profile + if (profiles.length > 0) { + // match profile + const filteredProfiles = profiles.filter((/** @type {string} */ profile) => profile !== 'planning'); + filteredProfiles.forEach((/** @type {string} */ profile) => { + agentNodes.filter(ag => ag.type == AgentType.Routing).forEach(r => { + if (r.profiles.find((/** @type {string} */ p) => p == profile)) { + editor.addConnection(r.nid, nodeId, `output_1`, `input_1`); + } else { + // editor.removeNodeInput(nid, "input_2"); + // editor.addConnection(userNodeId, nid, `output_1`, `input_1`); + } + }); + }); + } else { + // profile is empty + /*agentNodes.filter(ag => ag.type == "routing" && ag.profiles.length == 0) + .forEach(r => { + editor.addConnection(r.nid, nid, `output_1`, `input_1`); + });*/ + + editor.addConnection(userNodeId, nodeId, `output_1`, `input_1`); + } } - posY += nodeSpaceY; + if (!!nodeId) { + posY += nodeSpaceY; + } // draw fallback routing // fallback - const fallback = agent.routing_rules.find((/** @type {any} */ p) => p.type == "fallback"); - if (fallback) { - editor.addNodeOutput(nid); - let router = agentNodes.find(ag => ag.id == fallback.redirectTo); - if (router) { - editor.addNodeInput(router.nid); - var inputs = editor.getNodeFromId(router.nid).inputs; - let inputId = 0; - for (let prop in inputs) { - inputId++; - } - editor.addConnection(nid, router.nid, `output_1`, `input_${inputId}`); - } - } + // const fallback = agent.routing_rules.find((/** @type {any} */ p) => p.type == "fallback"); + // if (fallback) { + // editor.addNodeOutput(nid); + // let router = agentNodes.find(ag => ag.id == fallback.redirectTo); + // if (router) { + // editor.addNodeInput(router.nid); + // var inputs = editor.getNodeFromId(router.nid).inputs; + // let inputId = 0; + // for (let prop in inputs) { + // inputId++; + // } + // editor.addConnection(nid, router.nid, `output_1`, `input_${inputId}`); + // } + // } }); } @@ -261,15 +293,26 @@
- - + + handlePlannerAgentSelected()} />