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