mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 20:35:19 +00:00
refac: styling
This commit is contained in:
parent
e65d92fc6f
commit
9d39b9b42c
13 changed files with 409 additions and 339 deletions
|
|
@ -818,10 +818,8 @@
|
|||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="my-2 -mx-2">
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
||||
<AccessControl bind:accessControl />
|
||||
</div>
|
||||
<div class="my-2">
|
||||
<AccessControl bind:accessControl />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -292,10 +292,8 @@
|
|||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="my-2 -mx-2">
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
||||
<AccessControl bind:accessControl />
|
||||
</div>
|
||||
<div class="my-2">
|
||||
<AccessControl bind:accessControl />
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
|
|
|||
|
|
@ -112,14 +112,12 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
||||
<AccessControl
|
||||
bind:accessControl
|
||||
accessRoles={['read', 'write']}
|
||||
share={$user?.permissions?.sharing?.knowledge || $user?.role === 'admin'}
|
||||
sharePublic={$user?.permissions?.sharing?.public_knowledge || $user?.role === 'admin'}
|
||||
/>
|
||||
</div>
|
||||
<AccessControl
|
||||
bind:accessControl
|
||||
accessRoles={['read', 'write']}
|
||||
share={$user?.permissions?.sharing?.knowledge || $user?.role === 'admin'}
|
||||
sharePublic={$user?.permissions?.sharing?.public_knowledge || $user?.role === 'admin'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-2">
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
{#if actions.length > 0}
|
||||
<div>
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Actions')}</div>
|
||||
<div class=" self-center text-xs font-medium text-gray-500">{$i18n.t('Actions')}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
|
||||
<div>
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Capabilities')}</div>
|
||||
<div class=" self-center text-xs font-medium text-gray-500">{$i18n.t('Capabilities')}</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-2 flex-wrap">
|
||||
{#each Object.keys(capabilityLabels) as capability}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<div>
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Default Features')}</div>
|
||||
<div class=" self-center text-xs font-medium text-gray-500">{$i18n.t('Default Features')}</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-2 flex-wrap">
|
||||
{#each availableFeatures as feature}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
{#if filters.length > 0}
|
||||
<div>
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Filters')}</div>
|
||||
<div class=" self-center text-xs font-medium text-gray-500">{$i18n.t('Filters')}</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Filer order matters -->
|
||||
|
|
|
|||
|
|
@ -157,18 +157,14 @@
|
|||
<slot name="label">
|
||||
<div class="mb-2">
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class=" self-center text-sm font-medium">
|
||||
<div class=" self-center text-xs font-medium text-gray-500">
|
||||
{$i18n.t('Knowledge')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" text-xs dark:text-gray-500">
|
||||
{$i18n.t('To attach knowledge base here, add them to the "Knowledge" workspace first.')}
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col mb-1">
|
||||
{#if selectedItems?.length > 0}
|
||||
<div class=" flex flex-wrap items-center gap-2 mb-2.5">
|
||||
{#each selectedItems as file, fileIdx}
|
||||
|
|
@ -228,4 +224,8 @@
|
|||
{/if}
|
||||
<!-- {knowledge} -->
|
||||
</div>
|
||||
|
||||
<div class=" text-xs dark:text-gray-700">
|
||||
{$i18n.t('To attach knowledge base here, add them to the "Knowledge" workspace first.')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
import DefaultFiltersSelector from './DefaultFiltersSelector.svelte';
|
||||
import DefaultFeatures from './DefaultFeatures.svelte';
|
||||
import PromptSuggestions from './PromptSuggestions.svelte';
|
||||
import AccessControlModal from '../common/AccessControlModal.svelte';
|
||||
import LockClosed from '$lib/components/icons/LockClosed.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
|
@ -42,6 +44,7 @@
|
|||
|
||||
let showAdvanced = false;
|
||||
let showPreview = false;
|
||||
let showAccessControlModal = false;
|
||||
|
||||
let loaded = false;
|
||||
|
||||
|
|
@ -317,6 +320,14 @@
|
|||
</script>
|
||||
|
||||
{#if loaded}
|
||||
<AccessControlModal
|
||||
bind:show={showAccessControlModal}
|
||||
bind:accessControl
|
||||
accessRoles={['read', 'write']}
|
||||
share={$user?.permissions?.sharing?.models || $user?.role === 'admin'}
|
||||
sharePublic={$user?.permissions?.sharing?.public_models || $user?.role === 'admin'}
|
||||
/>
|
||||
|
||||
{#if onBack}
|
||||
<button
|
||||
class="flex space-x-1"
|
||||
|
|
@ -434,13 +445,13 @@
|
|||
<img
|
||||
src={info.meta.profile_image_url}
|
||||
alt="model profile"
|
||||
class="rounded-xl size-72 md:size-60 object-cover shrink-0"
|
||||
class="rounded-xl sm:size-60 size-max object-cover shrink-0"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
src="{WEBUI_BASE_URL}/static/favicon.png"
|
||||
alt="model profile"
|
||||
class=" rounded-xl size-72 md:size-60 object-cover shrink-0"
|
||||
class=" rounded-xl sm:size-60 size-max object-cover shrink-0"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
|
@ -485,124 +496,139 @@
|
|||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="mt-2 my-2 flex flex-col">
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<input
|
||||
class="text-3xl font-medium w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Model Name')}
|
||||
bind:value={name}
|
||||
required
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex justify-between items-start my-2">
|
||||
<div class=" flex flex-col">
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<input
|
||||
class="text-4xl font-medium w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Model Name')}
|
||||
bind:value={name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<input
|
||||
class="text-xs w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Model ID')}
|
||||
bind:value={id}
|
||||
disabled={edit}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<input
|
||||
class="text-xs w-full bg-transparent text-gray-500 outline-hidden"
|
||||
placeholder={$i18n.t('Model ID')}
|
||||
bind:value={id}
|
||||
disabled={edit}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if preset}
|
||||
<div class="my-1">
|
||||
<div class=" text-sm font-medium mb-1">{$i18n.t('Base Model (From)')}</div>
|
||||
|
||||
<div>
|
||||
<select
|
||||
class="text-sm w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Select a base model (e.g. llama3, gpt-4o)')}
|
||||
bind:value={info.base_model_id}
|
||||
on:change={(e) => {
|
||||
addUsage(e.target.value);
|
||||
<button
|
||||
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 rounded-full flex gap-1 items-center"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAccessControlModal = true;
|
||||
}}
|
||||
required
|
||||
>
|
||||
<option value={null} class=" text-gray-900"
|
||||
>{$i18n.t('Select a base model')}</option
|
||||
>
|
||||
{#each $models.filter((m) => (model ? m.id !== model.id : true) && !m?.preset && m?.owned_by !== 'arena' && !(m?.direct ?? false)) as model}
|
||||
<option value={model.id} class=" text-gray-900">{model.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<LockClosed strokeWidth="2.5" className="size-3.5" />
|
||||
|
||||
<div class="text-sm font-medium shrink-0">
|
||||
{$i18n.t('Access')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-1">
|
||||
<div class="mb-1 flex w-full justify-between items-center">
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Description')}</div>
|
||||
{#if preset}
|
||||
<div class="mb-1">
|
||||
<div class=" text-xs font-medium mb-1 text-gray-500">
|
||||
{$i18n.t('Base Model (From)')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 text-xs flex rounded-sm transition"
|
||||
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 class="ml-2 self-center">{$i18n.t('Default')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if enableDescription}
|
||||
<Textarea
|
||||
className=" text-sm w-full bg-transparent outline-hidden resize-none overflow-y-hidden "
|
||||
placeholder={$i18n.t('Add a short description about what this model does')}
|
||||
bind:value={info.meta.description}
|
||||
/>
|
||||
<div>
|
||||
<select
|
||||
class="text-sm w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Select a base model (e.g. llama3, gpt-4o)')}
|
||||
bind:value={info.base_model_id}
|
||||
on:change={(e) => {
|
||||
addUsage(e.target.value);
|
||||
}}
|
||||
required
|
||||
>
|
||||
<option value={null} class=" text-gray-900"
|
||||
>{$i18n.t('Select a base model')}</option
|
||||
>
|
||||
{#each $models.filter((m) => (model ? m.id !== model.id : true) && !m?.preset && m?.owned_by !== 'arena' && !(m?.direct ?? false)) as model}
|
||||
<option value={model.id} class=" text-gray-900">{model.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" mt-2 my-1">
|
||||
<div class="">
|
||||
<Tags
|
||||
tags={info?.meta?.tags ?? []}
|
||||
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 class="mb-1">
|
||||
<div class="mb-1 flex w-full justify-between items-center">
|
||||
<div class=" self-center text-xs font-medium text-gray-500">
|
||||
{$i18n.t('Description')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 text-xs flex rounded-sm transition"
|
||||
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 class="ml-2 self-center">{$i18n.t('Default')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if enableDescription}
|
||||
<Textarea
|
||||
className=" text-sm w-full bg-transparent outline-hidden resize-none overflow-y-hidden "
|
||||
placeholder={$i18n.t('Add a short description about what this model does')}
|
||||
bind:value={info.meta.description}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-full mb-1 max-w-full">
|
||||
<div class="">
|
||||
<Tags
|
||||
tags={info?.meta?.tags ?? []}
|
||||
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>
|
||||
</div>
|
||||
|
||||
<div class="my-2">
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
||||
<AccessControl
|
||||
bind:accessControl
|
||||
accessRoles={['read', 'write']}
|
||||
share={$user?.permissions?.sharing?.models || $user?.role === 'admin'}
|
||||
sharePublic={$user?.permissions?.sharing?.public_models || $user?.role === 'admin'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-1.5" />
|
||||
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
|
||||
|
||||
<div class="my-2">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Model Params')}</div>
|
||||
<div class=" self-center text-xs font-medium text-gray-500">
|
||||
{$i18n.t('Model Params')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
|
|
@ -648,12 +674,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
|
||||
|
||||
<div class="my-2">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class=" self-center text-sm font-medium">
|
||||
<div class=" self-center text-xs font-medium text-gray-500">
|
||||
{$i18n.t('Prompts')}
|
||||
</div>
|
||||
|
||||
|
|
@ -682,8 +708,6 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-1.5" />
|
||||
|
||||
<div class="my-2">
|
||||
<Knowledge bind:selectedItems={knowledge} />
|
||||
</div>
|
||||
|
|
@ -743,7 +767,33 @@
|
|||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="my-2 text-gray-300 dark:text-gray-700">
|
||||
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
|
||||
|
||||
<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-medium">
|
||||
{#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-medium">{$i18n.t('JSON Preview')}</div>
|
||||
|
||||
|
|
@ -774,30 +824,6 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="my-2 flex justify-end pb-20">
|
||||
<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-medium">
|
||||
{#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>
|
||||
</form>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
import { getContext } from 'svelte';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let promptSuggestions = [];
|
||||
|
|
@ -25,194 +27,163 @@
|
|||
</script>
|
||||
|
||||
<div class=" space-y-3">
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-xs">
|
||||
<div class="flex w-full justify-between mb-1.5">
|
||||
<div class=" self-center text-xs flex-1 shrink-0 w-full">
|
||||
{$i18n.t('Default Prompt Suggestions')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
|
||||
promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
<div class="flex justify-end gap-2">
|
||||
<input
|
||||
id="prompt-suggestions-import-input"
|
||||
type="file"
|
||||
accept=".json"
|
||||
hidden
|
||||
on:change={(e) => {
|
||||
const files = e.target.files;
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(files);
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
try {
|
||||
let suggestions = JSON.parse(event.target.result);
|
||||
|
||||
suggestions = suggestions.map((s) => {
|
||||
if (typeof s.title === 'string') {
|
||||
s.title = [s.title, ''];
|
||||
} else if (!Array.isArray(s.title)) {
|
||||
s.title = ['', ''];
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
promptSuggestions = [...promptSuggestions, ...suggestions];
|
||||
} catch (error) {
|
||||
toast.error($i18n.t('Invalid JSON file'));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(files[0]);
|
||||
|
||||
e.target.value = ''; // Reset the input value
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 py-1 rounded-xl bg-transparent dark:text-gray-200 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
const input = document.getElementById('prompt-suggestions-import-input');
|
||||
if (input) {
|
||||
input.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class=" self-center font-medium line-clamp-1">
|
||||
{$i18n.t('Import')}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{#if promptSuggestions.length}
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 py-1 rounded-xl bg-transparent dark:text-gray-200 transition"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
let blob = new Blob([JSON.stringify(promptSuggestions)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
saveAs(blob, `prompt-suggestions-export-${Date.now()}.json`);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center font-medium line-clamp-1">
|
||||
{$i18n.t('Export')}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class=" px-1.5 rounded-xl transition font-medium text-sm flex items-center"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
|
||||
promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3" strokeWidth="2.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if _promptSuggestions.length > 0}
|
||||
<div class="grid lg:grid-cols-2 flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each _promptSuggestions as prompt, promptIdx}
|
||||
<div
|
||||
class=" flex border rounded-3xl border-gray-100 dark:border-gray-800 dark:bg-gray-850 py-1.5"
|
||||
class=" flex border rounded-2xl border-gray-100 dark:border-gray-850 bg-transparent p-2"
|
||||
>
|
||||
<div class="flex flex-col flex-1 pl-1">
|
||||
<div class="py-1 gap-1">
|
||||
<input
|
||||
class="px-3 text-sm font-medium w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
|
||||
bind:value={prompt.title[0]}
|
||||
/>
|
||||
<div class="flex flex-col md:flex-row w-full gap-1 md:gap-2 px-2">
|
||||
<div class="gap-0.5 min-w-60">
|
||||
<Tooltip content={$i18n.t('e.g. Tell me a fun fact')} placement="top-start">
|
||||
<input
|
||||
class="text-sm w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Title')}
|
||||
bind:value={prompt.title[0]}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<input
|
||||
class="px-3 text-xs w-full bg-transparent outline-hidden text-gray-600 dark:text-gray-400"
|
||||
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
|
||||
bind:value={prompt.title[1]}
|
||||
/>
|
||||
<Tooltip content={$i18n.t('e.g. about the Roman Empire')} placement="top-start">
|
||||
<input
|
||||
class="text-sm w-full bg-transparent outline-hidden text-gray-600 dark:text-gray-400"
|
||||
placeholder={$i18n.t('Subtitle')}
|
||||
bind:value={prompt.title[1]}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-50 dark:border-gray-850 my-0.5" />
|
||||
|
||||
<textarea
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-hidden resize-none"
|
||||
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
|
||||
rows="4"
|
||||
bind:value={prompt.content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<button
|
||||
class="p-3"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
promptSuggestions.splice(promptIdx, 1);
|
||||
promptSuggestions = promptSuggestions;
|
||||
}}
|
||||
<Tooltip
|
||||
className="w-full self-center items-center flex"
|
||||
content={$i18n.t('e.g. Tell me a fun fact about the Roman Empire')}
|
||||
placement="top-start"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<textarea
|
||||
class="text-sm w-full bg-transparent outline-hidden resize-none"
|
||||
placeholder={$i18n.t('Prompt')}
|
||||
rows="2"
|
||||
bind:value={prompt.content}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 self-start"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
promptSuggestions.splice(promptIdx, 1);
|
||||
promptSuggestions = promptSuggestions;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-center w-full py-2">{$i18n.t('No suggestion prompts')}</div>
|
||||
<div class="text-xs text-center w-full text-gray-500 mb-1.5">
|
||||
{$i18n.t('No suggestion prompts')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 mt-2">
|
||||
<input
|
||||
id="prompt-suggestions-import-input"
|
||||
type="file"
|
||||
accept=".json"
|
||||
hidden
|
||||
on:change={(e) => {
|
||||
const files = e.target.files;
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(files);
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
try {
|
||||
let suggestions = JSON.parse(event.target.result);
|
||||
|
||||
suggestions = suggestions.map((s) => {
|
||||
if (typeof s.title === 'string') {
|
||||
s.title = [s.title, ''];
|
||||
} else if (!Array.isArray(s.title)) {
|
||||
s.title = ['', ''];
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
promptSuggestions = [...promptSuggestions, ...suggestions];
|
||||
} catch (error) {
|
||||
toast.error($i18n.t('Invalid JSON file'));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(files[0]);
|
||||
|
||||
e.target.value = ''; // Reset the input value
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
const input = document.getElementById('prompt-suggestions-import-input');
|
||||
if (input) {
|
||||
input.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
||||
{$i18n.t('Import Prompt Suggestions')}
|
||||
</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-3.5 h-3.5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{#if promptSuggestions.length}
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
let blob = new Blob([JSON.stringify(promptSuggestions)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
saveAs(blob, `prompt-suggestions-export-${Date.now()}.json`);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
||||
{$i18n.t('Export Prompt Suggestions')} ({promptSuggestions.length})
|
||||
</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-3.5 h-3.5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,16 +25,12 @@
|
|||
|
||||
<div>
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Tools')}</div>
|
||||
<div class=" self-center text-xs font-medium text-gray-500">{$i18n.t('Tools')}</div>
|
||||
</div>
|
||||
|
||||
<div class=" text-xs dark:text-gray-500">
|
||||
{$i18n.t('To select toolkits here, add them to the "Tools" workspace first.')}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col mb-1">
|
||||
{#if tools.length > 0}
|
||||
<div class=" flex items-center mt-2 flex-wrap">
|
||||
<div class=" flex items-center flex-wrap">
|
||||
{#each Object.keys(_tools) as tool, toolIdx}
|
||||
<div class=" flex items-center gap-2 mr-3">
|
||||
<div class="self-center flex items-center">
|
||||
|
|
@ -57,4 +53,8 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" text-xs dark:text-gray-700">
|
||||
{$i18n.t('To select toolkits here, add them to the "Tools" workspace first.')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
<div class=" rounded-lg flex flex-col gap-2">
|
||||
<div class="">
|
||||
<div class=" text-sm font-medium mb-1.5">{$i18n.t('Visibility')}</div>
|
||||
<div class=" text-xs font-medium mb-2.5 text-gray-500">{$i18n.t('Visibility')}</div>
|
||||
|
||||
<div class="flex gap-2.5 items-center mb-1">
|
||||
<div>
|
||||
|
|
@ -149,8 +149,8 @@
|
|||
)}
|
||||
<div>
|
||||
<div class="">
|
||||
<div class="flex justify-between mb-1.5">
|
||||
<div class="text-sm font-medium">
|
||||
<div class="flex justify-between mb-2.5">
|
||||
<div class="text-xs font-medium text-gray-500">
|
||||
{$i18n.t('Groups')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -224,7 +224,7 @@
|
|||
<div class="flex flex-1 items-center">
|
||||
<div class="w-full px-0.5">
|
||||
<select
|
||||
class="outline-hidden bg-transparent text-sm rounded-lg block w-full pr-10 max-w-full
|
||||
class="outline-hidden bg-transparent text-sm block w-full pr-10 max-w-full
|
||||
{selectedGroupId ? '' : 'text-gray-500'}
|
||||
dark:placeholder-gray-500"
|
||||
bind:value={selectedGroupId}
|
||||
|
|
|
|||
79
src/lib/components/workspace/common/Visibility.svelte
Normal file
79
src/lib/components/workspace/common/Visibility.svelte
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let onChange: Function = () => {};
|
||||
export let state = 'private';
|
||||
</script>
|
||||
|
||||
<div class=" rounded-lg flex flex-col gap-2">
|
||||
<div class="">
|
||||
<div class=" text-xs font-medium mb-2.5 text-gray-500">{$i18n.t('Visibility')}</div>
|
||||
|
||||
<div class="flex gap-2.5 items-center mb-1">
|
||||
<div>
|
||||
<div class=" p-2 bg-black/5 dark:bg-white/5 rounded-full">
|
||||
{#if state === 'private'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.115 5.19l.319 1.913A6 6 0 008.11 10.36L9.75 12l-.387.775c-.217.433-.132.956.21 1.298l1.348 1.348c.21.21.329.497.329.795v1.089c0 .426.24.815.622 1.006l.153.076c.433.217.956.132 1.298-.21l.723-.723a8.7 8.7 0 002.288-4.042 1.087 1.087 0 00-.358-1.099l-1.33-1.108c-.251-.21-.582-.299-.905-.245l-1.17.195a1.125 1.125 0 01-.98-.314l-.295-.295a1.125 1.125 0 010-1.591l.13-.132a1.125 1.125 0 011.3-.21l.603.302a.809.809 0 001.086-1.086L14.25 7.5l1.256-.837a4.5 4.5 0 001.528-1.732l.146-.292M6.115 5.19A9 9 0 1017.18 4.64M6.115 5.19A8.965 8.965 0 0112 3c1.929 0 3.716.607 5.18 1.64"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<select
|
||||
id="models"
|
||||
class="outline-hidden bg-transparent text-sm font-medium block w-fit pr-10 max-w-full placeholder-gray-400"
|
||||
value={state === 'private' ? 'private' : 'public'}
|
||||
on:change={(e) => {
|
||||
if (e.target.value === 'public') {
|
||||
state = 'public';
|
||||
} else {
|
||||
state = 'private';
|
||||
}
|
||||
onChange(state);
|
||||
}}
|
||||
>
|
||||
<option class=" text-gray-700" value="public" selected>{$i18n.t('Public')}</option>
|
||||
<option class=" text-gray-700" value="private" selected>{$i18n.t('Private')}</option>
|
||||
</select>
|
||||
|
||||
<div class=" text-xs text-gray-400 font-medium">
|
||||
{#if state === 'private'}
|
||||
{$i18n.t('Only select users and groups with permission can access')}
|
||||
{:else}
|
||||
{$i18n.t('Accessible to all users')}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Reference in a new issue