Spaces:
Running
Running
File size: 4,728 Bytes
0c2fc21 d3c7f96 0c2fc21 c38a89e 0c2fc21 c38a89e 0c2fc21 c38a89e 0c2fc21 45f314a d3c7f96 45f314a d3c7f96 c38a89e 0c2fc21 c38a89e d3c7f96 c38a89e d3c7f96 c38a89e d3c7f96 c38a89e d3c7f96 c38a89e d3c7f96 c38a89e 0c2fc21 d3c7f96 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | import { useState } from "react";
import Markdown from "./Markdown.jsx";
const THINK_START = "‹‹THINK››";
const THINK_END = "‹‹/THINK››";
function splitThinking(text) {
const startIdx = text.indexOf(THINK_START);
const endIdx = text.indexOf(THINK_END);
if (startIdx !== -1 && endIdx !== -1) {
const thinking = text.slice(startIdx + THINK_START.length, endIdx).trim();
const response = text.slice(endIdx + THINK_END.length).trim();
return { thinking, response };
}
// Still streaming inside think block
if (startIdx !== -1 && endIdx === -1) {
const thinking = text.slice(startIdx + THINK_START.length).trim();
return { thinking, response: null };
}
return { thinking: null, response: text };
}
function ThinkingBlock({ thinking }) {
const [open, setOpen] = useState(false);
if (!thinking) return null;
return (
<div className="mb-3">
<button
onClick={() => setOpen((v) => !v)}
className="flex items-center gap-1.5 text-[11px] text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-colors cursor-pointer"
>
<span className={`transition-transform ${open ? "rotate-90" : ""}`}>▶</span>
<span>💭 Thinking</span>
</button>
{open && (
<div className="mt-1.5 pl-4 border-l-2 border-[var(--color-blue)]/20 text-xs text-[var(--color-text-secondary)]/80 leading-relaxed whitespace-pre-wrap">
{thinking}
</div>
)}
</div>
);
}
function AssistantContent({ text }) {
const { thinking, response } = splitThinking(text);
return (
<>
<ThinkingBlock thinking={thinking} />
{response && <Markdown>{response}</Markdown>}
{response === null && thinking && (
<span className="text-[11px] text-[var(--color-text-secondary)] italic">thinking...</span>
)}
</>
);
}
export default function MessageList({ messages, streamingText, isStreaming, processingStep }) {
return (
<div className="px-4 py-4 space-y-6">
{messages.map((msg, i) => (
<Message key={i} msg={msg} />
))}
{isStreaming && processingStep && !streamingText && (
<div className="flex gap-3">
<div className="w-7 h-7 rounded-lg bg-gradient-to-br from-[#3186FF] to-[#4FA0FF] flex items-center justify-center text-white text-xs font-bold shrink-0">
G
</div>
<div className="flex items-center gap-2 text-sm text-[var(--color-text-secondary)] pt-1">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-[var(--color-blue)] animate-pulse" />
<span className="capitalize">{processingStep}...</span>
</div>
</div>
)}
{isStreaming && streamingText && (
<div className="flex gap-3">
<div className="w-7 h-7 rounded-lg bg-gradient-to-br from-[#3186FF] to-[#4FA0FF] flex items-center justify-center text-white text-xs font-bold shrink-0">
G
</div>
<div className="text-sm text-[var(--color-text)] leading-relaxed pt-1 min-w-0">
<AssistantContent text={streamingText} />
<span className="inline-block w-1.5 h-4 bg-[var(--color-blue)] animate-pulse ml-0.5 align-text-bottom rounded-sm" />
</div>
</div>
)}
</div>
);
}
function Message({ msg }) {
const isUser = msg.role === "user";
return (
<div className="flex gap-3">
<div
className={`w-7 h-7 rounded-lg flex items-center justify-center text-xs font-bold shrink-0 ${
isUser
? "bg-[var(--color-surface-high)] text-[var(--color-text-secondary)]"
: "bg-gradient-to-br from-[#3186FF] to-[#4FA0FF] text-white"
}`}
>
{isUser ? "Y" : "G"}
</div>
<div className="flex-1 min-w-0 pt-0.5">
{msg.videoUrl ? (
<video controls src={msg.videoUrl} className="max-w-sm max-h-48 rounded-xl mb-2 border border-[var(--color-outline)]" />
) : msg.imageUrl ? (
<img
src={msg.imageUrl}
alt="Attached"
className="max-w-xs max-h-48 rounded-xl mb-2 border border-[var(--color-outline)]"
/>
) : null}
{msg.audioUrl && (
<audio controls src={msg.audioUrl} className="mb-2 h-8 max-w-xs" />
)}
{isUser ? (
<div className="text-sm text-[var(--color-text)] leading-relaxed">
{msg.content.filter((c) => c.type === "text").map((c) => c.text).join("")}
</div>
) : (
<div className="text-sm text-[var(--color-text)] leading-relaxed">
<AssistantContent text={msg.content} />
</div>
)}
</div>
</div>
);
}
|