From 1973115678b8d2cde4aff8bb8d334548536132ad Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 11 Feb 2026 14:22:26 -0600 Subject: [PATCH] feat: skills frontend --- src/lib/apis/skills/index.ts | 284 +++++++++++ src/lib/components/workspace/Skills.svelte | 440 ++++++++++++++++++ .../workspace/Skills/SkillEditor.svelte | 225 +++++++++ .../workspace/Skills/SkillMenu.svelte | 105 +++++ src/lib/stores/index.ts | 1 + src/routes/(app)/workspace/+layout.svelte | 11 + .../(app)/workspace/skills/+page.svelte | 5 + .../workspace/skills/create/+page.svelte | 56 +++ .../(app)/workspace/skills/edit/+page.svelte | 71 +++ 9 files changed, 1198 insertions(+) create mode 100644 src/lib/apis/skills/index.ts create mode 100644 src/lib/components/workspace/Skills.svelte create mode 100644 src/lib/components/workspace/Skills/SkillEditor.svelte create mode 100644 src/lib/components/workspace/Skills/SkillMenu.svelte create mode 100644 src/routes/(app)/workspace/skills/+page.svelte create mode 100644 src/routes/(app)/workspace/skills/create/+page.svelte create mode 100644 src/routes/(app)/workspace/skills/edit/+page.svelte diff --git a/src/lib/apis/skills/index.ts b/src/lib/apis/skills/index.ts new file mode 100644 index 0000000000..52d14cbbd3 --- /dev/null +++ b/src/lib/apis/skills/index.ts @@ -0,0 +1,284 @@ +import { WEBUI_API_BASE_URL } from '$lib/constants'; + +export const createNewSkill = async (token: string, skill: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/create`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + ...skill + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getSkills = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getSkillList = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/list`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const exportSkills = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/export`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getSkillById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/id/${id}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const updateSkillById = async (token: string, id: string, skill: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/id/${id}/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + ...skill + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const updateSkillAccessGrants = async ( + token: string, + id: string, + accessGrants: any[] +) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/id/${id}/access/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + access_grants: accessGrants + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const toggleSkillById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/id/${id}/toggle`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const deleteSkillById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/skills/id/${id}/delete`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/components/workspace/Skills.svelte b/src/lib/components/workspace/Skills.svelte new file mode 100644 index 0000000000..33cfc86c43 --- /dev/null +++ b/src/lib/components/workspace/Skills.svelte @@ -0,0 +1,440 @@ + + + + + {$i18n.t('Skills')} • {$WEBUI_NAME} + + + +{#if loaded} +
+
+
+
+ {$i18n.t('Skills')} +
+ +
+ {filteredItems.length} +
+
+ +
+ {#if skills.length && ($user?.role === 'admin' || $user?.permissions?.workspace?.skills)} + + {/if} + + {#if $user?.role === 'admin' || $user?.permissions?.workspace?.skills} + + + + + + {/if} +
+
+
+ +
+
+
+
+ +
+ + {#if query} +
+ +
+ {/if} +
+
+ +
{ + if (e.deltaY !== 0) { + e.preventDefault(); + e.currentTarget.scrollLeft += e.deltaY; + } + }} + > +
+ { + localStorage.workspaceViewOption = value; + await tick(); + }} + /> +
+
+ + {#if (filteredItems ?? []).length !== 0} +
+ {#each filteredItems as skill} + +
+ {#if skill.write_access} + +
+
+ +
+
+ {skill.name} +
+ {#if !skill.is_active} + + {/if} +
+
+
+
+ + {$i18n.t('By {{name}}', { + name: capitalizeFirstLetter( + skill?.user?.name ?? skill?.user?.email ?? $i18n.t('Deleted User') + ) + })} + +
+
+
+
+
+ {:else} +
+
+
+
+ +
+
+ {skill.name} +
+ {#if !skill.is_active} + + {/if} +
+
+ +
+
+
+ + {$i18n.t('By {{name}}', { + name: capitalizeFirstLetter( + skill?.user?.name ?? skill?.user?.email ?? $i18n.t('Deleted User') + ) + })} + +
+
+
+
+
+ {/if} + {#if skill.write_access} +
+ {#if shiftKey} + + + + {:else} + { + goto(`/workspace/skills/edit?id=${encodeURIComponent(skill.id)}`); + }} + cloneHandler={() => { + cloneHandler(skill); + }} + exportHandler={() => { + exportHandler(skill); + }} + deleteHandler={async () => { + selectedSkill = skill; + showDeleteConfirm = true; + }} + onClose={() => {}} + > + + + {/if} + + +
+ {/if} +
+
+ {/each} +
+ {:else} +
+
+
📝
+
{$i18n.t('No skills found')}
+
+ {$i18n.t('Try adjusting your search or filter to find what you are looking for.')} +
+
+
+ {/if} +
+ + { + deleteHandler(selectedSkill); + }} + > +
+ {$i18n.t('This will delete')} {selectedSkill.name}. +
+
+{:else} +
+ +
+{/if} diff --git a/src/lib/components/workspace/Skills/SkillEditor.svelte b/src/lib/components/workspace/Skills/SkillEditor.svelte new file mode 100644 index 0000000000..f5a9da51c6 --- /dev/null +++ b/src/lib/components/workspace/Skills/SkillEditor.svelte @@ -0,0 +1,225 @@ + + + { + if (edit && skill?.id) { + try { + await updateSkillAccessGrants(localStorage.token, skill.id, accessGrants); + toast.success($i18n.t('Saved')); + } catch (error) { + toast.error(`${error}`); + } + } + }} +/> + +
+
+
+