This commit is contained in:
Timothy Jaeryang Baek
2026-07-17 05:31:32 -04:00
parent 421da67446
commit d0bea60581
7 changed files with 558 additions and 618 deletions
@@ -97,133 +97,114 @@
}}
/>
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
<div class="mx-auto w-full md:px-0 h-full">
<form class=" flex flex-col max-h-[100dvh] h-full" on:submit|preventDefault={submitHandler}>
<div class="flex flex-col flex-1 overflow-auto h-0 rounded-lg">
<div class="w-full mb-2 flex flex-col gap-0.5">
<div class="flex w-full items-center">
<div class=" shrink-0 mr-2">
<Tooltip content={$i18n.t('Back')}>
<button
class="w-full text-left text-sm py-1.5 px-1 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
aria-label={$i18n.t('Back')}
on:click={() => {
goto('/workspace/skills');
}}
type="button"
>
<ChevronLeft strokeWidth="2.5" />
</button>
</Tooltip>
<div class="flex h-full w-full min-w-0 flex-col overflow-hidden">
<form class="flex h-full min-h-0 min-w-0 flex-col" on:submit|preventDefault={submitHandler}>
<button
class="mb-1 flex h-6 w-fit items-center gap-1 rounded-md px-0.5 text-xs text-gray-400 transition-colors duration-75 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
type="button"
on:click={() => {
goto('/workspace/skills');
}}
>
<ChevronLeft className="size-3" strokeWidth="2" />
<span>{$i18n.t('Back')}</span>
</button>
<div class="flex shrink-0 items-start gap-2 pb-2">
<div class="min-w-0 flex-1">
<Tooltip content={$i18n.t('e.g. Code Review Guidelines')} placement="top-start">
<input
class="w-full bg-transparent text-sm outline-hidden"
type="text"
placeholder={$i18n.t('Skill Name')}
aria-label={$i18n.t('Skill Name')}
bind:value={name}
required
{disabled}
/>
</Tooltip>
<div class="mt-0.5 flex min-w-0 items-center gap-2 text-xs text-gray-500">
{#if edit}
<div class="shrink-0 truncate font-mono" title={id}>
{id}
</div>
<div class="flex-1">
<Tooltip content={$i18n.t('e.g. Code Review Guidelines')} placement="top-start">
<input
class="w-full text-2xl bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Skill Name')}
aria-label={$i18n.t('Skill Name')}
bind:value={name}
required
{disabled}
/>
</Tooltip>
</div>
<div class="self-center shrink-0">
{#if !disabled}
<AccessButton on:click={() => (showAccessControlModal = true)} />
{:else}
<span
class="text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded-full"
>{$i18n.t('Read Only')}</span
>
{/if}
</div>
</div>
<div class=" flex gap-2 px-1 items-center">
{#if edit}
<div class="text-sm text-gray-500 shrink-0">
{id}
</div>
{:else}
<Tooltip
className="w-full"
content={$i18n.t('e.g. code-review-guidelines')}
placement="top-start"
>
<input
class="w-full text-sm disabled:text-gray-500 bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Skill ID')}
aria-label={$i18n.t('Skill ID')}
bind:value={id}
required
disabled={edit}
/>
</Tooltip>
{/if}
{:else}
<Tooltip
className="w-full self-center items-center flex"
content={$i18n.t('e.g. Step-by-step instructions for code reviews')}
className="min-w-[8rem] flex-1"
content={$i18n.t('e.g. code-review-guidelines')}
placement="top-start"
>
<input
class="w-full text-sm bg-transparent outline-hidden"
class="w-full bg-transparent font-mono outline-hidden disabled:text-gray-500"
type="text"
placeholder={$i18n.t('Skill Description')}
aria-label={$i18n.t('Skill Description')}
bind:value={description}
{disabled}
placeholder={$i18n.t('Skill ID')}
aria-label={$i18n.t('Skill ID')}
bind:value={id}
required
disabled={edit}
/>
</Tooltip>
</div>
</div>
<div class="mb-2 flex-1 overflow-auto h-0 rounded-lg">
<div class="h-full flex flex-col">
<div
class="bg-gray-50 dark:bg-gray-900 rounded-xl border border-gray-100/50 dark:border-gray-850/50 flex-1 min-h-0 overflow-hidden flex flex-col"
>
{#if disabled}
<div class="px-4 py-3 overflow-y-auto flex-1">
<pre class="text-xs whitespace-pre-wrap font-mono">{content}</pre>
</div>
{:else}
<textarea
class="w-full flex-1 text-xs bg-transparent outline-hidden resize-none font-mono px-4 py-3"
bind:value={content}
on:input={handleContentInput}
placeholder={$i18n.t('Enter skill instructions in markdown...')}
aria-label={$i18n.t('Skill Instructions')}
required
/>
{/if}
</div>
</div>
</div>
<div class="pb-3 flex justify-end">
{#if !disabled}
<button
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex items-center gap-2 whitespace-nowrap"
type="submit"
disabled={loading}
>
{$i18n.t(edit ? 'Save' : 'Save & Create')}
{#if loading}
<span class="shrink-0">
<Spinner />
</span>
{/if}
</button>
{/if}
<Tooltip
className="flex min-w-0 flex-1 items-center"
content={$i18n.t('e.g. Step-by-step instructions for code reviews')}
placement="top-start"
>
<input
class="w-full bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Skill Description')}
aria-label={$i18n.t('Skill Description')}
bind:value={description}
{disabled}
/>
</Tooltip>
</div>
</div>
</form>
</div>
<div class="flex shrink-0 items-center gap-1 pr-0.5">
{#if !disabled}
<AccessButton on:click={() => (showAccessControlModal = true)} />
{:else}
<span class="rounded-lg bg-gray-100 px-2 py-1 text-xs text-gray-500 dark:bg-gray-850">
{$i18n.t('Read Only')}
</span>
{/if}
</div>
</div>
<div class="min-h-0 flex-1 overflow-hidden rounded-lg bg-gray-50/60 dark:bg-white/[0.03]">
{#if disabled}
<div class="h-full overflow-y-auto px-3 py-2">
<pre class="whitespace-pre-wrap font-mono text-[11px] leading-relaxed">{content}</pre>
</div>
{:else}
<textarea
class="h-full w-full resize-none bg-transparent px-3 py-2 font-mono text-[11px] leading-relaxed outline-hidden placeholder:text-gray-400 dark:placeholder:text-gray-600"
bind:value={content}
on:input={handleContentInput}
placeholder={$i18n.t('Enter skill instructions in markdown...')}
aria-label={$i18n.t('Skill Instructions')}
required
></textarea>
{/if}
</div>
{#if !disabled}
<div class="flex shrink-0 justify-end py-2">
<button
class="flex h-7 items-center gap-1.5 rounded-lg bg-gray-900 px-2.5 text-xs text-white transition hover:bg-black disabled:opacity-60 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-white"
type="submit"
disabled={loading}
>
{$i18n.t(edit ? 'Save' : 'Save & Create')}
{#if loading}
<Spinner className="size-3" />
{/if}
</button>
</div>
{/if}
</form>
</div>
@@ -14,6 +14,7 @@
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import AccessControlModal from '../common/AccessControlModal.svelte';
let formElement = null;
@@ -25,7 +26,7 @@
export let edit = false;
export let clone = false;
export let onSave = () => {};
export let onSave = /** @param {any} _value */ async (_value) => {};
export let id = '';
export let name = '';
@@ -159,13 +160,17 @@ class Tools:
const saveHandler = async () => {
loading = true;
onSave({
id,
name,
meta,
content,
access_grants: accessGrants
});
try {
await onSave({
id,
name,
meta,
content,
access_grants: accessGrants
});
} finally {
loading = false;
}
};
const submitHandler = async () => {
@@ -207,143 +212,142 @@ class Tools:
}}
/>
<div class=" flex flex-col justify-between w-full min-w-0 overflow-y-auto overflow-x-hidden h-full">
<div class="mx-auto w-full min-w-0 md:px-0 h-full">
<form
bind:this={formElement}
class=" flex flex-col min-w-0 max-h-[100dvh] h-full"
on:submit|preventDefault={() => {
if (edit) {
submitHandler();
} else {
showConfirm = true;
}
<div class="flex h-full w-full min-w-0 flex-col overflow-hidden">
<form
bind:this={formElement}
class="flex h-full min-h-0 min-w-0 flex-col"
on:submit|preventDefault={() => {
if (edit) {
submitHandler();
} else {
showConfirm = true;
}
}}
>
<button
class="mb-1 flex h-6 w-fit items-center gap-1 rounded-md px-0.5 text-xs text-gray-400 transition-colors duration-75 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
type="button"
on:click={() => {
goto('/workspace/tools');
}}
>
<div class="flex flex-col flex-1 min-w-0 overflow-auto h-0 rounded-lg">
<div class="w-full mb-2 flex flex-col gap-0.5">
<div class="flex w-full min-w-0 items-center">
<div class=" shrink-0 mr-2">
<Tooltip content={$i18n.t('Back')}>
<button
class="w-full text-left text-sm py-1.5 px-1 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
aria-label={$i18n.t('Back')}
on:click={() => {
goto('/workspace/tools');
}}
type="button"
>
<ChevronLeft strokeWidth="2.5" />
</button>
</Tooltip>
<ChevronLeft className="size-3" strokeWidth="2" />
<span>{$i18n.t('Back')}</span>
</button>
<div class="flex shrink-0 items-start gap-2 pb-2">
<div class="min-w-0 flex-1">
<Tooltip content={$i18n.t('e.g. My Tools')} placement="top-start">
<input
class="w-full bg-transparent text-sm outline-hidden"
type="text"
placeholder={$i18n.t('Tool Name')}
aria-label={$i18n.t('Tool Name')}
bind:value={name}
required
/>
</Tooltip>
<div class="mt-0.5 flex min-w-0 items-center gap-2 text-xs text-gray-500">
{#if edit}
<div class="shrink-0 truncate font-mono" title={id}>
{id}
</div>
<div class="flex-1 min-w-0">
<Tooltip content={$i18n.t('e.g. My Tools')} placement="top-start">
<input
class="w-full text-2xl bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Tool Name')}
aria-label={$i18n.t('Tool Name')}
bind:value={name}
required
/>
</Tooltip>
</div>
<div class="self-center shrink-0">
<AccessButton
on:click={() => {
showAccessControlModal = true;
}}
/>
</div>
</div>
<div class=" flex min-w-0 gap-2 px-1 items-center">
{#if edit}
<div class="text-sm text-gray-500 shrink-0">
{id}
</div>
{:else}
<Tooltip className="w-full" content={$i18n.t('e.g. my_tools')} placement="top-start">
<input
class="w-full text-sm disabled:text-gray-500 bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Tool ID')}
aria-label={$i18n.t('Tool ID')}
bind:value={id}
required
disabled={edit}
/>
</Tooltip>
{/if}
{:else}
<Tooltip
className="w-full self-center items-center flex"
content={$i18n.t('e.g. Tools for performing various operations')}
className="min-w-[8rem] flex-1"
content={$i18n.t('e.g. my_tools')}
placement="top-start"
>
<input
class="w-full text-sm bg-transparent outline-hidden"
class="w-full bg-transparent font-mono outline-hidden disabled:text-gray-500"
type="text"
placeholder={$i18n.t('Tool Description')}
aria-label={$i18n.t('Tool Description')}
bind:value={meta.description}
placeholder={$i18n.t('Tool ID')}
aria-label={$i18n.t('Tool ID')}
bind:value={id}
required
disabled={edit}
/>
</Tooltip>
</div>
</div>
{/if}
<div class="mb-2 flex-1 min-w-0 overflow-auto h-0 rounded-lg">
<CodeEditor
bind:this={codeEditor}
value={content}
lang="python"
{boilerplate}
onChange={(e) => {
_content = e;
if (!edit) {
const fm = extractFrontmatter(e);
if (fm.title && !name) {
name = formatSkillName(fm.title);
id = nameToId(fm.title);
}
if (fm.description && !meta.description) {
meta = { ...meta, description: fm.description };
}
}
}}
onSave={async () => {
if (formElement) {
formElement.requestSubmit();
}
}}
/>
</div>
<div class="pb-3 flex justify-between">
<div class="flex-1 pr-3">
<div class="text-xs text-gray-500 line-clamp-2">
<span class=" font-normal dark:text-gray-200">{$i18n.t('Warning:')}</span>
{$i18n.t('Tools are a function calling system with arbitrary code execution')} <br />—
<span class=" font-normal dark:text-gray-400"
>{$i18n.t(`don't install random tools from sources you don't trust.`)}</span
>
</div>
</div>
<button
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
type="submit"
<Tooltip
className="flex min-w-0 flex-1 items-center"
content={$i18n.t('e.g. Tools for performing various operations')}
placement="top-start"
>
{$i18n.t('Save')}
</button>
<input
class="w-full bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Tool Description')}
aria-label={$i18n.t('Tool Description')}
bind:value={meta.description}
required
/>
</Tooltip>
</div>
</div>
</form>
</div>
<div class="flex shrink-0 items-center gap-1 pr-0.5">
<AccessButton
on:click={() => {
showAccessControlModal = true;
}}
/>
</div>
</div>
<div class="min-h-0 flex-1 overflow-hidden rounded-lg">
<CodeEditor
bind:this={codeEditor}
value={content}
lang="python"
{boilerplate}
className="text-[11px]"
onChange={(e) => {
_content = e;
if (!edit) {
const fm = extractFrontmatter(e);
if (fm.title && !name) {
name = formatSkillName(fm.title);
id = nameToId(fm.title);
}
if (fm.description && !meta.description) {
meta = { ...meta, description: fm.description };
}
}
}}
onSave={async () => {
if (formElement) {
formElement.requestSubmit();
}
}}
/>
</div>
<div class="shrink-0 py-2 text-xs text-gray-500">
<div class="flex items-center justify-between gap-3">
<div class="min-w-0">
<span class="font-normal dark:text-gray-200">{$i18n.t('Warning:')}</span>
{$i18n.t('Tools can execute arbitrary code.')}
<span class="font-normal dark:text-gray-400">
{$i18n.t('Only install tools from sources you trust.')}
</span>
</div>
<button
class="flex h-7 shrink-0 items-center gap-1.5 rounded-lg bg-gray-900 px-2.5 text-xs text-white transition hover:bg-black disabled:opacity-60 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-white"
type="submit"
disabled={loading}
>
{$i18n.t(edit ? 'Save' : 'Save & Create')}
{#if loading}
<Spinner className="size-3" />
{/if}
</button>
</div>
</div>
</form>
</div>
<ConfirmDialog