feat: UI improvements
This commit is contained in:
parent
3507687382
commit
3994a39675
|
|
@ -105,6 +105,11 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
function getEnabledToolCount(group: ToolGroup): number {
|
||||
if (isGroupDisabled(group)) return 0;
|
||||
return group.tools.filter((tool) => toolsStore.isToolEnabled(tool.function.name)).length;
|
||||
}
|
||||
|
||||
function handleToolsSubMenuOpen(open: boolean) {
|
||||
if (open) {
|
||||
if (toolsStore.builtinTools.length === 0 && !toolsStore.loading) {
|
||||
|
|
@ -337,26 +342,44 @@
|
|||
</span>
|
||||
|
||||
<span class="ml-auto shrink-0 text-xs text-muted-foreground">
|
||||
{group.tools.length}
|
||||
{getEnabledToolCount(group)}/{group.tools.length}
|
||||
</span>
|
||||
</Collapsible.Trigger>
|
||||
|
||||
{#if groupDisabled && hoveredGroup === group.label && group.serverId}
|
||||
<Switch
|
||||
checked={false}
|
||||
onclick={(e: MouseEvent) => e.stopPropagation()}
|
||||
onCheckedChange={() =>
|
||||
group.serverId && toggleServerForChat(group.serverId)}
|
||||
class="mr-2 shrink-0"
|
||||
/>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Switch
|
||||
checked={false}
|
||||
onclick={(e: MouseEvent) => e.stopPropagation()}
|
||||
onCheckedChange={() =>
|
||||
group.serverId && toggleServerForChat(group.serverId)}
|
||||
class="mr-2 shrink-0"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="left">
|
||||
<p>Enable {group.label}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{:else}
|
||||
<Checkbox
|
||||
{checked}
|
||||
{indeterminate}
|
||||
disabled={groupDisabled}
|
||||
onCheckedChange={() => toolsStore.toggleGroup(group)}
|
||||
class="mr-2 h-4 w-4 shrink-0 {groupDisabled ? 'opacity-40' : ''}"
|
||||
/>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Checkbox
|
||||
{checked}
|
||||
{indeterminate}
|
||||
disabled={groupDisabled}
|
||||
onCheckedChange={() => toolsStore.toggleGroup(group)}
|
||||
class="mr-2 h-4 w-4 shrink-0 {groupDisabled ? 'opacity-40' : ''}"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
<p>
|
||||
{checked ? 'Disable' : 'Enable'}
|
||||
{group.tools.length} tool{group.tools.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
@ -365,7 +388,7 @@
|
|||
{#each group.tools as tool (tool.function.name)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2 rounded px-2 py-1 text-left text-sm transition-colors {groupDisabled
|
||||
class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm transition-colors {groupDisabled
|
||||
? 'pointer-events-none opacity-40'
|
||||
: 'hover:bg-muted/50'}"
|
||||
onclick={() =>
|
||||
|
|
@ -381,7 +404,7 @@
|
|||
class="h-4 w-4 shrink-0"
|
||||
/>
|
||||
|
||||
<span class="min-w-0 flex-1 truncate">
|
||||
<span class="min-w-0 flex-1 truncate font-mono text-[12px]">
|
||||
{tool.function.name}
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,29 @@
|
|||
<script lang="ts">
|
||||
import { Plus, MessageSquare, Zap, FolderOpen } from '@lucide/svelte';
|
||||
import { page } from '$app/state';
|
||||
import {
|
||||
Plus,
|
||||
MessageSquare,
|
||||
Zap,
|
||||
FolderOpen,
|
||||
PencilRuler,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Loader2
|
||||
} from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as Collapsible from '$lib/components/ui/collapsible';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import * as Sheet from '$lib/components/ui/sheet';
|
||||
import { FILE_TYPE_ICONS } from '$lib/constants';
|
||||
import { FILE_TYPE_ICONS, TOOLTIP_DELAY_DURATION } from '$lib/constants';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { toolsStore, type ToolGroup } from '$lib/stores/tools.svelte';
|
||||
import { ToolSource } from '$lib/enums';
|
||||
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { TruncatedText } from '$lib/components/app';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
|
|
@ -32,6 +53,61 @@
|
|||
|
||||
let sheetOpen = $state(false);
|
||||
|
||||
let expandedGroups = new SvelteSet<string>();
|
||||
let groups = $derived(toolsStore.toolGroups);
|
||||
let activeGroups = $derived(
|
||||
groups.filter(
|
||||
(g) =>
|
||||
g.source !== ToolSource.MCP ||
|
||||
!g.serverId ||
|
||||
conversationsStore.isMcpServerEnabledForChat(g.serverId)
|
||||
)
|
||||
);
|
||||
let totalToolCount = $derived(activeGroups.reduce((n, g) => n + g.tools.length, 0));
|
||||
|
||||
function isGroupDisabled(group: ToolGroup): boolean {
|
||||
return (
|
||||
group.source === ToolSource.MCP &&
|
||||
!!group.serverId &&
|
||||
!conversationsStore.isMcpServerEnabledForChat(group.serverId)
|
||||
);
|
||||
}
|
||||
let hoveredGroup = $state<string | null>(null);
|
||||
|
||||
function getGroupCheckedState(group: (typeof groups)[number]): {
|
||||
checked: boolean;
|
||||
indeterminate: boolean;
|
||||
} {
|
||||
return {
|
||||
checked: toolsStore.isGroupFullyEnabled(group),
|
||||
indeterminate: toolsStore.isGroupPartiallyEnabled(group)
|
||||
};
|
||||
}
|
||||
|
||||
function getFavicon(group: { source: ToolSource; label: string }): string | null {
|
||||
if (group.source !== ToolSource.MCP) return null;
|
||||
|
||||
for (const server of mcpStore.getServersSorted()) {
|
||||
if (mcpStore.getServerLabel(server) === group.label) {
|
||||
return mcpStore.getServerFavicon(server.id);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleToolsSubMenuOpen(open: boolean) {
|
||||
if (open) {
|
||||
if (toolsStore.builtinTools.length === 0 && !toolsStore.loading) {
|
||||
toolsStore.fetchBuiltinTools();
|
||||
}
|
||||
mcpStore.runHealthChecksForServers(mcpStore.getServersSorted().filter((s) => s.enabled));
|
||||
}
|
||||
}
|
||||
async function toggleServerForChat(serverId: string) {
|
||||
await conversationsStore.toggleMcpServerForChat(serverId);
|
||||
}
|
||||
|
||||
function handleMcpPromptClick() {
|
||||
sheetOpen = false;
|
||||
onMcpPromptClick?.();
|
||||
|
|
@ -72,7 +148,7 @@
|
|||
<Plus class="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<Sheet.Content side="bottom" class="max-h-[85vh] gap-0">
|
||||
<Sheet.Content side="bottom" class="max-h-[85vh] gap-0 overflow-y-auto">
|
||||
<Sheet.Header>
|
||||
<Sheet.Title>Add to chat</Sheet.Title>
|
||||
|
||||
|
|
@ -81,38 +157,50 @@
|
|||
</Sheet.Description>
|
||||
</Sheet.Header>
|
||||
|
||||
<div class="flex flex-col gap-1 overflow-y-auto px-1.5 pb-2">
|
||||
<!-- Images -->
|
||||
<button
|
||||
type="button"
|
||||
class={sheetItemClass}
|
||||
disabled={!hasVisionModality}
|
||||
onclick={handleSheetFileUpload}
|
||||
>
|
||||
<FILE_TYPE_ICONS.image class="h-4 w-4 shrink-0" />
|
||||
<div class="flex flex-col gap-1 px-1.5 pb-2">
|
||||
{#if hasVisionModality}
|
||||
<button type="button" class={sheetItemClass} onclick={handleSheetFileUpload}>
|
||||
<FILE_TYPE_ICONS.image class="h-4 w-4 shrink-0" />
|
||||
|
||||
<span>Images</span>
|
||||
<span>Images</span>
|
||||
</button>
|
||||
{:else}
|
||||
<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
|
||||
<Tooltip.Trigger>
|
||||
<button type="button" class={sheetItemClass} disabled>
|
||||
<FILE_TYPE_ICONS.image class="h-4 w-4 shrink-0" />
|
||||
|
||||
{#if !hasVisionModality}
|
||||
<span class="ml-auto text-xs text-muted-foreground">Requires vision model</span>
|
||||
{/if}
|
||||
</button>
|
||||
<span>Images</span>
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<!-- Audio -->
|
||||
<button
|
||||
type="button"
|
||||
class={sheetItemClass}
|
||||
disabled={!hasAudioModality}
|
||||
onclick={handleSheetFileUpload}
|
||||
>
|
||||
<FILE_TYPE_ICONS.audio class="h-4 w-4 shrink-0" />
|
||||
<Tooltip.Content side="right">
|
||||
<p>Image processing requires a vision model</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
|
||||
<span>Audio Files</span>
|
||||
{#if hasAudioModality}
|
||||
<button type="button" class={sheetItemClass} onclick={handleSheetFileUpload}>
|
||||
<FILE_TYPE_ICONS.audio class="h-4 w-4 shrink-0" />
|
||||
|
||||
{#if !hasAudioModality}
|
||||
<span class="ml-auto text-xs text-muted-foreground">Requires audio model</span>
|
||||
{/if}
|
||||
</button>
|
||||
<span>Audio Files</span>
|
||||
</button>
|
||||
{:else}
|
||||
<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
|
||||
<Tooltip.Trigger>
|
||||
<button type="button" class={sheetItemClass} disabled>
|
||||
<FILE_TYPE_ICONS.audio class="h-4 w-4 shrink-0" />
|
||||
|
||||
<span>Audio Files</span>
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
<p>Audio files processing requires an audio model</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
|
||||
<button type="button" class={sheetItemClass} onclick={handleSheetFileUpload}>
|
||||
<FILE_TYPE_ICONS.text class="h-4 w-4 shrink-0" />
|
||||
|
|
@ -120,21 +208,197 @@
|
|||
<span>Text Files</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class={sheetItemClass} onclick={handleSheetFileUpload}>
|
||||
<FILE_TYPE_ICONS.pdf class="h-4 w-4 shrink-0" />
|
||||
{#if hasVisionModality}
|
||||
<button type="button" class={sheetItemClass} onclick={handleSheetFileUpload}>
|
||||
<FILE_TYPE_ICONS.pdf class="h-4 w-4 shrink-0" />
|
||||
|
||||
<span>PDF Files</span>
|
||||
<span>PDF Files</span>
|
||||
</button>
|
||||
{:else}
|
||||
<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
|
||||
<Tooltip.Trigger>
|
||||
<button type="button" class={sheetItemClass} disabled>
|
||||
<FILE_TYPE_ICONS.pdf class="h-4 w-4 shrink-0" />
|
||||
|
||||
{#if !hasVisionModality}
|
||||
<span class="ml-auto text-xs text-muted-foreground">Text-only</span>
|
||||
{/if}
|
||||
<span>PDF Files</span>
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
<p>PDFs will be converted to text. Image-based PDFs may not work properly.</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
|
||||
<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
|
||||
<Tooltip.Trigger>
|
||||
<button type="button" class={sheetItemClass} onclick={handleSheetSystemPromptClick}>
|
||||
<MessageSquare class="h-4 w-4 shrink-0" />
|
||||
|
||||
<span>System Message</span>
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
<p>
|
||||
{#if !page.params.id}
|
||||
Add custom system message for a new conversation
|
||||
{:else}
|
||||
Inject custom system message at the beginning of the conversation
|
||||
{/if}
|
||||
</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
|
||||
<div class="my-2 border-t"></div>
|
||||
|
||||
<button type="button" class={sheetItemClass} onclick={() => handleToolsSubMenuOpen(true)}>
|
||||
<PencilRuler class="h-4 w-4 shrink-0" />
|
||||
|
||||
<span>Tools</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class={sheetItemClass} onclick={handleSheetSystemPromptClick}>
|
||||
<MessageSquare class="h-4 w-4 shrink-0" />
|
||||
{#if totalToolCount === 0 && groups.length === 0}
|
||||
<div class="px-3 py-4 text-center text-sm text-muted-foreground">
|
||||
{#if toolsStore.loading}
|
||||
<Loader2 class="mx-auto mb-1 h-4 w-4 animate-spin" />
|
||||
Loading tools...
|
||||
{:else if toolsStore.error}
|
||||
Failed to load tools
|
||||
{:else}
|
||||
No tools available
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="max-h-80 overflow-y-auto p-2 pr-1">
|
||||
{#each groups as group (group.label)}
|
||||
{@const groupDisabled = isGroupDisabled(group)}
|
||||
{@const isExpanded = expandedGroups.has(group.label)}
|
||||
{@const { checked, indeterminate } = groupDisabled
|
||||
? { checked: false, indeterminate: false }
|
||||
: getGroupCheckedState(group)}
|
||||
{@const favicon = getFavicon(group)}
|
||||
|
||||
<span>System Message</span>
|
||||
</button>
|
||||
<Collapsible.Root
|
||||
open={isExpanded}
|
||||
onOpenChange={() => {
|
||||
if (expandedGroups.has(group.label)) {
|
||||
expandedGroups.delete(group.label);
|
||||
} else {
|
||||
expandedGroups.add(group.label);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="flex items-center gap-1"
|
||||
onmouseenter={() => {
|
||||
if (groupDisabled) hoveredGroup = group.label;
|
||||
}}
|
||||
onmouseleave={() => {
|
||||
if (hoveredGroup === group.label) hoveredGroup = null;
|
||||
}}
|
||||
>
|
||||
<Collapsible.Trigger
|
||||
class="flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted/50 {groupDisabled
|
||||
? 'opacity-40'
|
||||
: ''}"
|
||||
>
|
||||
{#if isExpanded}
|
||||
<ChevronDown class="h-3.5 w-3.5 shrink-0" />
|
||||
{:else}
|
||||
<ChevronRight class="h-3.5 w-3.5 shrink-0" />
|
||||
{/if}
|
||||
|
||||
<span class="inline-flex min-w-0 items-center gap-1.5 font-medium">
|
||||
{#if favicon}
|
||||
<img
|
||||
src={favicon}
|
||||
alt=""
|
||||
class="h-4 w-4 shrink-0 rounded-sm"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<span class="truncate">{group.label}</span>
|
||||
</span>
|
||||
|
||||
<span class="ml-auto shrink-0 text-xs text-muted-foreground">
|
||||
{group.tools.length}
|
||||
</span>
|
||||
</Collapsible.Trigger>
|
||||
|
||||
{#if groupDisabled && hoveredGroup === group.label && group.serverId}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Switch
|
||||
checked={false}
|
||||
onclick={(e: MouseEvent) => e.stopPropagation()}
|
||||
onCheckedChange={() =>
|
||||
group.serverId && toggleServerForChat(group.serverId)}
|
||||
class="mr-2 shrink-0"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="left">
|
||||
<p>Enable {group.label}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{:else}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Checkbox
|
||||
{checked}
|
||||
{indeterminate}
|
||||
disabled={groupDisabled}
|
||||
onCheckedChange={() => toolsStore.toggleGroup(group)}
|
||||
class="mr-2 h-4 w-4 shrink-0 {groupDisabled ? 'opacity-40' : ''}"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="right">
|
||||
<p>
|
||||
{checked ? 'Disable' : 'Enable'}
|
||||
{group.tools.length} tool{group.tools.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Collapsible.Content>
|
||||
<div class="ml-4 flex flex-col gap-0.5 border-l border-border/50 pl-2">
|
||||
{#each group.tools as tool (tool.function.name)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2 rounded px-2 py-1 text-left text-sm transition-colors {groupDisabled
|
||||
? 'pointer-events-none opacity-40'
|
||||
: 'hover:bg-muted/50'}"
|
||||
onclick={() => !groupDisabled && toolsStore.toggleTool(tool.function.name)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={groupDisabled
|
||||
? false
|
||||
: toolsStore.isToolEnabled(tool.function.name)}
|
||||
disabled={groupDisabled}
|
||||
onCheckedChange={() =>
|
||||
!groupDisabled && toolsStore.toggleTool(tool.function.name)}
|
||||
class="h-4 w-4 shrink-0"
|
||||
/>
|
||||
|
||||
<TruncatedText
|
||||
text={tool.function.name}
|
||||
class="min-w-0 flex-1 truncate"
|
||||
showTooltip={true}
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if hasMcpPromptsSupport}
|
||||
<button type="button" class={sheetItemClass} onclick={handleMcpPromptClick}>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
ChatFormActionAttachmentsSheet,
|
||||
ChatFormActionRecord,
|
||||
ChatFormActionSubmit,
|
||||
McpServersSelector,
|
||||
McpServersSheet,
|
||||
ModelsSelector,
|
||||
ModelsSelectorSheet
|
||||
} from '$lib/components/app';
|
||||
|
|
@ -22,6 +20,7 @@
|
|||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
import { activeMessages, conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { IsMobile } from '$lib/hooks/is-mobile.svelte';
|
||||
import McpActiveServersAvatars from '$lib/components/app/mcp/McpActiveServersAvatars.svelte';
|
||||
|
||||
interface Props {
|
||||
canSend?: boolean;
|
||||
|
|
@ -215,17 +214,7 @@
|
|||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
{#if isMobile.current}
|
||||
<McpServersSheet
|
||||
{disabled}
|
||||
onSettingsClick={() => (showChatSettingsDialogWithMcpSection = true)}
|
||||
/>
|
||||
{:else}
|
||||
<McpServersSelector
|
||||
{disabled}
|
||||
onSettingsClick={() => (showChatSettingsDialogWithMcpSection = true)}
|
||||
/>
|
||||
{/if}
|
||||
<McpActiveServersAvatars onClick={() => (showChatSettingsDialogWithMcpSection = true)} />
|
||||
|
||||
{#if isMobile.current}
|
||||
<ModelsSelectorSheet
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/components/ui/utils';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
|
|
@ -7,9 +8,10 @@
|
|||
|
||||
interface Props {
|
||||
class?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
let { class: className = '' }: Props = $props();
|
||||
let { class: className = '', onClick }: Props = $props();
|
||||
|
||||
let mcpServers = $derived(mcpStore.getServersSorted().filter((s) => s.enabled));
|
||||
let enabledMcpServersForChat = $derived(
|
||||
|
|
@ -28,30 +30,41 @@
|
|||
let mcpFavicons = $derived(
|
||||
healthyEnabledMcpServers
|
||||
.slice(0, MAX_DISPLAYED_MCP_AVATARS)
|
||||
.map((s) => ({ id: s.id, url: mcpStore.getServerFavicon(s.id) }))
|
||||
.map((s) => ({
|
||||
id: s.id,
|
||||
name: mcpStore.getServerDisplayName(s.id),
|
||||
url: mcpStore.getServerFavicon(s.id)
|
||||
}))
|
||||
.filter((f) => f.url !== null)
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if hasEnabledMcpServers && mcpFavicons.length > 0}
|
||||
<div class={cn('inline-flex items-center gap-1.5', className)}>
|
||||
<button class={cn('inline-flex items-center gap-1.5', className)} onclick={onClick}>
|
||||
<div class="flex -space-x-1">
|
||||
{#each mcpFavicons as favicon (favicon.id)}
|
||||
<div class="box-shadow-lg overflow-hidden rounded-full bg-muted ring-1 ring-muted">
|
||||
<img
|
||||
src={favicon.url}
|
||||
alt=""
|
||||
class="h-4 w-4"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<div class="box-shadow-lg overflow-hidden rounded-full bg-muted ring-1 ring-muted">
|
||||
<img
|
||||
src={favicon.url}
|
||||
alt=""
|
||||
class="h-4 w-4"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>{favicon.name}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if extraServersCount > 0}
|
||||
<span class="text-xs text-muted-foreground">+{extraServersCount}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { Settings } from '@lucide/svelte';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { DropdownMenuSearchable, McpActiveServersAvatars } from '$lib/components/app';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
|
|
@ -136,12 +137,19 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={isEnabledForChat}
|
||||
disabled={hasError}
|
||||
onclick={(e: MouseEvent) => e.stopPropagation()}
|
||||
onCheckedChange={() => toggleServerForChat(server.id)}
|
||||
/>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Switch
|
||||
checked={isEnabledForChat}
|
||||
disabled={hasError}
|
||||
onclick={(e: MouseEvent) => e.stopPropagation()}
|
||||
onCheckedChange={() => toggleServerForChat(server.id)}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>{isEnabledForChat ? 'Disable' : 'Enable'} {getServerLabel(server)}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@
|
|||
: 'text-muted-foreground',
|
||||
sheetOpen ? 'text-foreground' : ''
|
||||
)}
|
||||
style="max-width: min(calc(100cqw - 10.5rem), 20rem)"
|
||||
style="max-width: min(calc(100cqw - 9rem), 20rem)"
|
||||
disabled={disabled || updating}
|
||||
onclick={() => handleOpenChange(true)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,4 +4,9 @@
|
|||
let { ref = $bindable(null), ...restProps }: TooltipPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
|
||||
<TooltipPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="tooltip-trigger"
|
||||
class="cursor-pointer"
|
||||
{...restProps}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue