This commit is contained in:
Timothy Jaeryang Baek
2026-07-17 05:40:40 -04:00
parent 21e180182a
commit d027a32ed1
4 changed files with 306 additions and 208 deletions
@@ -71,6 +71,8 @@
let models = null; let models = null;
let modelsConfig = null; let modelsConfig = null;
let modelOrderList: string[] = []; let modelOrderList: string[] = [];
let defaultModelIds: string[] = [];
let defaultPinnedModelIds: string[] = [];
let workspaceModels: ModelListItem[] = []; let workspaceModels: ModelListItem[] = [];
let baseModels: ModelListItem[] = []; let baseModels: ModelListItem[] = [];
@@ -121,6 +123,9 @@
return 'text-gray-500 dark:text-gray-400'; return 'text-gray-500 dark:text-gray-400';
}; };
const isDefaultModel = (model) => defaultModelIds.includes(model.id);
const isDefaultPinnedModel = (model) => defaultPinnedModelIds.includes(model.id);
$: if (models) { $: if (models) {
const modelOrder = new Map(modelOrderList.map((id, idx) => [id, idx])); const modelOrder = new Map(modelOrderList.map((id, idx) => [id, idx]));
@@ -230,6 +235,10 @@
modelsConfig = await getModelsConfig(localStorage.token); modelsConfig = await getModelsConfig(localStorage.token);
modelOrderList = modelsConfig?.MODEL_ORDER_LIST ?? []; modelOrderList = modelsConfig?.MODEL_ORDER_LIST ?? [];
defaultModelIds = (modelsConfig?.DEFAULT_MODELS ?? '').split(',').filter((id) => id);
defaultPinnedModelIds = (modelsConfig?.DEFAULT_PINNED_MODELS ?? '')
.split(',')
.filter((id) => id);
tags = await getBaseModelTags(localStorage.token); tags = await getBaseModelTags(localStorage.token);
if (selectedTag && !tags.includes(selectedTag)) { if (selectedTag && !tags.includes(selectedTag)) {
@@ -282,8 +291,8 @@
savingModelOrder = true; savingModelOrder = true;
const res = await setModelsConfig(localStorage.token, { const res = await setModelsConfig(localStorage.token, {
DEFAULT_MODELS: modelsConfig?.DEFAULT_MODELS ?? null, DEFAULT_MODELS: defaultModelIds.join(','),
DEFAULT_PINNED_MODELS: modelsConfig?.DEFAULT_PINNED_MODELS ?? null, DEFAULT_PINNED_MODELS: defaultPinnedModelIds.join(','),
MODEL_ORDER_LIST: orderedModelIds, MODEL_ORDER_LIST: orderedModelIds,
DEFAULT_MODEL_METADATA: modelsConfig?.DEFAULT_MODEL_METADATA ?? null, DEFAULT_MODEL_METADATA: modelsConfig?.DEFAULT_MODEL_METADATA ?? null,
DEFAULT_MODEL_PARAMS: modelsConfig?.DEFAULT_MODEL_PARAMS ?? null DEFAULT_MODEL_PARAMS: modelsConfig?.DEFAULT_MODEL_PARAMS ?? null
@@ -307,6 +316,65 @@
savingModelOrder = false; savingModelOrder = false;
}; };
const saveModelDefaults = async (
nextDefaultModelIds: string[],
nextDefaultPinnedModelIds: string[],
successMessage: string
) => {
const previousDefaultModelIds = defaultModelIds;
const previousDefaultPinnedModelIds = defaultPinnedModelIds;
defaultModelIds = nextDefaultModelIds;
defaultPinnedModelIds = nextDefaultPinnedModelIds;
const res = await setModelsConfig(localStorage.token, {
DEFAULT_MODELS: nextDefaultModelIds.join(','),
DEFAULT_PINNED_MODELS: nextDefaultPinnedModelIds.join(','),
MODEL_ORDER_LIST: modelsConfig?.MODEL_ORDER_LIST ?? [],
DEFAULT_MODEL_METADATA: modelsConfig?.DEFAULT_MODEL_METADATA ?? null,
DEFAULT_MODEL_PARAMS: modelsConfig?.DEFAULT_MODEL_PARAMS ?? null
}).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
modelsConfig = res;
toast.success(successMessage);
} else {
defaultModelIds = previousDefaultModelIds;
defaultPinnedModelIds = previousDefaultPinnedModelIds;
}
};
const toggleDefaultModelHandler = async (model) => {
const nextDefaultModelIds = isDefaultModel(model)
? defaultModelIds.filter((id) => id !== model.id)
: [...new Set([...defaultModelIds, model.id])];
await saveModelDefaults(
nextDefaultModelIds,
defaultPinnedModelIds,
isDefaultModel(model)
? $i18n.t('Model removed from selected models')
: $i18n.t('Model added to selected models')
);
};
const toggleDefaultPinnedModelHandler = async (model) => {
const nextDefaultPinnedModelIds = isDefaultPinnedModel(model)
? defaultPinnedModelIds.filter((id) => id !== model.id)
: [...new Set([...defaultPinnedModelIds, model.id])];
await saveModelDefaults(
defaultModelIds,
nextDefaultPinnedModelIds,
isDefaultPinnedModel(model)
? $i18n.t('Model removed from pinned models')
: $i18n.t('Model added to pinned models')
);
};
const positionChangeHandler = async (event) => { const positionChangeHandler = async (event) => {
const { oldIndex, newIndex, item } = event; const { oldIndex, newIndex, item } = event;
@@ -803,7 +871,7 @@
class="flex cursor-pointer transition w-full px-2 py-1 rounded-xl hover:bg-gray-50/70 dark:hover:bg-gray-850/50 {model class="flex cursor-pointer transition w-full px-2 py-1 rounded-xl hover:bg-gray-50/70 dark:hover:bg-gray-850/50 {model
?.meta?.hidden ?.meta?.hidden
? 'opacity-50 dark:opacity-50' ? 'opacity-50 dark:opacity-50'
: ''}" : ''}"
id="model-item-{model.id}" id="model-item-{model.id}"
> >
<div class="self-center pr-1 text-gray-400 dark:text-gray-600"> <div class="self-center pr-1 text-gray-400 dark:text-gray-600">
@@ -876,6 +944,22 @@
> >
{modelAccessLabel(model)} {modelAccessLabel(model)}
</span> </span>
{#if isDefaultModel(model)}
<span
class="shrink-0 text-[11px] font-normal leading-4 text-gray-500 dark:text-gray-400"
>
{$i18n.t('Selected')}
</span>
{/if}
{#if isDefaultPinnedModel(model)}
<span
class="shrink-0 text-[11px] font-normal leading-4 text-gray-500 dark:text-gray-400"
>
{$i18n.t('Pinned')}
</span>
{/if}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@@ -934,6 +1018,14 @@
privacyHandler={() => { privacyHandler={() => {
toggleModelPrivacyHandler(model); toggleModelPrivacyHandler(model);
}} }}
isDefaultSelected={isDefaultModel(model)}
isDefaultPinned={isDefaultPinnedModel(model)}
defaultSelectedHandler={() => {
toggleDefaultModelHandler(model);
}}
defaultPinnedHandler={() => {
toggleDefaultPinnedModelHandler(model);
}}
pinModelHandler={() => { pinModelHandler={() => {
pinModelHandler(model.id); pinModelHandler(model.id);
}} }}
@@ -14,6 +14,7 @@
import ArrowUpCircle from '$lib/components/icons/ArrowUpCircle.svelte'; import ArrowUpCircle from '$lib/components/icons/ArrowUpCircle.svelte';
import Pin from '$lib/components/icons/Pin.svelte'; import Pin from '$lib/components/icons/Pin.svelte';
import PinSlash from '$lib/components/icons/PinSlash.svelte'; import PinSlash from '$lib/components/icons/PinSlash.svelte';
import Check from '$lib/components/icons/Check.svelte';
import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte'; import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte'; import LockClosed from '$lib/components/icons/LockClosed.svelte';
@@ -28,6 +29,10 @@
export let exportHandler: Function; export let exportHandler: Function;
export let hideHandler: Function; export let hideHandler: Function;
export let privacyHandler: Function; export let privacyHandler: Function;
export let isDefaultSelected = false;
export let isDefaultPinned = false;
export let defaultSelectedHandler: Function;
export let defaultPinnedHandler: Function;
export let pinModelHandler: Function; export let pinModelHandler: Function;
export let copyLinkHandler: Function; export let copyLinkHandler: Function;
export let cloneHandler: Function; export let cloneHandler: Function;
@@ -108,6 +113,46 @@
</div> </div>
</button> </button>
<button
class="select-none flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl"
on:click={async () => {
show = false;
await defaultSelectedHandler();
}}
>
<Check className="size-3.5" />
<div class="flex items-center">
{#if isDefaultSelected}
{$i18n.t('Remove Selected Model')}
{:else}
{$i18n.t('Set as Selected Model')}
{/if}
</div>
</button>
<button
class="select-none flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl"
on:click={async () => {
show = false;
await defaultPinnedHandler();
}}
>
{#if isDefaultPinned}
<PinSlash className="size-3.5" />
{:else}
<Pin className="size-3.5" />
{/if}
<div class="flex items-center">
{#if isDefaultPinned}
{$i18n.t('Remove Pinned Model')}
{:else}
{$i18n.t('Set as Pinned Model')}
{/if}
</div>
</button>
<button <button
class="select-none flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl" class="select-none flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl"
on:click={() => { on:click={() => {
@@ -4,7 +4,7 @@
import { getContext, onMount } from 'svelte'; import { getContext, onMount } from 'svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
import { models, config as _config } from '$lib/stores'; import { config as _config } from '$lib/stores';
import { DEFAULT_CAPABILITIES } from '$lib/constants'; import { DEFAULT_CAPABILITIES } from '$lib/constants';
import { getModelsConfig, setModelsConfig, setDefaultPromptSuggestions } from '$lib/apis/configs'; import { getModelsConfig, setModelsConfig, setDefaultPromptSuggestions } from '$lib/apis/configs';
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
@@ -14,7 +14,6 @@
import ChevronUp from '$lib/components/icons/ChevronUp.svelte'; import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
import XMark from '$lib/components/icons/XMark.svelte'; import XMark from '$lib/components/icons/XMark.svelte';
import ModelSelector from './ModelSelector.svelte';
import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte'; import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
import Capabilities from '$lib/components/workspace/Models/Capabilities.svelte'; import Capabilities from '$lib/components/workspace/Models/Capabilities.svelte';
@@ -29,10 +28,6 @@
let config = null; let config = null;
let defaultModelIds = [];
let defaultPinnedModelIds = [];
let modelIds = []; let modelIds = [];
let loading = false; let loading = false;
@@ -52,30 +47,7 @@
const init = async () => { const init = async () => {
config = await getModelsConfig(localStorage.token); config = await getModelsConfig(localStorage.token);
if (config?.DEFAULT_MODELS) { modelIds = config.MODEL_ORDER_LIST || [];
defaultModelIds = (config?.DEFAULT_MODELS).split(',').filter((id) => id);
} else {
defaultModelIds = [];
}
if (config?.DEFAULT_PINNED_MODELS) {
defaultPinnedModelIds = (config?.DEFAULT_PINNED_MODELS).split(',').filter((id) => id);
} else {
defaultPinnedModelIds = [];
}
const modelOrderList = config.MODEL_ORDER_LIST || [];
const allModelIds = $models.map((model) => model.id);
// Create a Set for quick lookup of ordered IDs
const orderedSet = new Set(modelOrderList);
modelIds = [
// Add all IDs from MODEL_ORDER_LIST that exist in allModelIds
...modelOrderList.filter((id) => orderedSet.has(id) && allModelIds.includes(id)),
// Add remaining IDs not in MODEL_ORDER_LIST, sorted alphabetically
...allModelIds.filter((id) => !orderedSet.has(id)).sort((a, b) => a.localeCompare(b))
];
const savedMeta = config?.DEFAULT_MODEL_METADATA; const savedMeta = config?.DEFAULT_MODEL_METADATA;
if (savedMeta && Object.keys(savedMeta).length > 0) { if (savedMeta && Object.keys(savedMeta).length > 0) {
@@ -101,8 +73,8 @@
}; };
const res = await setModelsConfig(localStorage.token, { const res = await setModelsConfig(localStorage.token, {
DEFAULT_MODELS: defaultModelIds.join(','), DEFAULT_MODELS: config?.DEFAULT_MODELS ?? null,
DEFAULT_PINNED_MODELS: defaultPinnedModelIds.join(','), DEFAULT_PINNED_MODELS: config?.DEFAULT_PINNED_MODELS ?? null,
MODEL_ORDER_LIST: modelIds, MODEL_ORDER_LIST: modelIds,
DEFAULT_MODEL_METADATA: metadata, DEFAULT_MODEL_METADATA: metadata,
DEFAULT_MODEL_PARAMS: Object.fromEntries( DEFAULT_MODEL_PARAMS: Object.fromEntries(
@@ -173,147 +145,123 @@
<div class="flex-1 mt-1 lg:mt-1 lg:h-[30rem] lg:max-h-[30rem] flex flex-col min-w-0"> <div class="flex-1 mt-1 lg:mt-1 lg:h-[30rem] lg:max-h-[30rem] flex flex-col min-w-0">
<div class="w-full h-full overflow-y-auto overflow-x-hidden scrollbar-hidden"> <div class="w-full h-full overflow-y-auto overflow-x-hidden scrollbar-hidden">
<ModelSelector <div>
title={$i18n.t('Selected Models')} <button
tooltip={$i18n.t( class="flex w-full justify-between items-center"
'Set the default models that are automatically selected for all users when a new chat is created.' type="button"
)} on:click={() => {
models={$models.filter((model) => !(model?.info?.meta?.hidden ?? false))} showDefaultPromptSuggestions = !showDefaultPromptSuggestions;
bind:modelIds={defaultModelIds} }}
/> >
<div class="text-xs text-gray-500 font-normal">
{$i18n.t('Prompt Suggestions')}
</div>
<div>
{#if showDefaultPromptSuggestions}
<ChevronUp className="size-3" />
{:else}
<ChevronDown className="size-3" />
{/if}
</div>
</button>
<hr class=" border-gray-50 dark:border-gray-800/10 my-2.5 w-full" /> {#if showDefaultPromptSuggestions}
<div class="mt-2">
<PromptSuggestions bind:promptSuggestions />
<ModelSelector {#if promptSuggestions.length > 0}
title={$i18n.t('Pinned Models')} <div class="text-xs text-left w-full mt-2 text-gray-500">
tooltip={$i18n.t( {$i18n.t(
'Set the models that are automatically pinned to the sidebar for all users.' 'Adjusting these settings will apply changes universally to all users.'
)} )}
models={$models.filter((model) => !(model?.info?.meta?.hidden ?? false))} </div>
bind:modelIds={defaultPinnedModelIds} {/if}
/> </div>
{/if}
</div>
<hr class=" border-gray-50 dark:border-gray-800/10 my-2.5 w-full" /> <hr class=" border-gray-50 dark:border-gray-800/10 my-2.5 w-full" />
<div> <div>
<button <button
class="flex w-full justify-between items-center" class="flex w-full justify-between items-center"
type="button" type="button"
on:click={() => { on:click={() => {
showDefaultPromptSuggestions = !showDefaultPromptSuggestions; showDefaultCapabilities = !showDefaultCapabilities;
}} }}
> >
<div class="text-xs text-gray-500 font-normal"> <div class="text-xs text-gray-500 font-normal">
{$i18n.t('Prompt Suggestions')} {$i18n.t('Model Capabilities')}
</div> </div>
<div> <div>
{#if showDefaultPromptSuggestions} {#if showDefaultCapabilities}
<ChevronUp className="size-3" /> <ChevronUp className="size-3" />
{:else} {:else}
<ChevronDown className="size-3" /> <ChevronDown className="size-3" />
{/if} {/if}
</div> </div>
</button> </button>
{#if showDefaultPromptSuggestions} {#if showDefaultCapabilities}
<div class="mt-2"> <div class="mt-2">
<PromptSuggestions bind:promptSuggestions /> <Capabilities bind:capabilities={defaultCapabilities} />
{#if promptSuggestions.length > 0} {#if Object.keys(defaultCapabilities).filter((key) => defaultCapabilities[key]).length > 0}
<div class="text-xs text-left w-full mt-2 text-gray-500"> {@const availableFeatures = Object.entries(defaultCapabilities)
{$i18n.t( .filter(
'Adjusting these settings will apply changes universally to all users.' ([key, value]) =>
)} value &&
</div> ['web_search', 'code_interpreter', 'image_generation'].includes(key)
{/if} )
</div> .map(([key, value]) => key)}
{/if}
</div>
<hr class=" border-gray-50 dark:border-gray-800/10 my-2.5 w-full" /> {#if availableFeatures.length > 0}
<div>
<button
class="flex w-full justify-between items-center"
type="button"
on:click={() => {
showDefaultCapabilities = !showDefaultCapabilities;
}}
>
<div class="text-xs text-gray-500 font-normal">
{$i18n.t('Model Capabilities')}
</div>
<div>
{#if showDefaultCapabilities}
<ChevronUp className="size-3" />
{:else}
<ChevronDown className="size-3" />
{/if}
</div>
</button>
{#if showDefaultCapabilities}
<div class="mt-2">
<Capabilities bind:capabilities={defaultCapabilities} />
{#if Object.keys(defaultCapabilities).filter((key) => defaultCapabilities[key]).length > 0}
{@const availableFeatures = Object.entries(defaultCapabilities)
.filter(
([key, value]) =>
value &&
['web_search', 'code_interpreter', 'image_generation'].includes(
key
)
)
.map(([key, value]) => key)}
{#if availableFeatures.length > 0}
<div class="mt-4">
<DefaultFeatures
{availableFeatures}
bind:featureIds={defaultFeatureIds}
/>
</div>
{/if}
{/if}
{#if defaultCapabilities.builtin_tools}
<div class="mt-4"> <div class="mt-4">
<BuiltinTools bind:builtinTools /> <DefaultFeatures
{availableFeatures}
bind:featureIds={defaultFeatureIds}
/>
</div> </div>
{/if} {/if}
</div> {/if}
{/if}
</div>
<hr class=" border-gray-50 dark:border-gray-800/10 my-2.5 w-full" /> {#if defaultCapabilities.builtin_tools}
<div class="mt-4">
<BuiltinTools bind:builtinTools />
</div>
{/if}
</div>
{/if}
</div>
<div> <hr class=" border-gray-50 dark:border-gray-800/10 my-2.5 w-full" />
<button
class="flex w-full justify-between items-center"
type="button"
on:click={() => {
showDefaultParams = !showDefaultParams;
}}
>
<div class="text-xs text-gray-500 font-normal">
{$i18n.t('Model Parameters')}
</div>
<div>
{#if showDefaultParams}
<ChevronUp className="size-3" />
{:else}
<ChevronDown className="size-3" />
{/if}
</div>
</button>
{#if showDefaultParams} <div>
<div class="mt-2"> <button
<AdvancedParams admin={true} custom={true} bind:params={defaultParams} /> class="flex w-full justify-between items-center"
</div> type="button"
{/if} on:click={() => {
</div> showDefaultParams = !showDefaultParams;
}}
>
<div class="text-xs text-gray-500 font-normal">
{$i18n.t('Model Parameters')}
</div>
<div>
{#if showDefaultParams}
<ChevronUp className="size-3" />
{:else}
<ChevronDown className="size-3" />
{/if}
</div>
</button>
{#if showDefaultParams}
<div class="mt-2">
<AdvancedParams admin={true} custom={true} bind:params={defaultParams} />
</div>
{/if}
</div>
</div> </div>
<div class="flex justify-end items-center pt-3 text-sm font-normal gap-1.5"> <div class="flex justify-end items-center pt-3 text-sm font-normal gap-1.5">
@@ -2,6 +2,7 @@
import { onMount, tick, getContext } from 'svelte'; import { onMount, tick, getContext } from 'svelte';
import type { Writable } from 'svelte/store'; import type { Writable } from 'svelte/store';
import type { i18n as i18nType } from 'i18next'; import type { i18n as i18nType } from 'i18next';
import { goto } from '$app/navigation';
import Textarea from '$lib/components/common/Textarea.svelte'; import Textarea from '$lib/components/common/Textarea.svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
@@ -15,6 +16,7 @@
import Spinner from '$lib/components/common/Spinner.svelte'; import Spinner from '$lib/components/common/Spinner.svelte';
import Modal from '$lib/components/common/Modal.svelte'; import Modal from '$lib/components/common/Modal.svelte';
import XMark from '$lib/components/icons/XMark.svelte'; import XMark from '$lib/components/icons/XMark.svelte';
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
import { import {
getPromptHistory, getPromptHistory,
setProductionPromptVersion, setProductionPromptVersion,
@@ -382,9 +384,19 @@
{#if edit} {#if edit}
<!-- Edit mode: Read-only view with history --> <!-- Edit mode: Read-only view with history -->
<div class="flex flex-col w-full h-full max-h-[100dvh]"> <div class="flex h-full max-h-[100dvh] w-full flex-col">
<!-- Header --> <button
<div class="flex items-start justify-between gap-4 shrink-0"> 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/prompts');
}}
>
<ChevronLeft className="size-3" strokeWidth="2" />
<span>{$i18n.t('Back')}</span>
</button>
<div class="flex shrink-0 items-start justify-between gap-3 pb-2">
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<input <input
class="w-full bg-transparent text-sm outline-hidden" class="w-full bg-transparent text-sm outline-hidden"
@@ -394,49 +406,35 @@
{disabled} {disabled}
/> />
<div class="flex w-full flex-1 items-center gap-0.5 text-xs text-gray-500"> <div class="mt-0.5 flex min-w-0 items-center gap-2 text-xs text-gray-500">
<span>/</span> <div class="flex min-w-0 flex-1 items-center gap-0.5">
<input <span>/</span>
class="bg-transparent outline-hidden" <input
placeholder={$i18n.t('command')} class="min-w-0 flex-1 bg-transparent outline-hidden"
bind:value={command} placeholder={$i18n.t('command')}
on:input={debouncedSaveMetadata} bind:value={command}
{disabled} on:input={debouncedSaveMetadata}
/> {disabled}
/>
</div>
</div> </div>
</div> </div>
<div> <div class="flex shrink-0 items-center gap-1.5 pr-0.5">
<div class="flex items-center gap-2 shrink-0 justify-end"> {#if !disabled}
{#if !disabled} <button
<button class="flex shrink-0 items-center gap-1 rounded-lg bg-gray-50 px-2 py-1 text-xs font-normal text-gray-900 transition ring-1 ring-gray-200 hover:bg-gray-100 dark:bg-gray-850 dark:text-gray-100 dark:ring-gray-800 dark:hover:bg-gray-800"
class="flex shrink-0 items-center rounded-lg bg-gray-50 px-2 py-1 text-xs text-gray-900 ring-1 ring-gray-200 transition hover:bg-gray-100 dark:bg-gray-850 dark:text-gray-100 dark:ring-gray-800 dark:hover:bg-gray-800" on:click={() => (showEditModal = true)}
on:click={() => (showEditModal = true)} >
> {$i18n.t('Edit')}
{$i18n.t('Edit')} </button>
</button>
<AccessButton on:click={() => (showAccessControlModal = true)} /> <AccessButton on:click={() => (showAccessControlModal = true)} />
{:else} {:else}
<span class="text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded-full" <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 {$i18n.t('Read Only')}
> </span>
{/if} {/if}
</div>
<div>
<Tooltip content={$i18n.t('Click to copy ID')}>
<button
class="text-xs text-gray-500 font-mono px-2 py-1 rounded-lg cursor-pointer hover:underline transition"
on:click={() => {
copyToClipboard(prompt.id);
toast.success($i18n.t('ID copied to clipboard'));
}}
>
{prompt.id}
</button>
</Tooltip>
</div>
</div> </div>
</div> </div>
@@ -456,6 +454,18 @@
}} }}
/> />
</div> </div>
<Tooltip content={$i18n.t('Click to copy ID')}>
<button
class="min-w-0 max-w-[14rem] shrink-0 truncate rounded-md px-1 py-0.5 font-mono text-xs text-gray-400 transition hover:text-gray-700 dark:hover:text-gray-300"
on:click={() => {
copyToClipboard(prompt.id);
toast.success($i18n.t('ID copied to clipboard'));
}}
>
{prompt.id}
</button>
</Tooltip>
</div> </div>
<div class="flex flex-1 flex-col gap-3 overflow-hidden pb-4 md:flex-row"> <div class="flex flex-1 flex-col gap-3 overflow-hidden pb-4 md:flex-row">
@@ -517,9 +527,12 @@
</button> </button>
</div> </div>
<!-- Scrollable content --> <!-- Scrollable content -->
<div class="h-full overflow-y-auto rounded-lg bg-transparent px-1 py-1"> <div
<pre class="text-xs whitespace-pre-wrap font-mono pr-8">{selectedHistoryEntry?.snapshot class="h-full overflow-y-auto rounded-lg bg-gray-50/60 px-3 py-2 dark:bg-white/[0.03]"
?.content || content}</pre> >
<pre
class="whitespace-pre-wrap pr-8 font-mono text-[11px] leading-relaxed">{selectedHistoryEntry
?.snapshot?.content || content}</pre>
</div> </div>
</div> </div>
</div> </div>