支持在会话输入框直接进行文件/图片的拖拽和粘贴

This commit is contained in:
xintaofei
2026-03-08 10:54:06 +08:00
parent 68e2c7f989
commit 7a4cbcb73e
24 changed files with 1335 additions and 78 deletions

View File

@@ -8,6 +8,7 @@ import {
type AdaptedMessage,
type AdaptedContentPart,
type MessageGroup,
type UserImageDisplay,
type UserResourceDisplay,
groupAdaptedMessages,
extractUserResourcesFromText,
@@ -15,6 +16,7 @@ import {
import { TurnStats } from "./turn-stats"
import { LiveTurnStats } from "./live-turn-stats"
import { UserResourceLinks } from "./user-resource-links"
import { UserImageAttachments } from "./user-image-attachments"
import { useSessionStats } from "@/contexts/session-stats-context"
import { LiveMessageBlock } from "@/components/chat/live-message-block"
import { AgentPlanOverlay } from "@/components/chat/agent-plan-overlay"
@@ -45,6 +47,7 @@ interface MessageListViewProps {
interface ResolvedMessageGroup extends MessageGroup {
parts: AdaptedContentPart[]
resources: UserResourceDisplay[]
images: UserImageDisplay[]
}
function fallbackExtractUserResources(
@@ -53,11 +56,13 @@ function fallbackExtractUserResources(
): {
parts: AdaptedContentPart[]
resources: UserResourceDisplay[]
images: UserImageDisplay[]
} {
if (group.role !== "user") {
return {
parts: group.parts,
resources: group.userResources ?? [],
images: group.userImages ?? [],
}
}
@@ -94,7 +99,11 @@ function fallbackExtractUserResources(
parsedParts.push({ type: "text", text: attachedResourcesText })
}
return { parts: parsedParts, resources: dedupedResources }
return {
parts: parsedParts,
resources: dedupedResources,
images: group.userImages ?? [],
}
}
function resolveMessageGroup(
@@ -106,6 +115,7 @@ function resolveMessageGroup(
...group,
parts: resolved.parts,
resources: resolved.resources,
images: resolved.images,
}
}
@@ -125,6 +135,9 @@ const HistoricalMessageGroup = memo(function HistoricalMessageGroup({
<MessageContent>
<ContentPartsRenderer parts={group.parts} role={group.role} />
</MessageContent>
{group.role === "user" && group.images.length > 0 ? (
<UserImageAttachments images={group.images} className="self-end" />
) : null}
{group.role === "user" && group.resources.length > 0 ? (
<UserResourceLinks resources={group.resources} className="self-end" />
) : null}
@@ -152,6 +165,9 @@ const PendingMessageGroup = memo(function PendingMessageGroup({
<MessageContent>
<ContentPartsRenderer parts={group.parts} role={group.role} />
</MessageContent>
{group.role === "user" && group.images.length > 0 ? (
<UserImageAttachments images={group.images} className="self-end" />
) : null}
{group.role === "user" && group.resources.length > 0 ? (
<UserResourceLinks resources={group.resources} className="self-end" />
) : null}

View File

@@ -0,0 +1,38 @@
"use client"
import Image from "next/image"
import type { UserImageDisplay } from "@/lib/adapters/ai-elements-adapter"
interface UserImageAttachmentsProps {
images: UserImageDisplay[]
className?: string
}
export function UserImageAttachments({
images,
className,
}: UserImageAttachmentsProps) {
if (images.length === 0) return null
return (
<div className={className}>
<div className="flex flex-wrap gap-1.5">
{images.map((image, index) => (
<div
key={`${image.uri ?? image.name}-${index}`}
className="overflow-hidden rounded-md border border-border/70 bg-muted/30"
>
<Image
src={`data:${image.mime_type};base64,${image.data}`}
alt={image.name}
width={56}
height={56}
unoptimized
className="h-14 w-14 object-cover"
/>
</div>
))}
</div>
</div>
)
}