refac
This commit is contained in:
1 parent
437c06c479
commit
ea2e3d0afc
5 files changed
+406
-406
No files matched your search
@@ -350,7 +350,7 @@ class Pipe:
|
||||
}}
|
||||
>
|
||||
<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"
|
||||
class="mb-1 flex h-6 w-fit items-center gap-1 rounded-md 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('/admin/functions');
|
||||
@@ -360,7 +360,7 @@ class Pipe:
|
||||
<span>{$i18n.t('Back')}</span>
|
||||
</button>
|
||||
|
||||
<div class="flex shrink-0 items-start gap-2 pb-2">
|
||||
<div class="flex shrink-0 items-start gap-2 pb-2 px-1">
|
||||
<div class="min-w-0 flex-1">
|
||||
<Tooltip content={$i18n.t('e.g. My Filter')} placement="top-start">
|
||||
<input
|
||||
|
||||
@@ -425,7 +425,7 @@
|
||||
<div class="flex h-full min-h-0 w-full flex-col">
|
||||
{#if onBack}
|
||||
<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"
|
||||
class="mb-1 flex h-6 w-fit items-center gap-1 rounded-md 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={() => {
|
||||
onBack();
|
||||
@@ -448,200 +448,308 @@
|
||||
reader.onload = (event) => {
|
||||
let originalImageUrl = `${event.target?.result}`;
|
||||
|
||||
// For animated formats (gif, webp), skip resizing to preserve animation
|
||||
const fileType = (inputFiles[0] as any)?.['type'];
|
||||
if (fileType === 'image/gif' || fileType === 'image/webp') {
|
||||
info.meta.profile_image_url = originalImageUrl;
|
||||
inputFiles = null;
|
||||
filesInputElement.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new Image();
|
||||
img.src = originalImageUrl;
|
||||
|
||||
img.onload = function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Calculate the aspect ratio of the image
|
||||
const aspectRatio = img.width / img.height;
|
||||
|
||||
// Calculate the new width and height to fit within 100x100
|
||||
let newWidth, newHeight;
|
||||
if (aspectRatio > 1) {
|
||||
newWidth = 250 * aspectRatio;
|
||||
newHeight = 250;
|
||||
} else {
|
||||
newWidth = 250;
|
||||
newHeight = 250 / aspectRatio;
|
||||
// For animated formats (gif, webp), skip resizing to preserve animation
|
||||
const fileType = (inputFiles[0] as any)?.['type'];
|
||||
if (fileType === 'image/gif' || fileType === 'image/webp') {
|
||||
info.meta.profile_image_url = originalImageUrl;
|
||||
inputFiles = null;
|
||||
filesInputElement.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the canvas size
|
||||
canvas.width = 250;
|
||||
canvas.height = 250;
|
||||
const img = new Image();
|
||||
img.src = originalImageUrl;
|
||||
|
||||
// Calculate the position to center the image
|
||||
const offsetX = (250 - newWidth) / 2;
|
||||
const offsetY = (250 - newHeight) / 2;
|
||||
img.onload = function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Draw the image on the canvas
|
||||
ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
|
||||
// Calculate the aspect ratio of the image
|
||||
const aspectRatio = img.width / img.height;
|
||||
|
||||
// Get the base64 representation of the compressed image
|
||||
const compressedSrc = canvas.toDataURL('image/webp', 0.8);
|
||||
// Calculate the new width and height to fit within 100x100
|
||||
let newWidth, newHeight;
|
||||
if (aspectRatio > 1) {
|
||||
newWidth = 250 * aspectRatio;
|
||||
newHeight = 250;
|
||||
} else {
|
||||
newWidth = 250;
|
||||
newHeight = 250 / aspectRatio;
|
||||
}
|
||||
|
||||
// Display the compressed image
|
||||
info.meta.profile_image_url = compressedSrc;
|
||||
// Set the canvas size
|
||||
canvas.width = 250;
|
||||
canvas.height = 250;
|
||||
|
||||
inputFiles = null;
|
||||
filesInputElement.value = '';
|
||||
// Calculate the position to center the image
|
||||
const offsetX = (250 - newWidth) / 2;
|
||||
const offsetY = (250 - newHeight) / 2;
|
||||
|
||||
// Draw the image on the canvas
|
||||
ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
|
||||
|
||||
// Get the base64 representation of the compressed image
|
||||
const compressedSrc = canvas.toDataURL('image/webp', 0.8);
|
||||
|
||||
// Display the compressed image
|
||||
info.meta.profile_image_url = compressedSrc;
|
||||
|
||||
inputFiles = null;
|
||||
filesInputElement.value = '';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if (
|
||||
inputFiles &&
|
||||
inputFiles.length > 0 &&
|
||||
['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/svg+xml'].includes(
|
||||
(inputFiles[0] as any)?.['type']
|
||||
)
|
||||
) {
|
||||
reader.readAsDataURL(inputFiles[0]);
|
||||
} else {
|
||||
console.log(`Unsupported File Type '${(inputFiles[0] as any)?.['type']}'.`);
|
||||
inputFiles = null;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if !edit || (edit && model)}
|
||||
<form
|
||||
class="flex w-full flex-col gap-2.5 md:flex-row"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
if (
|
||||
inputFiles &&
|
||||
inputFiles.length > 0 &&
|
||||
['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/svg+xml'].includes(
|
||||
(inputFiles[0] as any)?.['type']
|
||||
)
|
||||
) {
|
||||
reader.readAsDataURL(inputFiles[0]);
|
||||
} else {
|
||||
console.log(`Unsupported File Type '${(inputFiles[0] as any)?.['type']}'.`);
|
||||
inputFiles = null;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="w-full px-1">
|
||||
<div class="flex w-full flex-col gap-3">
|
||||
<div class="flex w-full min-w-0 items-center gap-3 py-0.5">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<button
|
||||
class="group relative flex size-12 shrink-0 items-center overflow-hidden rounded-xl md:size-14 {info
|
||||
.meta.profile_image_url !== `${WEBUI_BASE_URL}/static/favicon.png`
|
||||
? 'bg-transparent'
|
||||
: 'bg-gray-50 dark:bg-gray-850'} ring-1 ring-gray-200/70 transition hover:ring-gray-300 dark:ring-white/10 dark:hover:ring-white/20"
|
||||
type="button"
|
||||
aria-label={$i18n.t('Upload profile image')}
|
||||
on:click={() => {
|
||||
filesInputElement.click();
|
||||
}}
|
||||
>
|
||||
{#if info.meta.profile_image_url}
|
||||
<img
|
||||
src={info.meta.profile_image_url}
|
||||
alt="model profile"
|
||||
class="size-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
src="{WEBUI_BASE_URL}/static/favicon.png"
|
||||
alt="model profile"
|
||||
class="size-full object-cover"
|
||||
/>
|
||||
{/if}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="absolute bottom-0 right-0 z-10 opacity-0 transition group-hover:opacity-100"
|
||||
{#if !edit || (edit && model)}
|
||||
<form
|
||||
class="flex w-full flex-col gap-2.5 md:flex-row"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
<div class="w-full px-1">
|
||||
<div class="flex w-full flex-col gap-3">
|
||||
<div class="flex w-full min-w-0 items-center gap-3 py-0.5">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<button
|
||||
class="group relative flex size-12 shrink-0 items-center overflow-hidden rounded-xl md:size-14 {info
|
||||
.meta.profile_image_url !== `${WEBUI_BASE_URL}/static/favicon.png`
|
||||
? 'bg-transparent'
|
||||
: 'bg-gray-50 dark:bg-gray-850'} ring-1 ring-gray-200/70 transition hover:ring-gray-300 dark:ring-white/10 dark:hover:ring-white/20"
|
||||
type="button"
|
||||
aria-label={$i18n.t('Upload profile image')}
|
||||
on:click={() => {
|
||||
filesInputElement.click();
|
||||
}}
|
||||
>
|
||||
<div class="m-1">
|
||||
<div
|
||||
class="rounded-full bg-gray-900 p-1 text-white shadow-sm transition dark:bg-white dark:text-black"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-3"
|
||||
{#if info.meta.profile_image_url}
|
||||
<img
|
||||
src={info.meta.profile_image_url}
|
||||
alt="model profile"
|
||||
class="size-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
src="{WEBUI_BASE_URL}/static/favicon.png"
|
||||
alt="model profile"
|
||||
class="size-full object-cover"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="absolute bottom-0 right-0 z-10 opacity-0 transition group-hover:opacity-100"
|
||||
>
|
||||
<div class="m-1">
|
||||
<div
|
||||
class="rounded-full bg-gray-900 p-1 text-white shadow-sm transition dark:bg-white dark:text-black"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute inset-0 bg-white opacity-0 transition group-hover:opacity-20 dark:bg-black"
|
||||
></div>
|
||||
</button>
|
||||
<div
|
||||
class="absolute inset-0 bg-white opacity-0 transition group-hover:opacity-20 dark:bg-black"
|
||||
></div>
|
||||
</button>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<input
|
||||
class="min-w-0 flex-1 bg-transparent text-base leading-tight text-gray-900 outline-hidden placeholder:text-gray-300 dark:text-white dark:placeholder:text-gray-700 md:text-lg"
|
||||
placeholder={$i18n.t('Model Name')}
|
||||
bind:value={name}
|
||||
required
|
||||
/>
|
||||
|
||||
<AccessButton
|
||||
on:click={() => {
|
||||
showAccessControlModal = true;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<input
|
||||
class="min-w-0 flex-1 bg-transparent text-base leading-tight text-gray-900 outline-hidden placeholder:text-gray-300 dark:text-white dark:placeholder:text-gray-700 md:text-lg"
|
||||
placeholder={$i18n.t('Model Name')}
|
||||
bind:value={name}
|
||||
class="block w-full bg-transparent py-0.5 text-xs text-gray-500 outline-hidden placeholder:text-gray-300 dark:text-gray-500 dark:placeholder:text-gray-700"
|
||||
placeholder={$i18n.t('Model ID')}
|
||||
bind:value={id}
|
||||
disabled={edit}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AccessButton
|
||||
on:click={() => {
|
||||
showAccessControlModal = true;
|
||||
}}
|
||||
/>
|
||||
{#if preset}
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Base Model (From)')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="block w-full bg-transparent py-0.5 text-xs text-gray-500 outline-hidden placeholder:text-gray-300 dark:text-gray-500 dark:placeholder:text-gray-700"
|
||||
placeholder={$i18n.t('Model ID')}
|
||||
bind:value={id}
|
||||
disabled={edit}
|
||||
required
|
||||
<ModelSelector
|
||||
id="workspace-base-model"
|
||||
placeholder={$i18n.t('Select a base model (e.g. llama3, gpt-4o)')}
|
||||
searchPlaceholder={$i18n.t('Search a model')}
|
||||
items={getBaseModelItems($models)}
|
||||
triggerClassName="text-xs"
|
||||
selectionOnly
|
||||
includeHidden={$user?.role === 'admin'}
|
||||
bind:value={info.base_model_id}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<div class="mb-1 flex w-full items-center justify-between">
|
||||
<div class="self-center text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Description')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="text-xs text-gray-500 transition hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
aria-pressed={enableDescription ? 'true' : 'false'}
|
||||
aria-label={enableDescription
|
||||
? $i18n.t('Custom description enabled')
|
||||
: $i18n.t('Default description enabled')}
|
||||
on:click={() => {
|
||||
enableDescription = !enableDescription;
|
||||
}}
|
||||
>
|
||||
{#if !enableDescription}
|
||||
<span>{$i18n.t('Default')}</span>
|
||||
{:else}
|
||||
<span>{$i18n.t('Custom')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if enableDescription}
|
||||
<Textarea
|
||||
className="w-full resize-none overflow-y-hidden bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
placeholder={$i18n.t('Add a short description about what this model does')}
|
||||
minSize={32}
|
||||
bind:value={info.meta.description}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-full">
|
||||
<Tags
|
||||
tags={info?.meta?.tags ?? []}
|
||||
{suggestionTags}
|
||||
on:delete={(e) => {
|
||||
const tagName = e.detail;
|
||||
info.meta.tags = info.meta.tags.filter((tag) => tag.name !== tagName);
|
||||
}}
|
||||
on:add={(e) => {
|
||||
const tagName = e.detail;
|
||||
if (!(info?.meta?.tags ?? null)) {
|
||||
info.meta.tags = [{ name: tagName }];
|
||||
} else {
|
||||
info.meta.tags = [...info.meta.tags, { name: tagName }];
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if preset}
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Base Model (From)')}
|
||||
<section class="mt-2.5">
|
||||
<div class="mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Model Params')}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2.5">
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('System Prompt')}
|
||||
</div>
|
||||
<div>
|
||||
<Textarea
|
||||
className="min-h-12 w-full resize-none overflow-y-hidden bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
placeholder={$i18n.t(
|
||||
'Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.'
|
||||
)}
|
||||
rows={2}
|
||||
minSize={48}
|
||||
bind:value={system}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModelSelector
|
||||
id="workspace-base-model"
|
||||
placeholder={$i18n.t('Select a base model (e.g. llama3, gpt-4o)')}
|
||||
searchPlaceholder={$i18n.t('Search a model')}
|
||||
items={getBaseModelItems($models)}
|
||||
triggerClassName="text-xs"
|
||||
selectionOnly
|
||||
includeHidden={$user?.role === 'admin'}
|
||||
bind:value={info.base_model_id}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex h-7 w-full justify-between">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Advanced Params')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb-1 flex w-full items-center justify-between">
|
||||
<button
|
||||
class="text-xs text-gray-500 transition hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAdvanced = !showAdvanced;
|
||||
}}
|
||||
>
|
||||
{#if showAdvanced}
|
||||
<span>{$i18n.t('Hide')}</span>
|
||||
{:else}
|
||||
<span>{$i18n.t('Show')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showAdvanced}
|
||||
<div class="my-2">
|
||||
<AdvancedParams admin={true} custom={true} layout="grid" bind:params />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
|
||||
|
||||
<section class="my-2.5">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="self-center text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Description')}
|
||||
{$i18n.t('Prompts')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="text-xs text-gray-500 transition hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
aria-pressed={enableDescription ? 'true' : 'false'}
|
||||
aria-label={enableDescription
|
||||
? $i18n.t('Custom description enabled')
|
||||
: $i18n.t('Default description enabled')}
|
||||
on:click={() => {
|
||||
enableDescription = !enableDescription;
|
||||
if ((info?.meta?.suggestion_prompts ?? null) === null) {
|
||||
info.meta.suggestion_prompts = [{ content: '', title: ['', ''] }];
|
||||
} else {
|
||||
info.meta.suggestion_prompts = null;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if !enableDescription}
|
||||
{#if (info?.meta?.suggestion_prompts ?? null) === null}
|
||||
<span>{$i18n.t('Default')}</span>
|
||||
{:else}
|
||||
<span>{$i18n.t('Custom')}</span>
|
||||
@@ -649,275 +757,167 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if enableDescription}
|
||||
<Textarea
|
||||
className="w-full resize-none overflow-y-hidden bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
placeholder={$i18n.t('Add a short description about what this model does')}
|
||||
minSize={32}
|
||||
bind:value={info.meta.description}
|
||||
/>
|
||||
{#if info?.meta?.suggestion_prompts}
|
||||
<PromptSuggestions bind:promptSuggestions={info.meta.suggestion_prompts} />
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<div class="my-3">
|
||||
<Knowledge bind:selectedItems={knowledge} />
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-full">
|
||||
<Tags
|
||||
tags={info?.meta?.tags ?? []}
|
||||
{suggestionTags}
|
||||
on:delete={(e) => {
|
||||
const tagName = e.detail;
|
||||
info.meta.tags = info.meta.tags.filter((tag) => tag.name !== tagName);
|
||||
}}
|
||||
on:add={(e) => {
|
||||
const tagName = e.detail;
|
||||
if (!(info?.meta?.tags ?? null)) {
|
||||
info.meta.tags = [{ name: tagName }];
|
||||
} else {
|
||||
info.meta.tags = [...info.meta.tags, { name: tagName }];
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="mt-2.5">
|
||||
<div class="mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Model Params')}
|
||||
<div class="my-3">
|
||||
<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools ?? []} />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2.5">
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('System Prompt')}
|
||||
</div>
|
||||
<div>
|
||||
<Textarea
|
||||
className="min-h-12 w-full resize-none overflow-y-hidden bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
placeholder={$i18n.t(
|
||||
'Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.'
|
||||
)}
|
||||
rows={2}
|
||||
minSize={48}
|
||||
bind:value={system}
|
||||
<div class="my-3">
|
||||
<SkillsSelector bind:selectedSkillIds={skillIds} skills={skillsList} />
|
||||
</div>
|
||||
|
||||
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0 || ($functions ?? []).filter((func) => func.type === 'action').length > 0}
|
||||
<hr class="my-3 border-gray-100/30 dark:border-gray-850/30" />
|
||||
|
||||
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0}
|
||||
<div class="my-3">
|
||||
<FiltersSelector
|
||||
bind:selectedFilterIds={filterIds}
|
||||
filters={($functions ?? []).filter((func) => func.type === 'filter')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex h-7 w-full justify-between">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Advanced Params')}
|
||||
{@const toggleableFilters = $functions.filter(
|
||||
(func) =>
|
||||
func.type === 'filter' &&
|
||||
(filterIds.includes(func.id) || func?.is_global) &&
|
||||
func?.meta?.toggle
|
||||
)}
|
||||
|
||||
{#if toggleableFilters.length > 0}
|
||||
<div class="my-3">
|
||||
<DefaultFiltersSelector
|
||||
bind:selectedFilterIds={defaultFilterIds}
|
||||
filters={toggleableFilters}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if ($functions ?? []).filter((func) => func.type === 'action').length > 0}
|
||||
<div class="my-3">
|
||||
<ActionsSelector
|
||||
bind:selectedActionIds={actionIds}
|
||||
actions={($functions ?? []).filter((func) => func.type === 'action')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<hr class="my-3 border-gray-100/30 dark:border-gray-850/30" />
|
||||
|
||||
<div class="my-3">
|
||||
<Capabilities bind:capabilities />
|
||||
</div>
|
||||
|
||||
{#if Object.keys(capabilities).filter((key) => capabilities[key]).length > 0}
|
||||
{@const availableFeatures = Object.entries(capabilities)
|
||||
.filter(
|
||||
([key, value]) =>
|
||||
value && ['web_search', 'code_interpreter', 'image_generation'].includes(key)
|
||||
)
|
||||
.map(([key, value]) => key)}
|
||||
|
||||
{#if availableFeatures.length > 0}
|
||||
<div class="my-3">
|
||||
<DefaultFeatures {availableFeatures} bind:featureIds={defaultFeatureIds} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if capabilities.builtin_tools}
|
||||
<div class="my-3">
|
||||
<BuiltinTools bind:builtinTools />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if capabilities.terminal}
|
||||
<div class="my-3">
|
||||
<TerminalSelector bind:terminalId />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-3">
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class="self-center text-xs font-normal text-gray-500">
|
||||
{$i18n.t('TTS Voice')}
|
||||
</div>
|
||||
</div>
|
||||
<TTSVoiceInput
|
||||
bind:value={tts.voice}
|
||||
{voices}
|
||||
placeholder={$i18n.t('e.g. alloy, echo, shimmer')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="my-3 border-gray-100/30 dark:border-gray-850/30" />
|
||||
|
||||
<div class="my-2 flex justify-end">
|
||||
<button
|
||||
class=" text-sm px-3 py-2 transition rounded-lg {loading
|
||||
? ' cursor-not-allowed bg-black hover:bg-gray-900 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black'
|
||||
: 'bg-black hover:bg-gray-900 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black'} flex w-full justify-center"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
<div class=" self-center font-normal">
|
||||
{#if edit}
|
||||
{$i18n.t('Save & Update')}
|
||||
{:else}
|
||||
{$i18n.t('Save & Create')}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="ml-1.5 self-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="my-2 text-gray-300 dark:text-gray-700 pb-20">
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-sm font-normal">{$i18n.t('JSON Preview')}</div>
|
||||
|
||||
<button
|
||||
class="text-xs text-gray-500 transition hover:text-gray-700 dark:hover:text-gray-300"
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAdvanced = !showAdvanced;
|
||||
showPreview = !showPreview;
|
||||
}}
|
||||
>
|
||||
{#if showAdvanced}
|
||||
<span>{$i18n.t('Hide')}</span>
|
||||
{#if showPreview}
|
||||
<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
|
||||
{:else}
|
||||
<span>{$i18n.t('Show')}</span>
|
||||
<span class="ml-2 self-center">{$i18n.t('Show')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showAdvanced}
|
||||
<div class="my-2">
|
||||
<AdvancedParams admin={true} custom={true} layout="grid" bind:params />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
|
||||
|
||||
<section class="my-2.5">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="self-center text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Prompts')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="text-xs text-gray-500 transition hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if ((info?.meta?.suggestion_prompts ?? null) === null) {
|
||||
info.meta.suggestion_prompts = [{ content: '', title: ['', ''] }];
|
||||
} else {
|
||||
info.meta.suggestion_prompts = null;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if (info?.meta?.suggestion_prompts ?? null) === null}
|
||||
<span>{$i18n.t('Default')}</span>
|
||||
{:else}
|
||||
<span>{$i18n.t('Custom')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if info?.meta?.suggestion_prompts}
|
||||
<PromptSuggestions bind:promptSuggestions={info.meta.suggestion_prompts} />
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<div class="my-3">
|
||||
<Knowledge bind:selectedItems={knowledge} />
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools ?? []} />
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<SkillsSelector bind:selectedSkillIds={skillIds} skills={skillsList} />
|
||||
</div>
|
||||
|
||||
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0 || ($functions ?? []).filter((func) => func.type === 'action').length > 0}
|
||||
<hr class="my-3 border-gray-100/30 dark:border-gray-850/30" />
|
||||
|
||||
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0}
|
||||
<div class="my-3">
|
||||
<FiltersSelector
|
||||
bind:selectedFilterIds={filterIds}
|
||||
filters={($functions ?? []).filter((func) => func.type === 'filter')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{@const toggleableFilters = $functions.filter(
|
||||
(func) =>
|
||||
func.type === 'filter' &&
|
||||
(filterIds.includes(func.id) || func?.is_global) &&
|
||||
func?.meta?.toggle
|
||||
)}
|
||||
|
||||
{#if toggleableFilters.length > 0}
|
||||
<div class="my-3">
|
||||
<DefaultFiltersSelector
|
||||
bind:selectedFilterIds={defaultFilterIds}
|
||||
filters={toggleableFilters}
|
||||
{#if showPreview}
|
||||
<div>
|
||||
<textarea
|
||||
class="text-sm w-full bg-transparent outline-hidden resize-none"
|
||||
rows="10"
|
||||
value={JSON.stringify(info, null, 2)}
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if ($functions ?? []).filter((func) => func.type === 'action').length > 0}
|
||||
<div class="my-3">
|
||||
<ActionsSelector
|
||||
bind:selectedActionIds={actionIds}
|
||||
actions={($functions ?? []).filter((func) => func.type === 'action')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<hr class="my-3 border-gray-100/30 dark:border-gray-850/30" />
|
||||
|
||||
<div class="my-3">
|
||||
<Capabilities bind:capabilities />
|
||||
</div>
|
||||
|
||||
{#if Object.keys(capabilities).filter((key) => capabilities[key]).length > 0}
|
||||
{@const availableFeatures = Object.entries(capabilities)
|
||||
.filter(
|
||||
([key, value]) =>
|
||||
value && ['web_search', 'code_interpreter', 'image_generation'].includes(key)
|
||||
)
|
||||
.map(([key, value]) => key)}
|
||||
|
||||
{#if availableFeatures.length > 0}
|
||||
<div class="my-3">
|
||||
<DefaultFeatures {availableFeatures} bind:featureIds={defaultFeatureIds} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if capabilities.builtin_tools}
|
||||
<div class="my-3">
|
||||
<BuiltinTools bind:builtinTools />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if capabilities.terminal}
|
||||
<div class="my-3">
|
||||
<TerminalSelector bind:terminalId />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-3">
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class="self-center text-xs font-normal text-gray-500">
|
||||
{$i18n.t('TTS Voice')}
|
||||
</div>
|
||||
</div>
|
||||
<TTSVoiceInput
|
||||
bind:value={tts.voice}
|
||||
{voices}
|
||||
placeholder={$i18n.t('e.g. alloy, echo, shimmer')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="my-3 border-gray-100/30 dark:border-gray-850/30" />
|
||||
|
||||
<div class="my-2 flex justify-end">
|
||||
<button
|
||||
class=" text-sm px-3 py-2 transition rounded-lg {loading
|
||||
? ' cursor-not-allowed bg-black hover:bg-gray-900 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black'
|
||||
: 'bg-black hover:bg-gray-900 text-white dark:bg-white dark:hover:bg-gray-100 dark:text-black'} flex w-full justify-center"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
<div class=" self-center font-normal">
|
||||
{#if edit}
|
||||
{$i18n.t('Save & Update')}
|
||||
{:else}
|
||||
{$i18n.t('Save & Create')}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="ml-1.5 self-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="my-2 text-gray-300 dark:text-gray-700 pb-20">
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-sm font-normal">{$i18n.t('JSON Preview')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showPreview = !showPreview;
|
||||
}}
|
||||
>
|
||||
{#if showPreview}
|
||||
<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Show')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showPreview}
|
||||
<div>
|
||||
<textarea
|
||||
class="text-sm w-full bg-transparent outline-hidden resize-none"
|
||||
rows="10"
|
||||
value={JSON.stringify(info, null, 2)}
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -385,7 +385,7 @@
|
||||
<!-- Edit mode: Read-only view with history -->
|
||||
<div class="flex h-full max-h-[100dvh] w-full flex-col">
|
||||
<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"
|
||||
class="mb-1 flex h-6 w-fit items-center gap-1 rounded-md 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/prompts');
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<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"
|
||||
class="mb-1 flex h-6 w-fit items-center gap-1 rounded-md 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');
|
||||
@@ -110,7 +110,7 @@
|
||||
<span>{$i18n.t('Back')}</span>
|
||||
</button>
|
||||
|
||||
<div class="flex shrink-0 items-start gap-2 pb-2">
|
||||
<div class="flex shrink-0 items-start gap-2 pb-2 px-1">
|
||||
<div class="min-w-0 flex-1">
|
||||
<Tooltip content={$i18n.t('e.g. Code Review Guidelines')} placement="top-start">
|
||||
<input
|
||||
|
||||
@@ -225,7 +225,7 @@ class Tools:
|
||||
}}
|
||||
>
|
||||
<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"
|
||||
class="mb-1 flex h-6 w-fit items-center gap-1 rounded-md 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');
|
||||
@@ -235,7 +235,7 @@ class Tools:
|
||||
<span>{$i18n.t('Back')}</span>
|
||||
</button>
|
||||
|
||||
<div class="flex shrink-0 items-start gap-2 pb-2">
|
||||
<div class="flex shrink-0 items-start gap-2 pb-2 px-1">
|
||||
<div class="min-w-0 flex-1">
|
||||
<Tooltip content={$i18n.t('e.g. My Tools')} placement="top-start">
|
||||
<input
|
||||
|
||||
Reference in new issue
Block a user