This commit is contained in:
Timothy Jaeryang Baek
2026-06-29 13:03:30 -05:00
parent 517cd8d102
commit 55cb98ff56
3 changed files with 210 additions and 211 deletions
+7 -6
View File
@@ -2977,12 +2977,13 @@
const loaded = chat?.chat ?? {};
if (equal(params, loaded.params ?? {}) && equal(chatFiles, loaded.files ?? [])) return;
const res = await updateChatById(localStorage.token, $chatId, { params, files: chatFiles }).catch(
(err) => {
console.error('[controls autosave]', err);
return null;
}
);
const res = await updateChatById(localStorage.token, $chatId, {
params,
files: chatFiles
}).catch((err) => {
console.error('[controls autosave]', err);
return null;
});
// Refresh the dedupe baseline so a later revert still saves.
if (res) chat = res;
};
+203 -203
View File
@@ -1755,43 +1755,43 @@
<div class="flex flex-1 items-center min-w-0 overflow-x-auto scrollbar-none">
{#if showWebSearchButton || showImageGenerationButton || showCodeInterpreterButton || showToolsButton || showSkillsButton || (toggleFilters && toggleFilters.length > 0)}
<IntegrationsMenu
selectedModels={selectedModelIds}
{toggleFilters}
{showWebSearchButton}
{showImageGenerationButton}
{showCodeInterpreterButton}
bind:selectedToolIds
bind:selectedSkillIds
bind:selectedFilterIds
bind:webSearchEnabled
bind:imageGenerationEnabled
bind:codeInterpreterEnabled
{onWebSearchToggle}
closeOnOutsideClick={integrationsMenuCloseOnOutsideClick}
onShowValves={(e) => {
const { type, id } = e;
selectedValvesType = type;
selectedValvesItemId = id;
showValvesModal = true;
integrationsMenuCloseOnOutsideClick = false;
}}
onClose={async () => {
await tick();
<IntegrationsMenu
selectedModels={selectedModelIds}
{toggleFilters}
{showWebSearchButton}
{showImageGenerationButton}
{showCodeInterpreterButton}
bind:selectedToolIds
bind:selectedSkillIds
bind:selectedFilterIds
bind:webSearchEnabled
bind:imageGenerationEnabled
bind:codeInterpreterEnabled
{onWebSearchToggle}
closeOnOutsideClick={integrationsMenuCloseOnOutsideClick}
onShowValves={(e) => {
const { type, id } = e;
selectedValvesType = type;
selectedValvesItemId = id;
showValvesModal = true;
integrationsMenuCloseOnOutsideClick = false;
}}
onClose={async () => {
await tick();
const chatInput = document.getElementById('chat-input');
chatInput?.focus();
}}
>
<button
type="button"
id="integration-menu-button"
class="bg-transparent hover:bg-gray-100 text-gray-700 dark:text-white dark:hover:bg-gray-800 rounded-full size-8 flex justify-center items-center outline-hidden focus:outline-hidden shrink-0"
aria-label={$i18n.t('Integrations')}
const chatInput = document.getElementById('chat-input');
chatInput?.focus();
}}
>
<Component className="size-4.5" strokeWidth="1.5" />
</button>
</IntegrationsMenu>
<button
type="button"
id="integration-menu-button"
class="bg-transparent hover:bg-gray-100 text-gray-700 dark:text-white dark:hover:bg-gray-800 rounded-full size-8 flex justify-center items-center outline-hidden focus:outline-hidden shrink-0"
aria-label={$i18n.t('Integrations')}
>
<Component className="size-4.5" strokeWidth="1.5" />
</button>
</IntegrationsMenu>
{/if}
{#if selectedModelIds.length === 1 && $models.find((m) => m.id === selectedModelIds[0])?.has_user_valves}
@@ -1814,190 +1814,190 @@
{/if}
<div class="ml-1 flex gap-1.5 shrink-0">
{#if (selectedToolIds ?? []).length > 0}
<Tooltip
content={$i18n.t('{{COUNT}} Available Tools', {
COUNT: (selectedToolIds ?? []).length
})}
>
<button
class="translate-y-[0.5px] px-1 flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg self-center transition"
aria-label="Available Tools"
type="button"
on:click={() => {
showTools = !showTools;
}}
{#if (selectedToolIds ?? []).length > 0}
<Tooltip
content={$i18n.t('{{COUNT}} Available Tools', {
COUNT: (selectedToolIds ?? []).length
})}
>
<Wrench className="size-4" strokeWidth="1.75" />
<span class="text-sm">
{(selectedToolIds ?? []).length}
</span>
</button>
</Tooltip>
{/if}
{#if (selectedSkillIds ?? []).length > 0}
<Tooltip
content={$i18n.t('{{COUNT}} Available Skills', {
COUNT: (selectedSkillIds ?? []).length
})}
>
<button
class="translate-y-[0.5px] px-1 flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg self-center transition"
aria-label="Available Skills"
type="button"
on:click={() => {
showSkills = !showSkills;
}}
>
<Keyframes className="size-4" strokeWidth="1.75" />
<span class="text-sm">
{(selectedSkillIds ?? []).length}
</span>
</button>
</Tooltip>
{/if}
{#each selectedFilterIds as filterId (filterId)}
{@const filter = toggleFilters.find((f) => f.id === filterId)}
{#if filter}
<Tooltip content={filter?.name} placement="top">
<button
on:click|preventDefault={() => {
if (
filter?.has_user_valves &&
($_user?.role === 'admin' ||
($_user?.permissions?.chat?.valves ?? true))
) {
selectedValvesType = 'function';
selectedValvesItemId = filterId;
showValvesModal = true;
} else {
selectedFilterIds = selectedFilterIds.filter(
(id) => id !== filterId
);
}
}}
class="translate-y-[0.5px] px-1 flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg self-center transition"
aria-label="Available Tools"
type="button"
class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {selectedFilterIds.includes(
filterId
)
? 'text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-600/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '} capitalize"
on:click={() => {
showTools = !showTools;
}}
>
{#if filter?.icon}
<div class="size-4 items-center flex justify-center">
<img
src={filter.icon}
class="size-3.5 {filter.icon.includes('data:image/svg')
? 'dark:invert-[80%]'
: ''}"
style="fill: currentColor;"
alt={filter.name}
/>
</div>
{:else}
<Sparkles className="size-4" strokeWidth="1.75" />
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="hidden group-hover:block"
on:click={(e) => {
e.stopPropagation();
e.preventDefault();
selectedFilterIds = selectedFilterIds.filter(
(id) => id !== filterId
);
<Wrench className="size-4" strokeWidth="1.75" />
<span class="text-sm">
{(selectedToolIds ?? []).length}
</span>
</button>
</Tooltip>
{/if}
{#if (selectedSkillIds ?? []).length > 0}
<Tooltip
content={$i18n.t('{{COUNT}} Available Skills', {
COUNT: (selectedSkillIds ?? []).length
})}
>
<button
class="translate-y-[0.5px] px-1 flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg self-center transition"
aria-label="Available Skills"
type="button"
on:click={() => {
showSkills = !showSkills;
}}
>
<Keyframes className="size-4" strokeWidth="1.75" />
<span class="text-sm">
{(selectedSkillIds ?? []).length}
</span>
</button>
</Tooltip>
{/if}
{#each selectedFilterIds as filterId (filterId)}
{@const filter = toggleFilters.find((f) => f.id === filterId)}
{#if filter}
<Tooltip content={filter?.name} placement="top">
<button
on:click|preventDefault={() => {
if (
filter?.has_user_valves &&
($_user?.role === 'admin' ||
($_user?.permissions?.chat?.valves ?? true))
) {
selectedValvesType = 'function';
selectedValvesItemId = filterId;
showValvesModal = true;
} else {
selectedFilterIds = selectedFilterIds.filter(
(id) => id !== filterId
);
}
}}
type="button"
class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {selectedFilterIds.includes(
filterId
)
? 'text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-600/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '} capitalize"
>
{#if filter?.icon}
<div class="size-4 items-center flex justify-center">
<img
src={filter.icon}
class="size-3.5 {filter.icon.includes('data:image/svg')
? 'dark:invert-[80%]'
: ''}"
style="fill: currentColor;"
alt={filter.name}
/>
</div>
{:else}
<Sparkles className="size-4" strokeWidth="1.75" />
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="hidden group-hover:block"
on:click={(e) => {
e.stopPropagation();
e.preventDefault();
selectedFilterIds = selectedFilterIds.filter(
(id) => id !== filterId
);
}}
>
<XMark className="size-4" strokeWidth="1.75" />
</div>
</button>
</Tooltip>
{/if}
{/each}
{#if webSearchEnabled}
<Tooltip content={$i18n.t('Web Search')} placement="top">
<button
on:click|preventDefault={() => (webSearchEnabled = !webSearchEnabled)}
type="button"
class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {webSearchEnabled ||
($settings?.webSearch ?? false) === 'always'
? ' text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-600/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '}"
>
<GlobeAlt className="size-4" strokeWidth="1.75" />
<div class="hidden group-hover:block">
<XMark className="size-4" strokeWidth="1.75" />
</div>
</button>
</Tooltip>
{/if}
{/each}
{#if webSearchEnabled}
<Tooltip content={$i18n.t('Web Search')} placement="top">
<button
on:click|preventDefault={() => (webSearchEnabled = !webSearchEnabled)}
type="button"
class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {webSearchEnabled ||
($settings?.webSearch ?? false) === 'always'
? ' text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-600/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '}"
>
<GlobeAlt className="size-4" strokeWidth="1.75" />
<div class="hidden group-hover:block">
<XMark className="size-4" strokeWidth="1.75" />
</div>
</button>
</Tooltip>
{/if}
{#if imageGenerationEnabled}
<Tooltip content={$i18n.t('Image')} placement="top">
<button
on:click|preventDefault={() =>
(imageGenerationEnabled = !imageGenerationEnabled)}
type="button"
class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {imageGenerationEnabled
? ' text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-700/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '}"
>
<Photo className="size-4" strokeWidth="1.75" />
<div class="hidden group-hover:block">
<XMark className="size-4" strokeWidth="1.75" />
</div>
</button>
</Tooltip>
{/if}
{#if imageGenerationEnabled}
<Tooltip content={$i18n.t('Image')} placement="top">
<button
on:click|preventDefault={() =>
(imageGenerationEnabled = !imageGenerationEnabled)}
type="button"
class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {imageGenerationEnabled
? ' text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-700/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '}"
>
<Photo className="size-4" strokeWidth="1.75" />
<div class="hidden group-hover:block">
<XMark className="size-4" strokeWidth="1.75" />
</div>
</button>
</Tooltip>
{/if}
{#if codeInterpreterEnabled}
<Tooltip content={$i18n.t('Code Interpreter')} placement="top">
<button
aria-label={codeInterpreterEnabled
? $i18n.t('Disable Code Interpreter')
: $i18n.t('Enable Code Interpreter')}
aria-pressed={codeInterpreterEnabled}
on:click|preventDefault={() =>
(codeInterpreterEnabled = !codeInterpreterEnabled)}
type="button"
class=" group p-[7px] flex gap-1.5 items-center text-sm transition-colors duration-300 max-w-full overflow-hidden {codeInterpreterEnabled
? ' text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-700/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '} {($settings?.highContrastMode ??
false)
? 'm-1'
: 'focus:outline-hidden rounded-full'}"
>
<Terminal className="size-3.5" strokeWidth="2" />
{#if codeInterpreterEnabled}
<Tooltip content={$i18n.t('Code Interpreter')} placement="top">
<button
aria-label={codeInterpreterEnabled
? $i18n.t('Disable Code Interpreter')
: $i18n.t('Enable Code Interpreter')}
aria-pressed={codeInterpreterEnabled}
on:click|preventDefault={() =>
(codeInterpreterEnabled = !codeInterpreterEnabled)}
type="button"
class=" group p-[7px] flex gap-1.5 items-center text-sm transition-colors duration-300 max-w-full overflow-hidden {codeInterpreterEnabled
? ' text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-700/10 border border-sky-200/40 dark:border-sky-500/20'
: 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '} {($settings?.highContrastMode ??
false)
? 'm-1'
: 'focus:outline-hidden rounded-full'}"
>
<Terminal className="size-3.5" strokeWidth="2" />
<div class="hidden group-hover:block">
<XMark className="size-4" strokeWidth="1.75" />
</div>
</button>
</Tooltip>
{/if}
<div class="hidden group-hover:block">
<XMark className="size-4" strokeWidth="1.75" />
</div>
</button>
</Tooltip>
{/if}
{#each pendingOAuthTools as pendingTool (pendingTool.id)}
<Tooltip content={$i18n.t('Click to connect')} placement="top">
<button
on:click|preventDefault={() => {
initiateOAuthRedirect(pendingTool);
}}
type="button"
class="group px-2 py-[5px] flex gap-1.5 items-center text-xs rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden
{#each pendingOAuthTools as pendingTool (pendingTool.id)}
<Tooltip content={$i18n.t('Click to connect')} placement="top">
<button
on:click|preventDefault={() => {
initiateOAuthRedirect(pendingTool);
}}
type="button"
class="group px-2 py-[5px] flex gap-1.5 items-center text-xs rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden
text-amber-600 dark:text-amber-400 bg-amber-50 hover:bg-amber-100 dark:bg-amber-400/10 dark:hover:bg-amber-600/10 border border-amber-200/40 dark:border-amber-500/20"
>
<Wrench className="size-3.5" strokeWidth="1.75" />
<span class="truncate">{pendingTool.name}</span>
</button>
</Tooltip>
{/each}
</div>
>
<Wrench className="size-3.5" strokeWidth="1.75" />
<span class="truncate">{pendingTool.name}</span>
</button>
</Tooltip>
{/each}
</div>
</div>
</div>
@@ -254,8 +254,6 @@
return allModels;
};
const enableAllHandler = async () => {
const allModels = await fetchAllWorkspaceModels();
const modelsToEnable = allModels.filter((m) => !(m.is_active ?? true));