返回 JoyAI-Echo
PartStack.tsx
1 import { useState } from "react";
2 import type { PromptPart } from "@/hooks/usePromptStack";
3
4 interface Props {
5 parts: PromptPart[];
6 }
7
8 const COLORS = [
9 "bg-blue-500",
10 "bg-green-500",
11 "bg-yellow-500",
12 "bg-purple-500",
13 "bg-pink-500",
14 "bg-orange-500",
15 "bg-teal-500",
16 "bg-red-500",
17 ];
18
19 export function PartStack({ parts }: Props) {
20 const [expanded, setExpanded] = useState<Set<number>>(new Set());
21 const totalChars = parts.reduce((sum, p) => sum + p.char_count, 0);
22
23 const toggle = (idx: number) => {
24 setExpanded((prev) => {
25 const next = new Set(prev);
26 if (next.has(idx)) next.delete(idx);
27 else next.add(idx);
28 return next;
29 });
30 };
31
32 return (
33 <div className="flex flex-col gap-2">
34 {/* Proportion bar */}
35 <div className="flex h-6 w-full overflow-hidden rounded-md">
36 {parts.map((part, i) => {
37 const pct = totalChars > 0 ? (part.char_count / totalChars) * 100 : 0;
38 if (pct < 0.5) return null;
39 return (
40 <div
41 key={i}
42 className={`${COLORS[i % COLORS.length]} opacity-70 flex items-center justify-center text-[9px] text-white font-medium overflow-hidden`}
43 style={{ width: `${pct}%` }}
44 title={`${part.label}: ${part.char_count} chars (${pct.toFixed(1)}%)`}
45 >
46 {pct > 8 ? part.label : ""}
47 </div>
48 );
49 })}
50 </div>
51
52 {/* Part cards */}
53 <div className="flex flex-col gap-1">
54 {parts.map((part, i) => (
55 <div key={i} className="border rounded-md overflow-hidden">
56 <button
57 onClick={() => toggle(i)}
58 className="flex w-full items-center justify-between px-3 py-1.5 text-xs hover:bg-muted/50 transition-colors"
59 >
60 <div className="flex items-center gap-2">
61 <span
62 className={`inline-block h-2.5 w-2.5 rounded-sm ${COLORS[i % COLORS.length]} opacity-70`}
63 />
64 <span className="font-medium">{part.label}</span>
65 </div>
66 <span className="text-muted-foreground font-mono">
67 {part.char_count >= 1000
68 ? `${(part.char_count / 1000).toFixed(1)}k`
69 : part.char_count}
70 </span>
71 </button>
72 {expanded.has(i) && (
73 <div className="border-t bg-muted/30 p-3 max-h-80 overflow-auto">
74 <pre className="text-xs whitespace-pre-wrap break-words font-mono text-foreground/80">
75 {part.content}
76 </pre>
77 </div>
78 )}
79 </div>
80 ))}
81 </div>
82 </div>
83 );
84 }
85
85 lines Plain Text