@agent-native/toolkit 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/agent-native.eject.json +411 -0
- package/dist/chat-history/ChatHistoryList.d.ts +103 -0
- package/dist/chat-history/ChatHistoryList.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryList.js +109 -0
- package/dist/chat-history/ChatHistoryList.js.map +1 -0
- package/dist/chat-history/ChatHistoryList.spec.d.ts +2 -0
- package/dist/chat-history/ChatHistoryList.spec.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryList.spec.js +283 -0
- package/dist/chat-history/ChatHistoryList.spec.js.map +1 -0
- package/dist/chat-history/index.d.ts +2 -0
- package/dist/chat-history/index.d.ts.map +1 -0
- package/dist/chat-history/index.js +2 -0
- package/dist/chat-history/index.js.map +1 -0
- package/dist/chat-history.css +314 -0
- package/package.json +20 -1
- package/src/app-shell/header-actions.tsx +84 -0
- package/src/app-shell/index.ts +1 -0
- package/src/chat-history/ChatHistoryList.spec.tsx +453 -0
- package/src/chat-history/ChatHistoryList.tsx +469 -0
- package/src/chat-history/index.ts +7 -0
- package/src/chat-history.css +314 -0
- package/src/collab-ui/AgentPresenceChip.tsx +69 -0
- package/src/collab-ui/LiveCursorOverlay.tsx +241 -0
- package/src/collab-ui/PresenceBar.tsx +346 -0
- package/src/collab-ui/RecentEditHighlights.tsx +225 -0
- package/src/collab-ui/RemoteSelectionRings.tsx +227 -0
- package/src/collab-ui/agent-identity.ts +16 -0
- package/src/collab-ui/index.ts +39 -0
- package/src/collab-ui/lead-client.ts +32 -0
- package/src/collab-ui/types.spec.ts +18 -0
- package/src/collab-ui/types.ts +96 -0
- package/src/composer/AgentComposerFrame.tsx +59 -0
- package/src/composer/ComposerPlusMenu.tsx +983 -0
- package/src/composer/MentionPopover.tsx +412 -0
- package/src/composer/PastedTextChip.tsx +150 -0
- package/src/composer/PromptComposer.spec.ts +73 -0
- package/src/composer/PromptComposer.tsx +718 -0
- package/src/composer/RealtimeVoiceMode.spec.tsx +771 -0
- package/src/composer/RealtimeVoiceMode.tsx +856 -0
- package/src/composer/TiptapComposer.spec.ts +390 -0
- package/src/composer/TiptapComposer.tsx +2936 -0
- package/src/composer/VoiceButton.spec.ts +33 -0
- package/src/composer/VoiceButton.tsx +255 -0
- package/src/composer/asset-picker-url.spec.ts +56 -0
- package/src/composer/asset-picker-url.ts +80 -0
- package/src/composer/attachment-accept.spec.ts +22 -0
- package/src/composer/attachment-accept.ts +71 -0
- package/src/composer/draft-key.spec.ts +19 -0
- package/src/composer/draft-key.ts +7 -0
- package/src/composer/extensions/FileReference.tsx +55 -0
- package/src/composer/extensions/MentionReference.tsx +93 -0
- package/src/composer/extensions/SkillReference.tsx +56 -0
- package/src/composer/index.ts +79 -0
- package/src/composer/pasted-text.spec.ts +162 -0
- package/src/composer/pasted-text.ts +160 -0
- package/src/composer/prompt-attachments.spec.ts +80 -0
- package/src/composer/prompt-attachments.ts +93 -0
- package/src/composer/realtime-voice-audio-level.spec.ts +32 -0
- package/src/composer/realtime-voice-audio-level.ts +64 -0
- package/src/composer/realtime-voice-transcript.spec.ts +95 -0
- package/src/composer/realtime-voice-transcript.ts +134 -0
- package/src/composer/runtime-adapters.tsx +343 -0
- package/src/composer/types.ts +66 -0
- package/src/composer/use-file-search.ts +64 -0
- package/src/composer/use-mention-search.ts +90 -0
- package/src/composer/use-skills.ts +45 -0
- package/src/composer/useRealtimeVoiceMode.spec.ts +818 -0
- package/src/composer/useRealtimeVoiceMode.tsx +2117 -0
- package/src/composer/useVoiceDictation.spec.ts +45 -0
- package/src/composer/useVoiceDictation.ts +1191 -0
- package/src/context-ui/ContextMeter.tsx +101 -0
- package/src/context-ui/ContextSegmentRow.tsx +184 -0
- package/src/context-ui/ContextTreemap.tsx +125 -0
- package/src/context-ui/ContextXRayPanel.tsx +377 -0
- package/src/context-ui/format.ts +42 -0
- package/src/context-ui/index.ts +15 -0
- package/src/context-ui/types.ts +43 -0
- package/src/design-tweaks/index.ts +1 -0
- package/src/design-tweaks/visual-style-controls.tsx +1084 -0
- package/src/editor/BubbleToolbar.tsx +253 -0
- package/src/editor/CodeBlockNode.tsx +254 -0
- package/src/editor/DragHandle.spec.ts +588 -0
- package/src/editor/DragHandle.ts +1437 -0
- package/src/editor/ImageExtension.roundtrip.spec.ts +117 -0
- package/src/editor/ImageExtension.ts +306 -0
- package/src/editor/RegistryBlockContext.tsx +107 -0
- package/src/editor/RegistryBlockNode.spec.ts +279 -0
- package/src/editor/RegistryBlockNode.tsx +652 -0
- package/src/editor/RichMarkdownEditor.collab.spec.ts +79 -0
- package/src/editor/RichMarkdownEditor.roundtrip.spec.ts +226 -0
- package/src/editor/RichMarkdownEditor.tsx +81 -0
- package/src/editor/RunId.ts +61 -0
- package/src/editor/SharedRichEditor.tsx +321 -0
- package/src/editor/SlashCommandMenu.tsx +310 -0
- package/src/editor/disableHistory.spec.ts +85 -0
- package/src/editor/extensions.ts +402 -0
- package/src/editor/gfmDoc.spec.ts +230 -0
- package/src/editor/gfmDoc.ts +92 -0
- package/src/editor/index.ts +93 -0
- package/src/editor/registrySlashCommands.ts +131 -0
- package/src/editor/structuralReorderUndo.spec.ts +144 -0
- package/src/editor/surgical-apply.spec.ts +279 -0
- package/src/editor/surgical-apply.ts +192 -0
- package/src/editor/useCollabReconcile.concurrent.spec.ts +422 -0
- package/src/editor/useCollabReconcile.idempotent.spec.ts +324 -0
- package/src/editor/useCollabReconcile.ts +589 -0
- package/src/editor.css +510 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-mobile.tsx +21 -0
- package/src/hooks/use-toast.ts +188 -0
- package/src/index.ts +8 -0
- package/src/onboarding/index.ts +1 -0
- package/src/provider.tsx +47 -0
- package/src/sharing/VisibilityBadge.spec.ts +96 -0
- package/src/sharing/VisibilityBadge.tsx +37 -0
- package/src/sharing/index.ts +4 -0
- package/src/styles.css +21 -0
- package/src/ui/accordion.tsx +56 -0
- package/src/ui/alert-dialog.tsx +141 -0
- package/src/ui/alert.tsx +59 -0
- package/src/ui/aspect-ratio.tsx +5 -0
- package/src/ui/avatar.tsx +48 -0
- package/src/ui/badge.tsx +37 -0
- package/src/ui/breadcrumb.tsx +115 -0
- package/src/ui/button.tsx +78 -0
- package/src/ui/calendar.tsx +214 -0
- package/src/ui/card.tsx +86 -0
- package/src/ui/carousel.tsx +260 -0
- package/src/ui/chart.tsx +375 -0
- package/src/ui/checkbox.tsx +28 -0
- package/src/ui/collapsible.tsx +9 -0
- package/src/ui/command.spec.tsx +51 -0
- package/src/ui/command.tsx +174 -0
- package/src/ui/context-menu.tsx +202 -0
- package/src/ui/date-picker.tsx +64 -0
- package/src/ui/dialog.spec.tsx +43 -0
- package/src/ui/dialog.tsx +146 -0
- package/src/ui/drawer.tsx +126 -0
- package/src/ui/dropdown-menu.tsx +218 -0
- package/src/ui/form.tsx +177 -0
- package/src/ui/hover-card.tsx +27 -0
- package/src/ui/index.ts +52 -0
- package/src/ui/input-otp.tsx +69 -0
- package/src/ui/input.tsx +22 -0
- package/src/ui/label.tsx +24 -0
- package/src/ui/logo.tsx +36 -0
- package/src/ui/menubar.tsx +235 -0
- package/src/ui/navigation-menu.tsx +128 -0
- package/src/ui/pagination.tsx +121 -0
- package/src/ui/popover.tsx +44 -0
- package/src/ui/progress.tsx +26 -0
- package/src/ui/radio-group.tsx +42 -0
- package/src/ui/resizable.tsx +43 -0
- package/src/ui/scroll-area.tsx +46 -0
- package/src/ui/select.tsx +158 -0
- package/src/ui/separator.tsx +29 -0
- package/src/ui/sheet.tsx +160 -0
- package/src/ui/sidebar.tsx +777 -0
- package/src/ui/skeleton.tsx +18 -0
- package/src/ui/slider.tsx +26 -0
- package/src/ui/sonner.tsx +58 -0
- package/src/ui/spinner.tsx +17 -0
- package/src/ui/switch.tsx +27 -0
- package/src/ui/table.tsx +117 -0
- package/src/ui/tabs.tsx +53 -0
- package/src/ui/textarea.tsx +23 -0
- package/src/ui/toast.tsx +127 -0
- package/src/ui/toaster.tsx +33 -0
- package/src/ui/toggle-group.tsx +59 -0
- package/src/ui/toggle.tsx +43 -0
- package/src/ui/tooltip.tsx +53 -0
- package/src/ui/use-toast.ts +3 -0
- package/src/utils.ts +8 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
|
|
4
|
+
import {
|
|
5
|
+
Tooltip,
|
|
6
|
+
TooltipContent,
|
|
7
|
+
TooltipProvider,
|
|
8
|
+
TooltipTrigger,
|
|
9
|
+
} from "../ui/tooltip.js";
|
|
10
|
+
import { cn } from "../utils.js";
|
|
11
|
+
import { formatContextTokens } from "./format.js";
|
|
12
|
+
import type { ContextManifestViewData } from "./types.js";
|
|
13
|
+
|
|
14
|
+
function ContextDonut({ pct, advisory }: { pct: number; advisory: boolean }) {
|
|
15
|
+
const radius = 7.5;
|
|
16
|
+
const circumference = 2 * Math.PI * radius;
|
|
17
|
+
const displayPct = Math.max(3, Math.min(100, pct));
|
|
18
|
+
const dashOffset = circumference - (displayPct / 100) * circumference;
|
|
19
|
+
return (
|
|
20
|
+
<span className="relative flex size-5 items-center justify-center">
|
|
21
|
+
<svg aria-hidden="true" viewBox="0 0 20 20" className="-rotate-90 size-5">
|
|
22
|
+
<circle
|
|
23
|
+
cx="10"
|
|
24
|
+
cy="10"
|
|
25
|
+
r={radius}
|
|
26
|
+
className="stroke-muted"
|
|
27
|
+
fill="none"
|
|
28
|
+
strokeWidth="3"
|
|
29
|
+
/>
|
|
30
|
+
<circle
|
|
31
|
+
cx="10"
|
|
32
|
+
cy="10"
|
|
33
|
+
r={radius}
|
|
34
|
+
className={cn(advisory ? "stroke-amber-500" : "stroke-[#00B5FF]")}
|
|
35
|
+
fill="none"
|
|
36
|
+
strokeLinecap="round"
|
|
37
|
+
strokeWidth="3"
|
|
38
|
+
strokeDasharray={circumference}
|
|
39
|
+
strokeDashoffset={dashOffset}
|
|
40
|
+
/>
|
|
41
|
+
</svg>
|
|
42
|
+
<span className="absolute size-2 rounded-full bg-background" />
|
|
43
|
+
</span>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ContextMeterView({
|
|
48
|
+
manifest,
|
|
49
|
+
contextWindow,
|
|
50
|
+
open,
|
|
51
|
+
onOpenChange,
|
|
52
|
+
children,
|
|
53
|
+
}: {
|
|
54
|
+
manifest: ContextManifestViewData;
|
|
55
|
+
contextWindow: number;
|
|
56
|
+
open: boolean;
|
|
57
|
+
onOpenChange: (open: boolean) => void;
|
|
58
|
+
children: ReactNode;
|
|
59
|
+
}) {
|
|
60
|
+
const pct = Math.min(
|
|
61
|
+
100,
|
|
62
|
+
Math.round((manifest.totalTokens / contextWindow) * 100),
|
|
63
|
+
);
|
|
64
|
+
const { conversationTokens, systemTokens } = manifest;
|
|
65
|
+
return (
|
|
66
|
+
<TooltipProvider delayDuration={200}>
|
|
67
|
+
<Popover open={open} onOpenChange={onOpenChange}>
|
|
68
|
+
<Tooltip>
|
|
69
|
+
<TooltipTrigger asChild>
|
|
70
|
+
<PopoverTrigger asChild>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
aria-label={`Context ${pct}%, ${formatContextTokens(manifest.totalTokens)}${systemTokens > 0 ? ` total: ${formatContextTokens(systemTokens)} system + ${formatContextTokens(conversationTokens)} conversation` : ""}. Open Context X-Ray.`}
|
|
74
|
+
className={cn(
|
|
75
|
+
"flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
76
|
+
open && "bg-accent/60 text-foreground",
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<ContextDonut pct={pct} advisory={!manifest.enforceable} />
|
|
80
|
+
</button>
|
|
81
|
+
</PopoverTrigger>
|
|
82
|
+
</TooltipTrigger>
|
|
83
|
+
<TooltipContent>
|
|
84
|
+
Context {pct}% · {formatContextTokens(manifest.totalTokens)}
|
|
85
|
+
{systemTokens > 0
|
|
86
|
+
? ` (${formatContextTokens(systemTokens)} system + ${formatContextTokens(conversationTokens)} conversation)`
|
|
87
|
+
: ""}
|
|
88
|
+
</TooltipContent>
|
|
89
|
+
</Tooltip>
|
|
90
|
+
<PopoverContent
|
|
91
|
+
align="end"
|
|
92
|
+
side="top"
|
|
93
|
+
sideOffset={8}
|
|
94
|
+
className="w-[min(92vw,380px)] overflow-hidden border-border/70 p-0"
|
|
95
|
+
>
|
|
96
|
+
{children}
|
|
97
|
+
</PopoverContent>
|
|
98
|
+
</Popover>
|
|
99
|
+
</TooltipProvider>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { IconLock, IconPin, IconRotate2, IconX } from "@tabler/icons-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
|
|
6
|
+
import { cn } from "../utils.js";
|
|
7
|
+
import { contextStatusLabel, formatContextTokens } from "./format.js";
|
|
8
|
+
import type { ContextSegmentViewData } from "./types.js";
|
|
9
|
+
|
|
10
|
+
export function SegmentProvenancePopoverView({
|
|
11
|
+
segment,
|
|
12
|
+
children,
|
|
13
|
+
}: {
|
|
14
|
+
segment: ContextSegmentViewData;
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}) {
|
|
17
|
+
return (
|
|
18
|
+
<Popover>
|
|
19
|
+
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
|
20
|
+
<PopoverContent align="start" className="w-80 p-3">
|
|
21
|
+
<div className="space-y-2">
|
|
22
|
+
<div>
|
|
23
|
+
<div className="text-[11px] font-medium uppercase text-muted-foreground">
|
|
24
|
+
Segment
|
|
25
|
+
</div>
|
|
26
|
+
<div className="mt-0.5 break-words text-xs text-foreground">
|
|
27
|
+
{segment.label}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="grid grid-cols-2 gap-2 text-[11px] text-muted-foreground">
|
|
31
|
+
<div>
|
|
32
|
+
<span className="block font-medium text-foreground">
|
|
33
|
+
{formatContextTokens(segment.tokenCount)}
|
|
34
|
+
</span>
|
|
35
|
+
tokens{segment.tokenMethod === "estimate" ? " estimated" : ""}
|
|
36
|
+
</div>
|
|
37
|
+
<div>
|
|
38
|
+
<span className="block font-medium text-foreground">
|
|
39
|
+
{contextStatusLabel(segment)}
|
|
40
|
+
</span>
|
|
41
|
+
current status
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
<span className="block font-medium text-foreground">
|
|
45
|
+
{segment.msgIndex ?? "-"}
|
|
46
|
+
</span>
|
|
47
|
+
message index
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
<span className="block font-medium text-foreground">
|
|
51
|
+
{segment.partIndex ?? "-"}
|
|
52
|
+
</span>
|
|
53
|
+
part index
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
{segment.protected ? (
|
|
57
|
+
<div className="rounded-md border border-border bg-muted/40 px-2 py-1.5 text-[11px] text-muted-foreground">
|
|
58
|
+
This segment is part of the active turn and cannot be evicted yet.
|
|
59
|
+
</div>
|
|
60
|
+
) : null}
|
|
61
|
+
</div>
|
|
62
|
+
</PopoverContent>
|
|
63
|
+
</Popover>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function ContextSegmentRowView({
|
|
68
|
+
segment,
|
|
69
|
+
advisory,
|
|
70
|
+
onPin,
|
|
71
|
+
onEvict,
|
|
72
|
+
onRestore,
|
|
73
|
+
}: {
|
|
74
|
+
segment: ContextSegmentViewData;
|
|
75
|
+
advisory: boolean;
|
|
76
|
+
onPin: () => void;
|
|
77
|
+
onEvict: () => void;
|
|
78
|
+
onRestore: () => void;
|
|
79
|
+
}) {
|
|
80
|
+
const disabled = segment.protected || segment.status === "evicted";
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
tabIndex={0}
|
|
84
|
+
onKeyDown={(event) => {
|
|
85
|
+
if (event.key === "p") onPin();
|
|
86
|
+
if (event.key === "e" && !disabled) onEvict();
|
|
87
|
+
if (event.key === "u" && segment.status !== "active") onRestore();
|
|
88
|
+
}}
|
|
89
|
+
className={cn(
|
|
90
|
+
"group flex min-h-11 items-center gap-2 rounded-sm px-2 py-1.5 outline-none transition-colors hover:bg-accent/35 focus-visible:bg-accent/35 focus-visible:ring-1 focus-visible:ring-ring",
|
|
91
|
+
segment.status === "evicted" && "opacity-60",
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
<SegmentProvenancePopoverView segment={segment}>
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
className="min-w-0 flex-1 text-left"
|
|
98
|
+
aria-label={`Inspect ${segment.label}`}
|
|
99
|
+
>
|
|
100
|
+
<div
|
|
101
|
+
className={cn(
|
|
102
|
+
"truncate text-[13px] font-medium leading-5 text-foreground",
|
|
103
|
+
segment.status === "evicted" && "line-through",
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
{segment.label}
|
|
107
|
+
</div>
|
|
108
|
+
<div className="flex items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
109
|
+
<span>{formatContextTokens(segment.tokenCount)}</span>
|
|
110
|
+
{segment.tokenMethod === "estimate" ? <span>~</span> : null}
|
|
111
|
+
<span>·</span>
|
|
112
|
+
<span>{contextStatusLabel(segment)}</span>
|
|
113
|
+
{advisory ? <span>· advisory</span> : null}
|
|
114
|
+
</div>
|
|
115
|
+
</button>
|
|
116
|
+
</SegmentProvenancePopoverView>
|
|
117
|
+
<div className="flex shrink-0 items-center gap-1 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100">
|
|
118
|
+
{segment.protected ? (
|
|
119
|
+
<Tooltip>
|
|
120
|
+
<TooltipTrigger asChild>
|
|
121
|
+
<span className="flex size-6 items-center justify-center rounded-md text-muted-foreground">
|
|
122
|
+
<IconLock className="h-3.5 w-3.5" />
|
|
123
|
+
</span>
|
|
124
|
+
</TooltipTrigger>
|
|
125
|
+
<TooltipContent>Protected during active turn</TooltipContent>
|
|
126
|
+
</Tooltip>
|
|
127
|
+
) : segment.status === "evicted" ||
|
|
128
|
+
segment.status === "summarized" ||
|
|
129
|
+
segment.status === "pinned" ? (
|
|
130
|
+
<Tooltip>
|
|
131
|
+
<TooltipTrigger asChild>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
onClick={onRestore}
|
|
135
|
+
className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
|
|
136
|
+
aria-label={
|
|
137
|
+
segment.status === "pinned"
|
|
138
|
+
? "Unpin segment"
|
|
139
|
+
: "Restore segment"
|
|
140
|
+
}
|
|
141
|
+
>
|
|
142
|
+
<IconRotate2 className="h-3.5 w-3.5" />
|
|
143
|
+
</button>
|
|
144
|
+
</TooltipTrigger>
|
|
145
|
+
<TooltipContent>
|
|
146
|
+
{segment.status === "pinned" ? "Unpin" : "Restore"}
|
|
147
|
+
</TooltipContent>
|
|
148
|
+
</Tooltip>
|
|
149
|
+
) : (
|
|
150
|
+
<>
|
|
151
|
+
<Tooltip>
|
|
152
|
+
<TooltipTrigger asChild>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
onClick={onPin}
|
|
156
|
+
className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
|
|
157
|
+
aria-label="Pin segment"
|
|
158
|
+
>
|
|
159
|
+
<IconPin className="h-3.5 w-3.5" />
|
|
160
|
+
</button>
|
|
161
|
+
</TooltipTrigger>
|
|
162
|
+
<TooltipContent>Pin</TooltipContent>
|
|
163
|
+
</Tooltip>
|
|
164
|
+
<Tooltip>
|
|
165
|
+
<TooltipTrigger asChild>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
onClick={onEvict}
|
|
169
|
+
className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-destructive"
|
|
170
|
+
aria-label="Evict segment"
|
|
171
|
+
>
|
|
172
|
+
<IconX className="h-3.5 w-3.5" />
|
|
173
|
+
</button>
|
|
174
|
+
</TooltipTrigger>
|
|
175
|
+
<TooltipContent>
|
|
176
|
+
{advisory ? "Record eviction intent" : "Evict"}
|
|
177
|
+
</TooltipContent>
|
|
178
|
+
</Tooltip>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Treemap } from "recharts";
|
|
3
|
+
|
|
4
|
+
import { contextGroupFill, formatContextTokens } from "./format.js";
|
|
5
|
+
import type {
|
|
6
|
+
ContextSegmentViewData,
|
|
7
|
+
ContextSystemSectionViewData,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
|
|
10
|
+
interface TreemapDatum {
|
|
11
|
+
name: string;
|
|
12
|
+
size: number;
|
|
13
|
+
group: string;
|
|
14
|
+
segmentId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function TreemapContent(props: any) {
|
|
18
|
+
const { x, y, width, height, name, group, size } = props;
|
|
19
|
+
if (width < 8 || height < 8) return null;
|
|
20
|
+
const showLabel = width > 90 && height > 42;
|
|
21
|
+
return (
|
|
22
|
+
<g>
|
|
23
|
+
<rect
|
|
24
|
+
x={x}
|
|
25
|
+
y={y}
|
|
26
|
+
width={width}
|
|
27
|
+
height={height}
|
|
28
|
+
rx={3}
|
|
29
|
+
ry={3}
|
|
30
|
+
fill={contextGroupFill(group)}
|
|
31
|
+
stroke="var(--background)"
|
|
32
|
+
strokeWidth={2}
|
|
33
|
+
/>
|
|
34
|
+
{showLabel ? (
|
|
35
|
+
<foreignObject
|
|
36
|
+
x={x + 6}
|
|
37
|
+
y={y + 6}
|
|
38
|
+
width={width - 12}
|
|
39
|
+
height={height - 12}
|
|
40
|
+
>
|
|
41
|
+
<div className="flex h-full flex-col justify-between overflow-hidden text-[11px] leading-tight text-white">
|
|
42
|
+
<div className="truncate font-medium">{name}</div>
|
|
43
|
+
<div className="text-white/85">{formatContextTokens(size)}</div>
|
|
44
|
+
</div>
|
|
45
|
+
</foreignObject>
|
|
46
|
+
) : null}
|
|
47
|
+
</g>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ContextTreemapView({
|
|
52
|
+
segments,
|
|
53
|
+
systemSections = [],
|
|
54
|
+
onSelect,
|
|
55
|
+
}: {
|
|
56
|
+
segments: ContextSegmentViewData[];
|
|
57
|
+
systemSections?: ContextSystemSectionViewData[];
|
|
58
|
+
onSelect?: (segmentId: string) => void;
|
|
59
|
+
}) {
|
|
60
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
61
|
+
const [chartWidth, setChartWidth] = useState(0);
|
|
62
|
+
const data: TreemapDatum[] = segments
|
|
63
|
+
.filter((segment) => segment.status !== "evicted" && segment.tokenCount > 0)
|
|
64
|
+
.map((segment) => ({
|
|
65
|
+
name: segment.label,
|
|
66
|
+
size: segment.tokenCount,
|
|
67
|
+
group: segment.group,
|
|
68
|
+
segmentId: segment.segmentId,
|
|
69
|
+
}));
|
|
70
|
+
data.push(
|
|
71
|
+
...systemSections
|
|
72
|
+
.filter((section) => section.tokenCount > 0)
|
|
73
|
+
.map((section) => ({
|
|
74
|
+
name: section.label,
|
|
75
|
+
size: section.tokenCount,
|
|
76
|
+
group: `System · ${section.governance}`,
|
|
77
|
+
segmentId: section.segmentId,
|
|
78
|
+
})),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const element = containerRef.current;
|
|
83
|
+
if (!element) return;
|
|
84
|
+
const updateWidth = () =>
|
|
85
|
+
setChartWidth(Math.max(0, Math.floor(element.clientWidth - 8)));
|
|
86
|
+
updateWidth();
|
|
87
|
+
const observer =
|
|
88
|
+
typeof ResizeObserver !== "undefined"
|
|
89
|
+
? new ResizeObserver(updateWidth)
|
|
90
|
+
: null;
|
|
91
|
+
observer?.observe(element);
|
|
92
|
+
window.addEventListener("resize", updateWidth);
|
|
93
|
+
return () => {
|
|
94
|
+
observer?.disconnect();
|
|
95
|
+
window.removeEventListener("resize", updateWidth);
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
if (data.length === 0) {
|
|
100
|
+
return (
|
|
101
|
+
<div className="flex h-52 items-center justify-center rounded-md bg-muted/30 text-xs text-muted-foreground">
|
|
102
|
+
No active segments
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return (
|
|
107
|
+
<div ref={containerRef} className="h-56 min-w-0 rounded-md bg-muted/25 p-1">
|
|
108
|
+
{chartWidth > 0 ? (
|
|
109
|
+
<Treemap
|
|
110
|
+
width={chartWidth}
|
|
111
|
+
height={216}
|
|
112
|
+
data={data as any}
|
|
113
|
+
dataKey="size"
|
|
114
|
+
nameKey="name"
|
|
115
|
+
aspectRatio={4 / 3}
|
|
116
|
+
isAnimationActive={false}
|
|
117
|
+
content={<TreemapContent />}
|
|
118
|
+
onClick={(datum: any) => {
|
|
119
|
+
if (datum?.segmentId) onSelect?.(datum.segmentId);
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
) : null}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|