fix(web): use raw token for Pico media proxy and refresh chat attachment UI (#2618)

This commit is contained in:
wenjie
2026-04-22 15:14:20 +08:00
committed by GitHub
parent 77b0c43392
commit f367a9c010
3 changed files with 183 additions and 109 deletions
@@ -69,113 +69,117 @@ export function AssistantMessage({
</div>
)}
<div
className={cn(
"relative overflow-hidden rounded-xl border",
isThought
? "border-border/30 bg-muted/20 text-muted-foreground dark:border-border/20 dark:bg-muted/10"
: "bg-card text-card-foreground border-border/60",
)}
>
{isThought && (
<div
className="text-muted-foreground/60 hover:text-muted-foreground/80 flex cursor-pointer items-center justify-between px-3 py-2 text-[12px] font-medium transition-colors select-none"
onClick={() => setIsExpanded(!isExpanded)}
>
<div className="flex items-center gap-1.5">
<IconBrain className="size-3.5" />
<span>{t("chat.reasoningLabel")}</span>
</div>
<IconChevronDown
className={cn(
"size-3.5 opacity-0 transition-all duration-200 group-hover:opacity-100",
isExpanded ? "rotate-180" : "",
)}
/>
</div>
)}
{(!isThought || isExpanded) && hasText && (
<div
className={cn(
"prose dark:prose-invert prose-pre:my-2 prose-pre:overflow-x-auto prose-pre:rounded-lg prose-pre:border prose-pre:bg-zinc-100 prose-pre:p-0 prose-pre:text-zinc-900 dark:prose-pre:bg-zinc-950 dark:prose-pre:text-zinc-100 max-w-none [overflow-wrap:anywhere] break-words",
isThought
? "prose-p:my-1.5 px-3 pt-0 pb-3 text-[13px] leading-relaxed opacity-70"
: "prose-p:my-2 p-4 text-[15px] leading-relaxed",
)}
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize, rehypeHighlight]}
{(hasText || isThought) && (
<div
className={cn(
"relative overflow-hidden rounded-xl border",
isThought
? "border-border/30 bg-muted/20 text-muted-foreground dark:border-border/20 dark:bg-muted/10"
: "bg-card text-card-foreground border-border/60",
)}
>
{isThought && (
<div
className="text-muted-foreground/60 hover:text-muted-foreground/80 flex cursor-pointer items-center justify-between px-3 py-2 text-[12px] font-medium transition-colors select-none"
onClick={() => setIsExpanded(!isExpanded)}
>
{content}
</ReactMarkdown>
</div>
)}
{(imageAttachments.length > 0 || fileAttachments.length > 0) && (
<div
className={cn("flex flex-col gap-3", hasText ? "px-4 pb-4" : "p-4")}
>
{imageAttachments.length > 0 && (
<div className="flex flex-wrap gap-3">
{imageAttachments.map((attachment, index) => (
<a
key={`${attachment.url}-${index}`}
href={attachment.url}
target="_blank"
rel="noreferrer"
className="overflow-hidden rounded-xl border"
>
<img
src={attachment.url}
alt={attachment.filename || "Attachment"}
className="max-h-72 max-w-full object-cover"
/>
</a>
))}
<div className="flex items-center gap-1.5">
<IconBrain className="size-3.5" />
<span>{t("chat.reasoningLabel")}</span>
</div>
)}
<IconChevronDown
className={cn(
"size-3.5 opacity-0 transition-all duration-200 group-hover:opacity-100",
isExpanded ? "rotate-180" : "",
)}
/>
</div>
)}
{(!isThought || isExpanded) && hasText && (
<div
className={cn(
"prose dark:prose-invert prose-pre:my-2 prose-pre:overflow-x-auto prose-pre:rounded-lg prose-pre:border prose-pre:bg-zinc-100 prose-pre:p-0 prose-pre:text-zinc-900 dark:prose-pre:bg-zinc-950 dark:prose-pre:text-zinc-100 max-w-none [overflow-wrap:anywhere] break-words",
isThought
? "prose-p:my-1.5 px-3 pt-0 pb-3 text-[13px] leading-relaxed opacity-70"
: "prose-p:my-2 p-4 text-[15px] leading-relaxed",
)}
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize, rehypeHighlight]}
>
{content}
</ReactMarkdown>
</div>
)}
{fileAttachments.length > 0 && (
<div className="flex flex-col gap-2">
{fileAttachments.map((attachment, index) => (
<a
key={`${attachment.url}-${index}`}
href={attachment.url}
download={attachment.filename}
className="bg-background/70 hover:bg-background/90 flex items-center justify-between gap-3 rounded-xl border px-3 py-2 transition-colors"
>
<span className="flex min-w-0 items-center gap-2">
<IconFileText className="text-muted-foreground size-4 shrink-0" />
<span className="truncate text-sm">
{attachment.filename || "Download attachment"}
</span>
</span>
<IconDownload className="text-muted-foreground size-4 shrink-0" />
</a>
))}
{!isThought && hasText && (
<Button
variant="ghost"
size="icon"
className={cn(
"bg-background/50 hover:bg-background/80 absolute top-2 right-2 h-7 w-7 opacity-0 transition-opacity group-hover:opacity-100",
)}
onClick={handleCopy}
>
{isCopied ? (
<IconCheck className="h-4 w-4 text-green-500" />
) : (
<IconCopy className="text-muted-foreground h-4 w-4" />
)}
</Button>
)}
</div>
)}
{imageAttachments.length > 0 && (
<div className="mt-1 flex flex-wrap gap-2">
{imageAttachments.map((attachment, index) => (
<a
key={`${attachment.url}-${index}`}
href={attachment.url}
target="_blank"
rel="noreferrer"
className="group/img relative overflow-hidden rounded-xl border border-border/50 bg-muted/30 shadow-sm transition-colors hover:border-border/80"
>
<img
src={attachment.url}
alt={attachment.filename || "Attached image"}
className="max-h-80 max-w-[280px] object-contain transition-transform duration-300 group-hover/img:scale-[1.02]"
/>
<div className="absolute inset-0 bg-black/0 transition-colors group-hover/img:bg-black/10 dark:group-hover/img:bg-black/20" />
</a>
))}
</div>
)}
{fileAttachments.length > 0 && (
<div className="mt-1 flex flex-wrap gap-3">
{fileAttachments.map((attachment, index) => (
<a
key={`${attachment.url}-${index}`}
href={attachment.url}
download={attachment.filename}
className="group/file flex w-fit min-w-[220px] max-w-sm items-center gap-3.5 rounded-xl border border-border/60 bg-card px-4 py-3 transition-all duration-300 hover:-translate-y-0.5 hover:border-violet-500/30 hover:shadow-sm dark:hover:border-violet-500/40"
>
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-violet-400 ring-1 ring-violet-500/10 dark:bg-violet-500/10 dark:text-violet-400 dark:ring-violet-500/30">
<IconFileText className="h-5 w-5" />
</div>
)}
</div>
)}
{!isThought && hasText && (
<Button
variant="ghost"
size="icon"
className={cn(
"bg-background/50 hover:bg-background/80 absolute top-2 right-2 h-7 w-7 opacity-0 transition-opacity group-hover:opacity-100",
)}
onClick={handleCopy}
>
{isCopied ? (
<IconCheck className="h-4 w-4 text-green-500" />
) : (
<IconCopy className="text-muted-foreground h-4 w-4" />
)}
</Button>
)}
</div>
<div className="flex min-w-0 flex-1 flex-col pr-1">
<span className="truncate text-[14px] font-medium leading-tight text-foreground/90 transition-colors group-hover/file:text-violet-600 dark:group-hover/file:text-violet-400">
{attachment.filename || "Download file"}
</span>
<span className="mt-1 text-[12px] font-medium text-muted-foreground/70">
{attachment.filename?.split(".").pop()?.toUpperCase() || "FILE"}
</span>
</div>
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted/60 text-muted-foreground/50 transition-all duration-300 group-hover/file:bg-violet-400 group-hover/file:text-white group-hover/file:shadow-sm dark:bg-muted/20 dark:group-hover/file:bg-violet-400">
<IconDownload className="h-4 w-4 transition-transform duration-300 group-hover/file:-translate-y-[1px]" />
</div>
</a>
))}
</div>
)}
</div>
)
}