@agent-native/toolkit 0.5.1 → 0.7.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/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/dist/composer/AgentComposerFrame.d.ts +19 -0
- package/dist/composer/AgentComposerFrame.d.ts.map +1 -0
- package/dist/composer/AgentComposerFrame.js +14 -0
- package/dist/composer/AgentComposerFrame.js.map +1 -0
- package/dist/composer/ComposerPlusMenu.d.ts +17 -0
- package/dist/composer/ComposerPlusMenu.d.ts.map +1 -0
- package/dist/composer/ComposerPlusMenu.js +501 -0
- package/dist/composer/ComposerPlusMenu.js.map +1 -0
- package/dist/composer/MentionPopover.d.ts +29 -0
- package/dist/composer/MentionPopover.d.ts.map +1 -0
- package/dist/composer/MentionPopover.js +180 -0
- package/dist/composer/MentionPopover.js.map +1 -0
- package/dist/composer/PastedTextChip.d.ts +9 -0
- package/dist/composer/PastedTextChip.d.ts.map +1 -0
- package/dist/composer/PastedTextChip.js +47 -0
- package/dist/composer/PastedTextChip.js.map +1 -0
- package/dist/composer/PromptComposer.d.ts +114 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -0
- package/dist/composer/PromptComposer.js +302 -0
- package/dist/composer/PromptComposer.js.map +1 -0
- package/dist/composer/PromptComposer.spec.d.ts +2 -0
- package/dist/composer/PromptComposer.spec.d.ts.map +1 -0
- package/dist/composer/PromptComposer.spec.js +64 -0
- package/dist/composer/PromptComposer.spec.js.map +1 -0
- package/dist/composer/RealtimeVoiceMode.d.ts +104 -0
- package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -0
- package/dist/composer/RealtimeVoiceMode.js +245 -0
- package/dist/composer/RealtimeVoiceMode.js.map +1 -0
- package/dist/composer/RealtimeVoiceMode.spec.d.ts +2 -0
- package/dist/composer/RealtimeVoiceMode.spec.d.ts.map +1 -0
- package/dist/composer/RealtimeVoiceMode.spec.js +439 -0
- package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -0
- package/dist/composer/TiptapComposer.d.ts +221 -0
- package/dist/composer/TiptapComposer.d.ts.map +1 -0
- package/dist/composer/TiptapComposer.js +1838 -0
- package/dist/composer/TiptapComposer.js.map +1 -0
- package/dist/composer/TiptapComposer.spec.d.ts +2 -0
- package/dist/composer/TiptapComposer.spec.d.ts.map +1 -0
- package/dist/composer/TiptapComposer.spec.js +277 -0
- package/dist/composer/TiptapComposer.spec.js.map +1 -0
- package/dist/composer/VoiceButton.d.ts +25 -0
- package/dist/composer/VoiceButton.d.ts.map +1 -0
- package/dist/composer/VoiceButton.js +100 -0
- package/dist/composer/VoiceButton.js.map +1 -0
- package/dist/composer/VoiceButton.spec.d.ts +2 -0
- package/dist/composer/VoiceButton.spec.d.ts.map +1 -0
- package/dist/composer/VoiceButton.spec.js +19 -0
- package/dist/composer/VoiceButton.spec.js.map +1 -0
- package/dist/composer/asset-picker-url.d.ts +16 -0
- package/dist/composer/asset-picker-url.d.ts.map +1 -0
- package/dist/composer/asset-picker-url.js +64 -0
- package/dist/composer/asset-picker-url.js.map +1 -0
- package/dist/composer/asset-picker-url.spec.d.ts +2 -0
- package/dist/composer/asset-picker-url.spec.d.ts.map +1 -0
- package/dist/composer/asset-picker-url.spec.js +23 -0
- package/dist/composer/asset-picker-url.spec.js.map +1 -0
- package/dist/composer/attachment-accept.d.ts +9 -0
- package/dist/composer/attachment-accept.d.ts.map +1 -0
- package/dist/composer/attachment-accept.js +65 -0
- package/dist/composer/attachment-accept.js.map +1 -0
- package/dist/composer/attachment-accept.spec.d.ts +2 -0
- package/dist/composer/attachment-accept.spec.d.ts.map +1 -0
- package/dist/composer/attachment-accept.spec.js +15 -0
- package/dist/composer/attachment-accept.spec.js.map +1 -0
- package/dist/composer/draft-key.d.ts +2 -0
- package/dist/composer/draft-key.d.ts.map +1 -0
- package/dist/composer/draft-key.js +8 -0
- package/dist/composer/draft-key.js.map +1 -0
- package/dist/composer/draft-key.spec.d.ts +2 -0
- package/dist/composer/draft-key.spec.d.ts.map +1 -0
- package/dist/composer/draft-key.spec.js +13 -0
- package/dist/composer/draft-key.spec.js.map +1 -0
- package/dist/composer/extensions/FileReference.d.ts +3 -0
- package/dist/composer/extensions/FileReference.d.ts.map +1 -0
- package/dist/composer/extensions/FileReference.js +36 -0
- package/dist/composer/extensions/FileReference.js.map +1 -0
- package/dist/composer/extensions/MentionReference.d.ts +3 -0
- package/dist/composer/extensions/MentionReference.d.ts.map +1 -0
- package/dist/composer/extensions/MentionReference.js +66 -0
- package/dist/composer/extensions/MentionReference.js.map +1 -0
- package/dist/composer/extensions/SkillReference.d.ts +3 -0
- package/dist/composer/extensions/SkillReference.d.ts.map +1 -0
- package/dist/composer/extensions/SkillReference.js +40 -0
- package/dist/composer/extensions/SkillReference.js.map +1 -0
- package/dist/composer/index.d.ts +14 -0
- package/dist/composer/index.d.ts.map +1 -0
- package/dist/composer/index.js +13 -0
- package/dist/composer/index.js.map +1 -0
- package/dist/composer/pasted-text.d.ts +31 -0
- package/dist/composer/pasted-text.d.ts.map +1 -0
- package/dist/composer/pasted-text.js +130 -0
- package/dist/composer/pasted-text.js.map +1 -0
- package/dist/composer/pasted-text.spec.d.ts +2 -0
- package/dist/composer/pasted-text.spec.d.ts.map +1 -0
- package/dist/composer/pasted-text.spec.js +107 -0
- package/dist/composer/pasted-text.spec.js.map +1 -0
- package/dist/composer/prompt-attachments.d.ts +18 -0
- package/dist/composer/prompt-attachments.d.ts.map +1 -0
- package/dist/composer/prompt-attachments.js +65 -0
- package/dist/composer/prompt-attachments.js.map +1 -0
- package/dist/composer/prompt-attachments.spec.d.ts +2 -0
- package/dist/composer/prompt-attachments.spec.d.ts.map +1 -0
- package/dist/composer/prompt-attachments.spec.js +63 -0
- package/dist/composer/prompt-attachments.spec.js.map +1 -0
- package/dist/composer/realtime-voice-audio-level.d.ts +14 -0
- package/dist/composer/realtime-voice-audio-level.d.ts.map +1 -0
- package/dist/composer/realtime-voice-audio-level.js +47 -0
- package/dist/composer/realtime-voice-audio-level.js.map +1 -0
- package/dist/composer/realtime-voice-audio-level.spec.d.ts +2 -0
- package/dist/composer/realtime-voice-audio-level.spec.d.ts.map +1 -0
- package/dist/composer/realtime-voice-audio-level.spec.js +25 -0
- package/dist/composer/realtime-voice-audio-level.spec.js.map +1 -0
- package/dist/composer/realtime-voice-transcript.d.ts +26 -0
- package/dist/composer/realtime-voice-transcript.d.ts.map +1 -0
- package/dist/composer/realtime-voice-transcript.js +103 -0
- package/dist/composer/realtime-voice-transcript.js.map +1 -0
- package/dist/composer/realtime-voice-transcript.spec.d.ts +2 -0
- package/dist/composer/realtime-voice-transcript.spec.d.ts.map +1 -0
- package/dist/composer/realtime-voice-transcript.spec.js +76 -0
- package/dist/composer/realtime-voice-transcript.spec.js.map +1 -0
- package/dist/composer/runtime-adapters.d.ts +163 -0
- package/dist/composer/runtime-adapters.d.ts.map +1 -0
- package/dist/composer/runtime-adapters.js +130 -0
- package/dist/composer/runtime-adapters.js.map +1 -0
- package/dist/composer/types.d.ts +60 -0
- package/dist/composer/types.d.ts.map +1 -0
- package/dist/composer/types.js +2 -0
- package/dist/composer/types.js.map +1 -0
- package/dist/composer/use-file-search.d.ts +6 -0
- package/dist/composer/use-file-search.d.ts.map +1 -0
- package/dist/composer/use-file-search.js +53 -0
- package/dist/composer/use-file-search.js.map +1 -0
- package/dist/composer/use-mention-search.d.ts +6 -0
- package/dist/composer/use-mention-search.d.ts.map +1 -0
- package/dist/composer/use-mention-search.js +75 -0
- package/dist/composer/use-mention-search.js.map +1 -0
- package/dist/composer/use-skills.d.ts +7 -0
- package/dist/composer/use-skills.d.ts.map +1 -0
- package/dist/composer/use-skills.js +40 -0
- package/dist/composer/use-skills.js.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.d.ts +179 -0
- package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.js +1657 -0
- package/dist/composer/useRealtimeVoiceMode.js.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.spec.d.ts +2 -0
- package/dist/composer/useRealtimeVoiceMode.spec.d.ts.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.spec.js +618 -0
- package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -0
- package/dist/composer/useVoiceDictation.d.ts +57 -0
- package/dist/composer/useVoiceDictation.d.ts.map +1 -0
- package/dist/composer/useVoiceDictation.js +1004 -0
- package/dist/composer/useVoiceDictation.js.map +1 -0
- package/dist/composer/useVoiceDictation.spec.d.ts +2 -0
- package/dist/composer/useVoiceDictation.spec.d.ts.map +1 -0
- package/dist/composer/useVoiceDictation.spec.js +32 -0
- package/dist/composer/useVoiceDictation.spec.js.map +1 -0
- package/dist/editor/RegistryBlockContext.d.ts +68 -0
- package/dist/editor/RegistryBlockContext.d.ts.map +1 -0
- package/dist/editor/RegistryBlockContext.js +10 -0
- package/dist/editor/RegistryBlockContext.js.map +1 -0
- package/dist/editor/RegistryBlockNode.d.ts +66 -0
- package/dist/editor/RegistryBlockNode.d.ts.map +1 -0
- package/dist/editor/RegistryBlockNode.js +406 -0
- package/dist/editor/RegistryBlockNode.js.map +1 -0
- package/dist/editor/RegistryBlockNode.spec.d.ts +2 -0
- package/dist/editor/RegistryBlockNode.spec.d.ts.map +1 -0
- package/dist/editor/RegistryBlockNode.spec.js +215 -0
- package/dist/editor/RegistryBlockNode.spec.js.map +1 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +2 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/registrySlashCommands.d.ts +68 -0
- package/dist/editor/registrySlashCommands.d.ts.map +1 -0
- package/dist/editor/registrySlashCommands.js +51 -0
- package/dist/editor/registrySlashCommands.js.map +1 -0
- package/package.json +32 -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,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemoteSelectionRings — renders colored outline rings + name tags over
|
|
3
|
+
* elements selected by remote participants.
|
|
4
|
+
*
|
|
5
|
+
* Each participant's presence payload may contain a `selection` key with an
|
|
6
|
+
* opaque descriptor (e.g. a CSS selector). The `resolveRect` callback maps
|
|
7
|
+
* a descriptor to a DOMRect (or null when the element isn't found). Rings
|
|
8
|
+
* are rendered as absolutely-positioned outlines anchored to the container.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* <div style={{ position: "relative" }}>
|
|
12
|
+
* {content}
|
|
13
|
+
* <RemoteSelectionRings
|
|
14
|
+
* others={others}
|
|
15
|
+
* resolveRect={(selector) => document.querySelector(selector)?.getBoundingClientRect() ?? null}
|
|
16
|
+
* containerRef={containerRef}
|
|
17
|
+
* />
|
|
18
|
+
* </div>
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
useState,
|
|
23
|
+
useEffect,
|
|
24
|
+
useRef,
|
|
25
|
+
useLayoutEffect,
|
|
26
|
+
memo,
|
|
27
|
+
type RefObject,
|
|
28
|
+
} from "react";
|
|
29
|
+
|
|
30
|
+
import type { OtherPresence } from "./types.js";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Selection descriptors may be a plain string (treated as the resolver
|
|
34
|
+
* input) or an object carrying a resolver input plus a human label
|
|
35
|
+
* ("Editing hero section").
|
|
36
|
+
*/
|
|
37
|
+
export type SelectionDescriptor = string | { selector: string; label?: string };
|
|
38
|
+
|
|
39
|
+
export interface RemoteSelectionRingsProps {
|
|
40
|
+
/** Remote participants. */
|
|
41
|
+
others: OtherPresence[];
|
|
42
|
+
/**
|
|
43
|
+
* Key inside presence payload that carries the selection descriptor.
|
|
44
|
+
* Default: "selection"
|
|
45
|
+
*/
|
|
46
|
+
selectionKey?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Resolver: maps a selection descriptor to a DOMRect relative to the
|
|
49
|
+
* viewport. Return null when the element is not found.
|
|
50
|
+
*/
|
|
51
|
+
resolveRect: (descriptor: string) => DOMRect | null;
|
|
52
|
+
/**
|
|
53
|
+
* Container element ref. Rings are positioned relative to this element's
|
|
54
|
+
* bounding box.
|
|
55
|
+
*/
|
|
56
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
57
|
+
/** Additional CSS class for the overlay div. */
|
|
58
|
+
className?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface Ring {
|
|
62
|
+
clientId: number;
|
|
63
|
+
color: string;
|
|
64
|
+
label: string;
|
|
65
|
+
avatarUrl?: string;
|
|
66
|
+
isAgent: boolean;
|
|
67
|
+
rect: { top: number; left: number; width: number; height: number };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const RingItem = memo(function RingItem({ ring }: { ring: Ring }) {
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
aria-label={`${ring.label} selection`}
|
|
74
|
+
style={{
|
|
75
|
+
position: "absolute",
|
|
76
|
+
top: ring.rect.top,
|
|
77
|
+
left: ring.rect.left,
|
|
78
|
+
width: ring.rect.width,
|
|
79
|
+
height: ring.rect.height,
|
|
80
|
+
outline: `2px solid ${ring.color}`,
|
|
81
|
+
outlineOffset: 2,
|
|
82
|
+
borderRadius: 3,
|
|
83
|
+
pointerEvents: "none",
|
|
84
|
+
boxShadow: `0 0 0 1px ${ring.color}40`,
|
|
85
|
+
zIndex: 9998,
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{/* Name tag in top-left corner of the ring */}
|
|
89
|
+
<div
|
|
90
|
+
style={{
|
|
91
|
+
position: "absolute",
|
|
92
|
+
top: -20,
|
|
93
|
+
left: 0,
|
|
94
|
+
display: "flex",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
gap: 4,
|
|
97
|
+
backgroundColor: ring.color,
|
|
98
|
+
color: "#fff",
|
|
99
|
+
fontSize: 10,
|
|
100
|
+
fontWeight: 600,
|
|
101
|
+
padding: "1px 5px",
|
|
102
|
+
borderRadius: 3,
|
|
103
|
+
whiteSpace: "nowrap",
|
|
104
|
+
maxWidth: 160,
|
|
105
|
+
overflow: "hidden",
|
|
106
|
+
textOverflow: "ellipsis",
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{ring.avatarUrl ? (
|
|
110
|
+
<img
|
|
111
|
+
src={ring.avatarUrl}
|
|
112
|
+
alt=""
|
|
113
|
+
style={{
|
|
114
|
+
width: 12,
|
|
115
|
+
height: 12,
|
|
116
|
+
borderRadius: "50%",
|
|
117
|
+
flexShrink: 0,
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
) : null}
|
|
121
|
+
{ring.label}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export function RemoteSelectionRings({
|
|
128
|
+
others,
|
|
129
|
+
selectionKey = "selection",
|
|
130
|
+
resolveRect,
|
|
131
|
+
containerRef,
|
|
132
|
+
className,
|
|
133
|
+
}: RemoteSelectionRingsProps) {
|
|
134
|
+
const overlayRef = useRef<HTMLDivElement>(null);
|
|
135
|
+
const [rings, setRings] = useState<Ring[]>([]);
|
|
136
|
+
|
|
137
|
+
// Recompute rings whenever others change or on animation frame.
|
|
138
|
+
const recompute = () => {
|
|
139
|
+
const container = containerRef.current;
|
|
140
|
+
if (!container) {
|
|
141
|
+
setRings([]);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const containerRect = container.getBoundingClientRect();
|
|
145
|
+
const next: Ring[] = [];
|
|
146
|
+
|
|
147
|
+
for (const other of others) {
|
|
148
|
+
const raw = other.presence[selectionKey] as
|
|
149
|
+
| SelectionDescriptor
|
|
150
|
+
| null
|
|
151
|
+
| undefined;
|
|
152
|
+
const selector =
|
|
153
|
+
typeof raw === "string" ? raw : raw ? raw.selector : undefined;
|
|
154
|
+
if (!selector || typeof selector !== "string") continue;
|
|
155
|
+
const selectionLabel =
|
|
156
|
+
raw && typeof raw === "object" ? raw.label : undefined;
|
|
157
|
+
|
|
158
|
+
const domRect = resolveRect(selector);
|
|
159
|
+
if (!domRect) continue;
|
|
160
|
+
|
|
161
|
+
// Convert viewport-relative rect to container-relative.
|
|
162
|
+
const top = domRect.top - containerRect.top;
|
|
163
|
+
const left = domRect.left - containerRect.left;
|
|
164
|
+
if (
|
|
165
|
+
left + domRect.width < 0 ||
|
|
166
|
+
top + domRect.height < 0 ||
|
|
167
|
+
left > containerRect.width ||
|
|
168
|
+
top > containerRect.height
|
|
169
|
+
) {
|
|
170
|
+
continue; // Out of container bounds — skip.
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const baseName = other.isAgent
|
|
174
|
+
? "AI"
|
|
175
|
+
: other.user.name || other.user.email;
|
|
176
|
+
next.push({
|
|
177
|
+
clientId: other.clientId,
|
|
178
|
+
color: other.user.color || "#94a3b8",
|
|
179
|
+
label: selectionLabel ? `${baseName} — ${selectionLabel}` : baseName,
|
|
180
|
+
avatarUrl: (other.user as { avatarUrl?: string }).avatarUrl,
|
|
181
|
+
isAgent: other.isAgent,
|
|
182
|
+
rect: { top, left, width: domRect.width, height: domRect.height },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setRings(next);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Recompute on scroll/resize of the container to keep rings in sync.
|
|
190
|
+
useLayoutEffect(() => {
|
|
191
|
+
recompute();
|
|
192
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
193
|
+
}, [others, selectionKey]);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
const container = containerRef.current;
|
|
197
|
+
if (!container) return;
|
|
198
|
+
const observer = new ResizeObserver(() => recompute());
|
|
199
|
+
observer.observe(container);
|
|
200
|
+
container.addEventListener("scroll", recompute, { passive: true });
|
|
201
|
+
window.addEventListener("scroll", recompute, { passive: true });
|
|
202
|
+
return () => {
|
|
203
|
+
observer.disconnect();
|
|
204
|
+
container.removeEventListener("scroll", recompute);
|
|
205
|
+
window.removeEventListener("scroll", recompute);
|
|
206
|
+
};
|
|
207
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
208
|
+
}, [containerRef]);
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div
|
|
212
|
+
ref={overlayRef}
|
|
213
|
+
aria-hidden
|
|
214
|
+
style={{
|
|
215
|
+
position: "absolute",
|
|
216
|
+
inset: 0,
|
|
217
|
+
pointerEvents: "none",
|
|
218
|
+
overflow: "hidden",
|
|
219
|
+
}}
|
|
220
|
+
className={className}
|
|
221
|
+
>
|
|
222
|
+
{rings.map((ring) => (
|
|
223
|
+
<RingItem key={ring.clientId} ring={ring} />
|
|
224
|
+
))}
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Reserve the maximum signed 32-bit id so the agent never wins client leader election.
|
|
2
|
+
export const AGENT_CLIENT_ID = 2147483647;
|
|
3
|
+
|
|
4
|
+
export interface AgentIdentity {
|
|
5
|
+
clientId: number;
|
|
6
|
+
name: string;
|
|
7
|
+
email: string;
|
|
8
|
+
color: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_AGENT_IDENTITY: AgentIdentity = {
|
|
12
|
+
clientId: AGENT_CLIENT_ID,
|
|
13
|
+
name: "AI Assistant",
|
|
14
|
+
email: "agent@system",
|
|
15
|
+
color: "#00B5FF",
|
|
16
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AgentPresenceChip,
|
|
3
|
+
type AgentPresenceChipProps,
|
|
4
|
+
} from "./AgentPresenceChip.js";
|
|
5
|
+
export {
|
|
6
|
+
AGENT_CLIENT_ID,
|
|
7
|
+
DEFAULT_AGENT_IDENTITY,
|
|
8
|
+
type AgentIdentity,
|
|
9
|
+
} from "./agent-identity.js";
|
|
10
|
+
export {
|
|
11
|
+
LiveCursorOverlay,
|
|
12
|
+
type CursorMapFn,
|
|
13
|
+
type LiveCursorOverlayProps,
|
|
14
|
+
} from "./LiveCursorOverlay.js";
|
|
15
|
+
export { PresenceBar, type PresenceBarProps } from "./PresenceBar.js";
|
|
16
|
+
export { isReconcileLeadClient } from "./lead-client.js";
|
|
17
|
+
export {
|
|
18
|
+
RecentEditHighlights,
|
|
19
|
+
type RecentEditHighlightsProps,
|
|
20
|
+
} from "./RecentEditHighlights.js";
|
|
21
|
+
export {
|
|
22
|
+
RemoteSelectionRings,
|
|
23
|
+
type RemoteSelectionRingsProps,
|
|
24
|
+
type SelectionDescriptor,
|
|
25
|
+
} from "./RemoteSelectionRings.js";
|
|
26
|
+
export {
|
|
27
|
+
RECENT_EDITS_MAX,
|
|
28
|
+
RECENT_EDIT_TTL_MS,
|
|
29
|
+
dedupeCollabUsersByEmail,
|
|
30
|
+
emailToColor,
|
|
31
|
+
emailToName,
|
|
32
|
+
type AttributedRecentEdit,
|
|
33
|
+
type CollabUser,
|
|
34
|
+
type NormalizedPoint,
|
|
35
|
+
type OtherPresence,
|
|
36
|
+
type PresencePayload,
|
|
37
|
+
type RecentEdit,
|
|
38
|
+
type RecentEditDescriptor,
|
|
39
|
+
} from "./types.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Awareness } from "y-protocols/awareness";
|
|
2
|
+
|
|
3
|
+
import { AGENT_CLIENT_ID } from "./agent-identity.js";
|
|
4
|
+
|
|
5
|
+
// Exactly one visible non-agent client may apply an external snapshot; allowing
|
|
6
|
+
// every client to reconcile it duplicates concurrent CRDT inserts.
|
|
7
|
+
export function isReconcileLeadClient(
|
|
8
|
+
awareness: Awareness | null | undefined,
|
|
9
|
+
localClientId: number | null | undefined,
|
|
10
|
+
): boolean {
|
|
11
|
+
if (localClientId == null) return false;
|
|
12
|
+
if (!awareness) return true;
|
|
13
|
+
|
|
14
|
+
let hasPeer = false;
|
|
15
|
+
let minVisible = localClientId;
|
|
16
|
+
awareness.getStates().forEach((state, clientId) => {
|
|
17
|
+
if (clientId === AGENT_CLIENT_ID || clientId === localClientId) return;
|
|
18
|
+
const candidate = state as { user?: unknown; visible?: boolean };
|
|
19
|
+
if (!candidate?.user) return;
|
|
20
|
+
hasPeer = true;
|
|
21
|
+
if (candidate.visible !== false && clientId < minVisible) {
|
|
22
|
+
minVisible = clientId;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!hasPeer) return true;
|
|
27
|
+
|
|
28
|
+
const localHidden =
|
|
29
|
+
typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
30
|
+
if (localHidden) return false;
|
|
31
|
+
return localClientId <= minVisible;
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { dedupeCollabUsersByEmail } from "./types.js";
|
|
4
|
+
|
|
5
|
+
describe("dedupeCollabUsersByEmail", () => {
|
|
6
|
+
it("ignores malformed awareness user payloads", () => {
|
|
7
|
+
expect(
|
|
8
|
+
dedupeCollabUsersByEmail([
|
|
9
|
+
{
|
|
10
|
+
name: "Broken",
|
|
11
|
+
email: undefined as unknown as string,
|
|
12
|
+
color: "#fff",
|
|
13
|
+
},
|
|
14
|
+
{ name: "Real", email: "real@example.com", color: "#000" },
|
|
15
|
+
]),
|
|
16
|
+
).toEqual([{ name: "Real", email: "real@example.com", color: "#000" }]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export interface CollabUser {
|
|
2
|
+
name: string;
|
|
3
|
+
email: string;
|
|
4
|
+
color: string;
|
|
5
|
+
avatarUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type PresencePayload = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export interface OtherPresence {
|
|
11
|
+
clientId: number;
|
|
12
|
+
user: CollabUser;
|
|
13
|
+
presence: PresencePayload;
|
|
14
|
+
isAgent: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface NormalizedPoint {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type RecentEditDescriptor =
|
|
23
|
+
| { kind: "text"; quote: string }
|
|
24
|
+
| { kind: "selector"; selector: string }
|
|
25
|
+
| { kind: "paths"; paths: string[] }
|
|
26
|
+
| { kind: "doc" }
|
|
27
|
+
| { kind: string; [key: string]: unknown };
|
|
28
|
+
|
|
29
|
+
export interface RecentEdit {
|
|
30
|
+
descriptor: RecentEditDescriptor;
|
|
31
|
+
label?: string;
|
|
32
|
+
at: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AttributedRecentEdit extends RecentEdit {
|
|
36
|
+
clientId: number;
|
|
37
|
+
user: OtherPresence["user"];
|
|
38
|
+
isAgent: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const RECENT_EDITS_MAX = 5;
|
|
42
|
+
export const RECENT_EDIT_TTL_MS = 10_000;
|
|
43
|
+
|
|
44
|
+
const CURSOR_COLORS = [
|
|
45
|
+
"#f87171",
|
|
46
|
+
"#fb923c",
|
|
47
|
+
"#fbbf24",
|
|
48
|
+
"#a3e635",
|
|
49
|
+
"#34d399",
|
|
50
|
+
"#22d3ee",
|
|
51
|
+
"#60a5fa",
|
|
52
|
+
"#14b8a6",
|
|
53
|
+
"#f472b6",
|
|
54
|
+
"#e879f9",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export function emailToColor(email: string): string {
|
|
58
|
+
let hash = 0;
|
|
59
|
+
for (let i = 0; i < email.length; i++) {
|
|
60
|
+
hash = ((hash << 5) - hash + email.charCodeAt(i)) | 0;
|
|
61
|
+
}
|
|
62
|
+
return CURSOR_COLORS[Math.abs(hash) % CURSOR_COLORS.length];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function emailToName(email: string): string {
|
|
66
|
+
const local = email.split("@")[0] || email;
|
|
67
|
+
return local.charAt(0).toUpperCase() + local.slice(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeCollabEmail(email: string): string {
|
|
71
|
+
return email.trim().toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function dedupeCollabUsersByEmail(users: CollabUser[]): CollabUser[] {
|
|
75
|
+
const byEmail = new Map<string, CollabUser>();
|
|
76
|
+
for (const user of users) {
|
|
77
|
+
// Awareness is a network boundary. Older clients or partially-written
|
|
78
|
+
// states can contain a malformed user payload; ignore it instead of
|
|
79
|
+
// letting a missing email reach normalizeCollabEmail().
|
|
80
|
+
if (!user || typeof user.email !== "string") continue;
|
|
81
|
+
const email = normalizeCollabEmail(user.email);
|
|
82
|
+
if (!email || byEmail.has(email)) continue;
|
|
83
|
+
byEmail.set(email, {
|
|
84
|
+
name:
|
|
85
|
+
typeof user.name === "string" && user.name.trim()
|
|
86
|
+
? user.name
|
|
87
|
+
: emailToName(email),
|
|
88
|
+
email,
|
|
89
|
+
color:
|
|
90
|
+
typeof user.color === "string" && user.color.trim()
|
|
91
|
+
? user.color
|
|
92
|
+
: emailToColor(email),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return Array.from(byEmail.values());
|
|
96
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ComposerPrimitive } from "@assistant-ui/react";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils.js";
|
|
5
|
+
import type { AgentComposerLayoutVariant } from "./types.js";
|
|
6
|
+
|
|
7
|
+
export interface AgentComposerFrameProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
rootClassName?: string;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
rootStyle?: React.CSSProperties;
|
|
13
|
+
layoutVariant?: AgentComposerLayoutVariant;
|
|
14
|
+
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The single visual shell for agent chat composition.
|
|
19
|
+
*
|
|
20
|
+
* AssistantChat, PromptComposer, and host surfaces such as Agent-Native Code
|
|
21
|
+
* all render this same frame so the composer does not drift across products.
|
|
22
|
+
*/
|
|
23
|
+
export function AgentComposerFrame({
|
|
24
|
+
children,
|
|
25
|
+
className,
|
|
26
|
+
rootClassName,
|
|
27
|
+
style,
|
|
28
|
+
rootStyle,
|
|
29
|
+
layoutVariant = "default",
|
|
30
|
+
onClick,
|
|
31
|
+
}: AgentComposerFrameProps) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-agent-composer-variant={layoutVariant}
|
|
35
|
+
data-agent-composer-slot="area"
|
|
36
|
+
className={cn(
|
|
37
|
+
"agent-composer-area shrink-0 px-3 py-2",
|
|
38
|
+
layoutVariant !== "default" && `agent-composer-area--${layoutVariant}`,
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
style={style}
|
|
42
|
+
onClick={onClick}
|
|
43
|
+
>
|
|
44
|
+
<ComposerPrimitive.Root
|
|
45
|
+
data-agent-composer-variant={layoutVariant}
|
|
46
|
+
data-agent-composer-slot="root"
|
|
47
|
+
className={cn(
|
|
48
|
+
"agent-composer-root flex flex-col rounded-lg border border-input bg-muted/45 transition-colors focus-within:border-ring",
|
|
49
|
+
layoutVariant !== "default" &&
|
|
50
|
+
`agent-composer-root--${layoutVariant}`,
|
|
51
|
+
rootClassName,
|
|
52
|
+
)}
|
|
53
|
+
style={rootStyle}
|
|
54
|
+
>
|
|
55
|
+
{children}
|
|
56
|
+
</ComposerPrimitive.Root>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|