feat: Integrate Resource Attachments into Chat Form UI
This commit is contained in:
parent
23e4ef7495
commit
aa7089d598
|
|
@ -4,8 +4,10 @@
|
|||
ChatFormActions,
|
||||
ChatFormFileInputInvisible,
|
||||
ChatFormPromptPicker,
|
||||
ChatFormTextarea
|
||||
ChatFormTextarea,
|
||||
McpResourcePicker
|
||||
} from '$lib/components/app';
|
||||
import ChatFormResourceAttachments from '../ChatFormResourceAttachments.svelte';
|
||||
import { INPUT_CLASSES } from '$lib/constants/css-classes';
|
||||
import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
|
||||
import { MimeTypeText, SpecialFileType } from '$lib/enums';
|
||||
|
|
@ -14,6 +16,7 @@
|
|||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { mcpHasResourceAttachments } from '$lib/stores/mcp-resources.svelte';
|
||||
import { conversationsStore, activeMessages } from '$lib/stores/conversations.svelte';
|
||||
import type { GetPromptResult, MCPPromptInfo, PromptMessage } from '$lib/types';
|
||||
import { isIMEComposing, parseClipboardContent } from '$lib/utils';
|
||||
|
|
@ -91,6 +94,10 @@
|
|||
let isPromptPickerOpen = $state(false);
|
||||
let promptSearchQuery = $state('');
|
||||
|
||||
// Resource Picker State
|
||||
let isResourcePickerOpen = $state(false);
|
||||
let preSelectedResourceUri = $state<string | undefined>(undefined);
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
|
|
@ -489,7 +496,7 @@
|
|||
onpaste={handlePaste}
|
||||
>
|
||||
<ChatFormTextarea
|
||||
class="px-2 py-1 md:py-0"
|
||||
class="px-2 py-1.5 md:pt-0"
|
||||
bind:this={textareaRef}
|
||||
bind:value
|
||||
onKeydown={handleKeydown}
|
||||
|
|
@ -501,6 +508,16 @@
|
|||
{placeholder}
|
||||
/>
|
||||
|
||||
{#if mcpHasResourceAttachments()}
|
||||
<ChatFormResourceAttachments
|
||||
class="mb-3"
|
||||
onResourceClick={(uri) => {
|
||||
preSelectedResourceUri = uri;
|
||||
isResourcePickerOpen = true;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<ChatFormActions
|
||||
bind:this={chatFormActionsRef}
|
||||
canSend={canSubmit}
|
||||
|
|
@ -514,7 +531,18 @@
|
|||
{onStop}
|
||||
onSystemPromptClick={() => onSystemPromptClick?.({ message: value, files: uploadedFiles })}
|
||||
onMcpPromptClick={showMcpPromptButton ? () => (isPromptPickerOpen = true) : undefined}
|
||||
onMcpResourcesClick={() => (isResourcePickerOpen = true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<McpResourcePicker
|
||||
bind:open={isResourcePickerOpen}
|
||||
preSelectedUri={preSelectedResourceUri}
|
||||
onOpenChange={(newOpen) => {
|
||||
if (!newOpen) {
|
||||
preSelectedResourceUri = undefined;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { Plus, MessageSquare, Zap } from '@lucide/svelte';
|
||||
import { Plus, MessageSquare, Zap, FolderOpen } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
|
|
@ -14,10 +14,12 @@
|
|||
hasAudioModality?: boolean;
|
||||
hasVisionModality?: boolean;
|
||||
hasMcpPromptsSupport?: boolean;
|
||||
hasMcpResourcesSupport?: boolean;
|
||||
onFileUpload?: () => void;
|
||||
onSystemPromptClick?: () => void;
|
||||
onMcpPromptClick?: () => void;
|
||||
onMcpServersClick?: () => void;
|
||||
onMcpResourcesClick?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
|
|
@ -26,10 +28,12 @@
|
|||
hasAudioModality = false,
|
||||
hasVisionModality = false,
|
||||
hasMcpPromptsSupport = false,
|
||||
hasMcpResourcesSupport = false,
|
||||
onFileUpload,
|
||||
onSystemPromptClick,
|
||||
onMcpPromptClick,
|
||||
onMcpServersClick
|
||||
onMcpServersClick,
|
||||
onMcpResourcesClick
|
||||
}: Props = $props();
|
||||
|
||||
let isNewChat = $derived(!page.params.id);
|
||||
|
|
@ -52,6 +56,11 @@
|
|||
onMcpServersClick?.();
|
||||
}
|
||||
|
||||
function handleMcpResourcesClick() {
|
||||
dropdownOpen = false;
|
||||
onMcpResourcesClick?.();
|
||||
}
|
||||
|
||||
const fileUploadTooltipText = 'Add files, system prompt or MCP Servers';
|
||||
</script>
|
||||
|
||||
|
|
@ -195,6 +204,17 @@
|
|||
<span>MCP Prompt</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
{#if hasMcpResourcesSupport}
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={handleMcpResourcesClick}
|
||||
>
|
||||
<FolderOpen class="h-4 w-4" />
|
||||
|
||||
<span>MCP Resources</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
onStop?: () => void;
|
||||
onSystemPromptClick?: () => void;
|
||||
onMcpPromptClick?: () => void;
|
||||
onMcpResourcesClick?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
|
|
@ -45,7 +46,8 @@
|
|||
onMicClick,
|
||||
onStop,
|
||||
onSystemPromptClick,
|
||||
onMcpPromptClick
|
||||
onMcpPromptClick,
|
||||
onMcpResourcesClick
|
||||
}: Props = $props();
|
||||
|
||||
let currentConfig = $derived(config());
|
||||
|
|
@ -165,6 +167,11 @@
|
|||
const perChatOverrides = conversationsStore.getAllMcpServerOverrides();
|
||||
return mcpStore.hasEnabledServers(perChatOverrides);
|
||||
});
|
||||
|
||||
let hasMcpResourcesSupport = $derived.by(() => {
|
||||
const perChatOverrides = conversationsStore.getAllMcpServerOverrides();
|
||||
return mcpStore.hasEnabledServers(perChatOverrides) && mcpStore.hasResourcesCapability();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex w-full items-center gap-3 {className}" style="container-type: inline-size">
|
||||
|
|
@ -174,9 +181,11 @@
|
|||
{hasAudioModality}
|
||||
{hasVisionModality}
|
||||
{hasMcpPromptsSupport}
|
||||
{hasMcpResourcesSupport}
|
||||
{onFileUpload}
|
||||
{onSystemPromptClick}
|
||||
{onMcpPromptClick}
|
||||
{onMcpResourcesClick}
|
||||
onMcpServersClick={() => (showMcpDialog = true)}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import {
|
||||
mcpResourceAttachments,
|
||||
mcpHasResourceAttachments
|
||||
} from '$lib/stores/mcp-resources.svelte';
|
||||
import { ChatAttachmentMcpResource, HorizontalScrollCarousel } from '$lib/components/app';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
onResourceClick?: (uri: string) => void;
|
||||
}
|
||||
|
||||
let { class: className, onResourceClick }: Props = $props();
|
||||
|
||||
const attachments = $derived(mcpResourceAttachments());
|
||||
const hasAttachments = $derived(mcpHasResourceAttachments());
|
||||
|
||||
function handleRemove(attachmentId: string) {
|
||||
mcpStore.removeResourceAttachment(attachmentId);
|
||||
}
|
||||
|
||||
function handleResourceClick(uri: string) {
|
||||
onResourceClick?.(uri);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if hasAttachments}
|
||||
<div class={className}>
|
||||
<HorizontalScrollCarousel gapSize="2">
|
||||
{#each attachments as attachment (attachment.id)}
|
||||
<ChatAttachmentMcpResource
|
||||
{attachment}
|
||||
onRemove={handleRemove}
|
||||
onClick={() => handleResourceClick(attachment.resource.uri)}
|
||||
/>
|
||||
{/each}
|
||||
</HorizontalScrollCarousel>
|
||||
</div>
|
||||
{/if}
|
||||
Loading…
Reference in New Issue