@fromsko/chat-ui 0.0.0-stage → 0.1.6

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.
Files changed (248) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -2
  3. package/lib/adapters/event-bus.d.ts +80 -0
  4. package/lib/adapters/event-bus.js +73 -0
  5. package/lib/adapters/index.d.ts +3 -0
  6. package/lib/adapters/index.js +3 -0
  7. package/lib/adapters/labels.d.ts +6 -0
  8. package/lib/adapters/labels.js +17 -0
  9. package/lib/adapters/stream-bridge.d.ts +9 -0
  10. package/lib/adapters/stream-bridge.js +7 -0
  11. package/lib/blocks/cards/AskCard.d.ts +8 -0
  12. package/lib/blocks/cards/AskCard.js +97 -0
  13. package/lib/blocks/cards/CompactionCard.d.ts +5 -0
  14. package/lib/blocks/cards/CompactionCard.js +5 -0
  15. package/lib/blocks/cards/ContextUsage.d.ts +14 -0
  16. package/lib/blocks/cards/ContextUsage.js +136 -0
  17. package/lib/blocks/cards/DisplayCard.d.ts +7 -0
  18. package/lib/blocks/cards/DisplayCard.js +27 -0
  19. package/lib/blocks/cards/MemoryPill.d.ts +5 -0
  20. package/lib/blocks/cards/MemoryPill.js +5 -0
  21. package/lib/blocks/cards/OutputCard.d.ts +5 -0
  22. package/lib/blocks/cards/OutputCard.js +13 -0
  23. package/lib/blocks/cards/PermissionCard.d.ts +21 -0
  24. package/lib/blocks/cards/PermissionCard.js +26 -0
  25. package/lib/blocks/cards/SpringCheck.d.ts +9 -0
  26. package/lib/blocks/cards/SpringCheck.js +17 -0
  27. package/lib/blocks/cards/TodoCard.d.ts +19 -0
  28. package/lib/blocks/cards/TodoCard.js +28 -0
  29. package/lib/blocks/cards/index.d.ts +10 -0
  30. package/lib/blocks/cards/index.js +10 -0
  31. package/lib/blocks/composer/Composer.d.ts +66 -0
  32. package/lib/blocks/composer/Composer.js +73 -0
  33. package/lib/blocks/composer/ComposerAttachments.d.ts +17 -0
  34. package/lib/blocks/composer/ComposerAttachments.js +10 -0
  35. package/lib/blocks/composer/ComposerGrip.d.ts +1 -0
  36. package/lib/blocks/composer/ComposerGrip.js +39 -0
  37. package/lib/blocks/composer/ComposerInput.d.ts +18 -0
  38. package/lib/blocks/composer/ComposerInput.js +60 -0
  39. package/lib/blocks/composer/ComposerMenus.d.ts +10 -0
  40. package/lib/blocks/composer/ComposerMenus.js +78 -0
  41. package/lib/blocks/composer/ComposerToolbar.d.ts +27 -0
  42. package/lib/blocks/composer/ComposerToolbar.js +16 -0
  43. package/lib/blocks/composer/MentionPicker.d.ts +13 -0
  44. package/lib/blocks/composer/MentionPicker.js +71 -0
  45. package/lib/blocks/composer/PromptPop.d.ts +7 -0
  46. package/lib/blocks/composer/PromptPop.js +40 -0
  47. package/lib/blocks/composer/QueuedPrompts.d.ts +14 -0
  48. package/lib/blocks/composer/QueuedPrompts.js +21 -0
  49. package/lib/blocks/composer/SlingButton.d.ts +10 -0
  50. package/lib/blocks/composer/SlingButton.js +112 -0
  51. package/lib/blocks/composer/index.d.ts +7 -0
  52. package/lib/blocks/composer/index.js +1 -0
  53. package/lib/blocks/conversation/ChatEmptyScene.d.ts +4 -0
  54. package/lib/blocks/conversation/ChatEmptyScene.js +21 -0
  55. package/lib/blocks/conversation/ChatHeader.d.ts +13 -0
  56. package/lib/blocks/conversation/ChatHeader.js +20 -0
  57. package/lib/blocks/conversation/ChatPane.d.ts +90 -0
  58. package/lib/blocks/conversation/ChatPane.js +60 -0
  59. package/lib/blocks/conversation/Conversation.d.ts +11 -0
  60. package/lib/blocks/conversation/Conversation.js +41 -0
  61. package/lib/blocks/conversation/LineSidebar.d.ts +14 -0
  62. package/lib/blocks/conversation/LineSidebar.js +42 -0
  63. package/lib/blocks/conversation/index.d.ts +5 -0
  64. package/lib/blocks/conversation/index.js +5 -0
  65. package/lib/blocks/index.d.ts +9 -0
  66. package/lib/blocks/index.js +9 -0
  67. package/lib/blocks/message/DayDivider.d.ts +10 -0
  68. package/lib/blocks/message/DayDivider.js +29 -0
  69. package/lib/blocks/message/DiffView.d.ts +5 -0
  70. package/lib/blocks/message/DiffView.js +18 -0
  71. package/lib/blocks/message/MermaidDiagram.d.ts +4 -0
  72. package/lib/blocks/message/MermaidDiagram.js +43 -0
  73. package/lib/blocks/message/Message.d.ts +17 -0
  74. package/lib/blocks/message/Message.js +45 -0
  75. package/lib/blocks/message/MessageActionBar.d.ts +18 -0
  76. package/lib/blocks/message/MessageActionBar.js +15 -0
  77. package/lib/blocks/message/MessageArtifacts.d.ts +13 -0
  78. package/lib/blocks/message/MessageArtifacts.js +9 -0
  79. package/lib/blocks/message/MessageAttachments.d.ts +5 -0
  80. package/lib/blocks/message/MessageAttachments.js +28 -0
  81. package/lib/blocks/message/MessageBubble.d.ts +11 -0
  82. package/lib/blocks/message/MessageBubble.js +31 -0
  83. package/lib/blocks/message/MessageRow.d.ts +20 -0
  84. package/lib/blocks/message/MessageRow.js +49 -0
  85. package/lib/blocks/message/MessageStream.d.ts +23 -0
  86. package/lib/blocks/message/MessageStream.js +80 -0
  87. package/lib/blocks/message/MessageTrace.d.ts +16 -0
  88. package/lib/blocks/message/MessageTrace.js +39 -0
  89. package/lib/blocks/message/PartView.d.ts +10 -0
  90. package/lib/blocks/message/PartView.js +29 -0
  91. package/lib/blocks/message/code-text.d.ts +15 -0
  92. package/lib/blocks/message/code-text.js +17 -0
  93. package/lib/blocks/message/index.d.ts +12 -0
  94. package/lib/blocks/message/index.js +12 -0
  95. package/lib/blocks/reasoning/Reasoning.d.ts +7 -0
  96. package/lib/blocks/reasoning/Reasoning.js +26 -0
  97. package/lib/blocks/reasoning/ReasoningBlock.d.ts +6 -0
  98. package/lib/blocks/reasoning/ReasoningBlock.js +5 -0
  99. package/lib/blocks/reasoning/index.d.ts +2 -0
  100. package/lib/blocks/reasoning/index.js +2 -0
  101. package/lib/blocks/selection/SelectionActions.d.ts +12 -0
  102. package/lib/blocks/selection/SelectionActions.js +29 -0
  103. package/lib/blocks/selection/SelectionBar.d.ts +10 -0
  104. package/lib/blocks/selection/SelectionBar.js +6 -0
  105. package/lib/blocks/selection/index.d.ts +2 -0
  106. package/lib/blocks/selection/index.js +2 -0
  107. package/lib/blocks/settings/DisplaySettingsPanel.d.ts +5 -0
  108. package/lib/blocks/settings/DisplaySettingsPanel.js +33 -0
  109. package/lib/blocks/settings/SquishSwitch.d.ts +8 -0
  110. package/lib/blocks/settings/SquishSwitch.js +21 -0
  111. package/lib/blocks/settings/index.d.ts +2 -0
  112. package/lib/blocks/settings/index.js +2 -0
  113. package/lib/blocks/status/LatticeLoader.d.ts +12 -0
  114. package/lib/blocks/status/LatticeLoader.js +22 -0
  115. package/lib/blocks/status/TypingIndicator.d.ts +5 -0
  116. package/lib/blocks/status/TypingIndicator.js +5 -0
  117. package/lib/blocks/status/index.d.ts +2 -0
  118. package/lib/blocks/status/index.js +2 -0
  119. package/lib/blocks/tool/SandboxCard.d.ts +5 -0
  120. package/lib/blocks/tool/SandboxCard.js +10 -0
  121. package/lib/blocks/tool/ToolCard.d.ts +10 -0
  122. package/lib/blocks/tool/ToolCard.js +32 -0
  123. package/lib/blocks/tool/ToolGroup.d.ts +6 -0
  124. package/lib/blocks/tool/ToolGroup.js +23 -0
  125. package/lib/blocks/tool/exec-envelope.d.ts +7 -0
  126. package/lib/blocks/tool/exec-envelope.js +43 -0
  127. package/lib/blocks/tool/index.d.ts +4 -0
  128. package/lib/blocks/tool/index.js +4 -0
  129. package/lib/domain/ask.d.ts +14 -0
  130. package/lib/domain/ask.js +50 -0
  131. package/lib/domain/attachments.d.ts +16 -0
  132. package/lib/domain/attachments.js +126 -0
  133. package/lib/domain/attribution.d.ts +4 -0
  134. package/lib/domain/attribution.js +11 -0
  135. package/lib/domain/composer-palette.d.ts +15 -0
  136. package/lib/domain/composer-palette.js +131 -0
  137. package/lib/domain/index.d.ts +12 -0
  138. package/lib/domain/index.js +12 -0
  139. package/lib/domain/mention.d.ts +17 -0
  140. package/lib/domain/mention.js +95 -0
  141. package/lib/domain/message-blocks.d.ts +29 -0
  142. package/lib/domain/message-blocks.js +63 -0
  143. package/lib/domain/message-menu.d.ts +4 -0
  144. package/lib/domain/message-menu.js +46 -0
  145. package/lib/domain/queue.d.ts +23 -0
  146. package/lib/domain/queue.js +52 -0
  147. package/lib/domain/reducer.d.ts +137 -0
  148. package/lib/domain/reducer.js +442 -0
  149. package/lib/domain/selection-actions.d.ts +13 -0
  150. package/lib/domain/selection-actions.js +56 -0
  151. package/lib/domain/selection-gates.d.ts +6 -0
  152. package/lib/domain/selection-gates.js +36 -0
  153. package/lib/domain/slash-commands.d.ts +16 -0
  154. package/lib/domain/slash-commands.js +142 -0
  155. package/lib/hooks/index.d.ts +8 -0
  156. package/lib/hooks/index.js +8 -0
  157. package/lib/hooks/useComposerPalette.d.ts +35 -0
  158. package/lib/hooks/useComposerPalette.js +120 -0
  159. package/lib/hooks/useComposerSubmit.d.ts +31 -0
  160. package/lib/hooks/useComposerSubmit.js +135 -0
  161. package/lib/hooks/useInputHistory.d.ts +14 -0
  162. package/lib/hooks/useInputHistory.js +52 -0
  163. package/lib/hooks/usePendingAsk.d.ts +11 -0
  164. package/lib/hooks/usePendingAsk.js +33 -0
  165. package/lib/hooks/usePendingPermissions.d.ts +10 -0
  166. package/lib/hooks/usePendingPermissions.js +26 -0
  167. package/lib/hooks/useQueuedPrompts.d.ts +12 -0
  168. package/lib/hooks/useQueuedPrompts.js +91 -0
  169. package/lib/hooks/useSelectionPop.d.ts +13 -0
  170. package/lib/hooks/useSelectionPop.js +130 -0
  171. package/lib/hooks/useStreamBridge.d.ts +1 -0
  172. package/lib/hooks/useStreamBridge.js +23 -0
  173. package/lib/index.d.ts +10 -0
  174. package/lib/index.js +10 -0
  175. package/lib/primitives/Badge.d.ts +7 -0
  176. package/lib/primitives/Badge.js +13 -0
  177. package/lib/primitives/Button.d.ts +9 -0
  178. package/lib/primitives/Button.js +21 -0
  179. package/lib/primitives/Collapsible.d.ts +12 -0
  180. package/lib/primitives/Collapsible.js +14 -0
  181. package/lib/primitives/Dialog.d.ts +10 -0
  182. package/lib/primitives/Dialog.js +19 -0
  183. package/lib/primitives/DropdownMenu.d.ts +28 -0
  184. package/lib/primitives/DropdownMenu.js +37 -0
  185. package/lib/primitives/Empty.d.ts +8 -0
  186. package/lib/primitives/Empty.js +6 -0
  187. package/lib/primitives/Input.d.ts +3 -0
  188. package/lib/primitives/Input.js +6 -0
  189. package/lib/primitives/Kbd.d.ts +2 -0
  190. package/lib/primitives/Kbd.js +5 -0
  191. package/lib/primitives/Popover.d.ts +15 -0
  192. package/lib/primitives/Popover.js +32 -0
  193. package/lib/primitives/ScrollArea.d.ts +5 -0
  194. package/lib/primitives/ScrollArea.js +5 -0
  195. package/lib/primitives/Separator.d.ts +5 -0
  196. package/lib/primitives/Separator.js +5 -0
  197. package/lib/primitives/Spinner.d.ts +5 -0
  198. package/lib/primitives/Spinner.js +5 -0
  199. package/lib/primitives/Textarea.d.ts +3 -0
  200. package/lib/primitives/Textarea.js +6 -0
  201. package/lib/primitives/Tooltip.d.ts +8 -0
  202. package/lib/primitives/Tooltip.js +7 -0
  203. package/lib/primitives/index.d.ts +14 -0
  204. package/lib/primitives/index.js +14 -0
  205. package/lib/reactbits/Motion.d.ts +24 -0
  206. package/lib/reactbits/Motion.js +52 -0
  207. package/lib/reactbits/index.d.ts +1 -0
  208. package/lib/reactbits/index.js +1 -0
  209. package/lib/stores/chat-store.d.ts +33 -0
  210. package/lib/stores/chat-store.js +93 -0
  211. package/lib/stores/display-store.d.ts +15 -0
  212. package/lib/stores/display-store.js +116 -0
  213. package/lib/stores/index.d.ts +3 -0
  214. package/lib/stores/index.js +3 -0
  215. package/lib/stores/theme-store.d.ts +8 -0
  216. package/lib/stores/theme-store.js +29 -0
  217. package/lib/tokens/index.d.ts +21 -0
  218. package/lib/tokens/index.js +66 -0
  219. package/lib/types/agent.d.ts +62 -0
  220. package/lib/types/agent.js +1 -0
  221. package/lib/types/callbacks.d.ts +55 -0
  222. package/lib/types/callbacks.js +1 -0
  223. package/lib/types/composer.d.ts +116 -0
  224. package/lib/types/composer.js +1 -0
  225. package/lib/types/conversation.d.ts +54 -0
  226. package/lib/types/conversation.js +1 -0
  227. package/lib/types/display.d.ts +25 -0
  228. package/lib/types/display.js +40 -0
  229. package/lib/types/index.d.ts +8 -0
  230. package/lib/types/index.js +8 -0
  231. package/lib/types/message.d.ts +100 -0
  232. package/lib/types/message.js +1 -0
  233. package/lib/types/selection.d.ts +60 -0
  234. package/lib/types/selection.js +1 -0
  235. package/lib/types/theme.d.ts +100 -0
  236. package/lib/types/theme.js +1 -0
  237. package/lib/utils/clipboard.d.ts +4 -0
  238. package/lib/utils/clipboard.js +41 -0
  239. package/lib/utils/cn.d.ts +2 -0
  240. package/lib/utils/cn.js +14 -0
  241. package/lib/utils/index.d.ts +3 -0
  242. package/lib/utils/index.js +3 -0
  243. package/lib/utils/storage.d.ts +11 -0
  244. package/lib/utils/storage.js +48 -0
  245. package/package.json +93 -4
  246. package/src/styles/blocks.css +2439 -0
  247. package/src/styles/index.css +3 -0
  248. package/src/tokens/theme.css +464 -0
@@ -0,0 +1,91 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { decideSubmit, enqueuePrompt, flushEdge, movePrompt, prependPrompt, removePrompt, nextQueuedId, MAX_QUEUED_PROMPTS, QUEUED_FULL_NOTICE, } from "../domain/queue";
3
+ import { LS_KEYS, loadJSON, saveJSON } from "../utils/storage";
4
+ export { decideSubmit, flushEdge, enqueuePrompt, removePrompt, movePrompt, takeHeadPrompt, prependPrompt, nextQueuedId, MAX_QUEUED_PROMPTS, QUEUED_FULL_NOTICE, } from "../domain/queue";
5
+ export function useQueuedPrompts(params) {
6
+ const { conversationId, status, onSend, onStop, onNotice } = params;
7
+ const [parked, setParked] = useState(() => loadJSON(LS_KEYS.queuedPrompts, {}));
8
+ const edgeRef = useRef({ conversationId, status });
9
+ const liveRef = useRef(onSend);
10
+ liveRef.current = onSend;
11
+ const items = parked[conversationId] ?? [];
12
+ const itemsRef = useRef(items);
13
+ itemsRef.current = items;
14
+ const commit = useCallback((cid, next) => {
15
+ setParked((prev) => {
16
+ const table = { ...prev };
17
+ if (next.length === 0)
18
+ delete table[cid];
19
+ else
20
+ table[cid] = next;
21
+ saveJSON(LS_KEYS.queuedPrompts, table);
22
+ return table;
23
+ });
24
+ }, []);
25
+ useEffect(() => {
26
+ const prev = edgeRef.current;
27
+ edgeRef.current = { conversationId, status };
28
+ const pending = itemsRef.current;
29
+ const { rest, taken } = flushEdge(prev, { conversationId, status }, pending);
30
+ if (taken) {
31
+ commit(conversationId, rest);
32
+ liveRef.current(taken.text, taken.attachments);
33
+ }
34
+ }, [conversationId, status, commit]);
35
+ const enqueue = useCallback((text, attachments) => {
36
+ const current = itemsRef.current;
37
+ const decision = decideSubmit(status === "streaming", current.length >= MAX_QUEUED_PROMPTS);
38
+ if (decision === "reject-full") {
39
+ onNotice?.(QUEUED_FULL_NOTICE);
40
+ return "rejected-full";
41
+ }
42
+ if (decision === "send") {
43
+ liveRef.current(text, attachments);
44
+ return "not-streaming";
45
+ }
46
+ const next = enqueuePrompt(current, {
47
+ id: nextQueuedId(),
48
+ text,
49
+ attachments,
50
+ });
51
+ if (!next) {
52
+ onNotice?.(QUEUED_FULL_NOTICE);
53
+ return "rejected-full";
54
+ }
55
+ commit(conversationId, next);
56
+ return "queued";
57
+ }, [status, conversationId, commit, onNotice]);
58
+ return useMemo(() => ({
59
+ items,
60
+ full: items.length >= MAX_QUEUED_PROMPTS,
61
+ enqueue,
62
+ remove: (id) => commit(conversationId, removePrompt(itemsRef.current, id)),
63
+ moveUp: (id) => commit(conversationId, movePrompt(itemsRef.current, id, -1)),
64
+ moveDown: (id) => commit(conversationId, movePrompt(itemsRef.current, id, 1)),
65
+ edit: (id) => {
66
+ const item = itemsRef.current.find((p) => p.id === id);
67
+ if (!item)
68
+ return null;
69
+ commit(conversationId, removePrompt(itemsRef.current, id));
70
+ return item.text;
71
+ },
72
+ sendNow: (id) => {
73
+ const current = itemsRef.current;
74
+ const index = current.findIndex((p) => p.id === id);
75
+ if (index < 0)
76
+ return;
77
+ const item = current[index];
78
+ if (status !== "streaming") {
79
+ commit(conversationId, removePrompt(current, id));
80
+ liveRef.current(item.text, item.attachments);
81
+ return;
82
+ }
83
+ if (!onStop) {
84
+ onNotice?.("未接线停止能力,无法插队发送");
85
+ return;
86
+ }
87
+ commit(conversationId, prependPrompt(removePrompt(current, id), item));
88
+ onStop();
89
+ },
90
+ }), [items, enqueue, conversationId, commit, status, onStop, onNotice]);
91
+ }
@@ -0,0 +1,13 @@
1
+ import type { SelectionTarget } from "../types/callbacks";
2
+ import type { PopPosition, PopSize, RectLike } from "../types/selection";
3
+ import { normalizeSelectedText, MAX_SELECTION_LENGTH, MIN_SELECTION_LENGTH } from "../domain/selection-actions";
4
+ export interface SelectionPopState extends PopPosition {
5
+ target: SelectionTarget;
6
+ bubble: HTMLElement;
7
+ }
8
+ export declare function selectionPopPosition(rect: RectLike, viewport: PopSize, size: PopSize): PopPosition | null;
9
+ export declare function useSelectionPop(): {
10
+ pop: SelectionPopState | null;
11
+ dismiss: () => void;
12
+ };
13
+ export { normalizeSelectedText, MIN_SELECTION_LENGTH, MAX_SELECTION_LENGTH };
@@ -0,0 +1,130 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { normalizeSelectedText, resolveSelection, MAX_SELECTION_LENGTH, MIN_SELECTION_LENGTH, } from "../domain/selection-actions";
3
+ const BLOCKED_SELECTOR = 'input, textarea, select, [contenteditable="true"], [contenteditable=""], [contenteditable="plaintext-only"], [data-ctx-native], [data-cmp="ai/code-copy"]';
4
+ const POP_SELECTOR = '[data-cmp="ac/selection-actions"]';
5
+ const SETTLE_MS = 10;
6
+ function popSizeFor(actionCount) {
7
+ return { width: actionCount * 74 + 10, height: 32 };
8
+ }
9
+ export function selectionPopPosition(rect, viewport, size) {
10
+ const EDGE = 4;
11
+ const GAP = 8;
12
+ if (rect.width <= 0 || rect.height <= 0)
13
+ return null;
14
+ if (rect.bottom < 0 || rect.top > viewport.height)
15
+ return null;
16
+ const left = Math.max(EDGE, Math.min(rect.left + rect.width / 2 - size.width / 2, viewport.width - size.width - EDGE));
17
+ let top = rect.top - size.height - GAP;
18
+ if (top < EDGE)
19
+ top = rect.bottom + GAP;
20
+ top = Math.max(EDGE, Math.min(top, viewport.height - size.height - EDGE));
21
+ return { left, top };
22
+ }
23
+ export function useSelectionPop() {
24
+ const [pop, setPop] = useState(null);
25
+ const rangeRef = useRef(null);
26
+ const dismiss = useCallback(() => {
27
+ rangeRef.current = null;
28
+ setPop(null);
29
+ }, []);
30
+ const follow = useCallback(() => {
31
+ const range = rangeRef.current;
32
+ if (!range)
33
+ return;
34
+ const rect = range.getBoundingClientRect();
35
+ const size = popSizeFor(3);
36
+ const pos = selectionPopPosition(rect, {
37
+ width: window.innerWidth,
38
+ height: window.innerHeight,
39
+ }, size);
40
+ if (!pos) {
41
+ dismiss();
42
+ return;
43
+ }
44
+ setPop((prev) => prev ? { ...prev, left: pos.left, top: pos.top } : prev);
45
+ }, [dismiss]);
46
+ useEffect(() => {
47
+ let timer;
48
+ const onMouseUp = (e) => {
49
+ if (e.button !== 0)
50
+ return;
51
+ const target = e.target;
52
+ if (target?.closest(POP_SELECTOR))
53
+ return;
54
+ window.clearTimeout(timer);
55
+ timer = window.setTimeout(() => {
56
+ const sel = window.getSelection();
57
+ if (!sel || sel.isCollapsed || sel.rangeCount === 0) {
58
+ dismiss();
59
+ return;
60
+ }
61
+ const range = sel.getRangeAt(0);
62
+ const raw = sel.toString();
63
+ const anchor = (range.startContainer instanceof HTMLElement
64
+ ? range.startContainer
65
+ : (range.startContainer.parentElement ?? null));
66
+ const blocked = Boolean(anchor?.closest(BLOCKED_SELECTOR));
67
+ const bubble = anchor?.closest('[data-cmp="ac/bubble"]') ?? null;
68
+ const target2 = resolveSelection({
69
+ raw,
70
+ blocked,
71
+ bubbleRole: bubble?.dataset.msgRole ?? null,
72
+ agentId: bubble?.dataset.agent ?? null,
73
+ });
74
+ if (!target2 || !bubble) {
75
+ dismiss();
76
+ return;
77
+ }
78
+ const rect = range.getBoundingClientRect();
79
+ const size = popSizeFor(3);
80
+ const pos = selectionPopPosition(rect, {
81
+ width: window.innerWidth,
82
+ height: window.innerHeight,
83
+ }, size);
84
+ if (!pos) {
85
+ dismiss();
86
+ return;
87
+ }
88
+ rangeRef.current = range;
89
+ setPop({ ...pos, target: target2, bubble });
90
+ }, SETTLE_MS);
91
+ };
92
+ const onSelectionChange = () => {
93
+ const sel = window.getSelection();
94
+ if (!sel || sel.isCollapsed) {
95
+ dismiss();
96
+ return;
97
+ }
98
+ follow();
99
+ };
100
+ const onMouseDown = (e) => {
101
+ const target = e.target;
102
+ if (target?.closest(POP_SELECTOR))
103
+ return;
104
+ dismiss();
105
+ };
106
+ const onKeyDown = (e) => {
107
+ if (e.key === "Escape")
108
+ dismiss();
109
+ };
110
+ window.addEventListener("mouseup", onMouseUp);
111
+ document.addEventListener("selectionchange", onSelectionChange);
112
+ document.addEventListener("mousedown", onMouseDown, true);
113
+ window.addEventListener("keydown", onKeyDown);
114
+ window.addEventListener("blur", dismiss);
115
+ document.addEventListener("scroll", follow, true);
116
+ window.addEventListener("resize", follow);
117
+ return () => {
118
+ window.clearTimeout(timer);
119
+ window.removeEventListener("mouseup", onMouseUp);
120
+ document.removeEventListener("selectionchange", onSelectionChange);
121
+ document.removeEventListener("mousedown", onMouseDown, true);
122
+ window.removeEventListener("keydown", onKeyDown);
123
+ window.removeEventListener("blur", dismiss);
124
+ document.removeEventListener("scroll", follow, true);
125
+ window.removeEventListener("resize", follow);
126
+ };
127
+ }, [dismiss, follow]);
128
+ return { pop, dismiss };
129
+ }
130
+ export { normalizeSelectedText, MIN_SELECTION_LENGTH, MAX_SELECTION_LENGTH };
@@ -0,0 +1 @@
1
+ export declare function useStreamBridge(enabled?: boolean): void;
@@ -0,0 +1,23 @@
1
+ import { useEffect } from "react";
2
+ import { getStreamBridge } from "../adapters/stream-bridge";
3
+ import { useChatStore } from "../stores/chat-store";
4
+ export function useStreamBridge(enabled = true) {
5
+ useEffect(() => {
6
+ if (!enabled)
7
+ return;
8
+ const bridge = getStreamBridge();
9
+ if (!bridge)
10
+ return;
11
+ const store = useChatStore.getState();
12
+ return bridge.subscribe({
13
+ onDelta: store.streamDelta,
14
+ onReasoning: store.streamReasoning,
15
+ onToolCall: store.streamToolCall,
16
+ onToolResult: store.streamToolResult,
17
+ onContextUsage: store.streamContextUsage,
18
+ onMemory: store.streamMemory,
19
+ onCompaction: store.streamCompaction,
20
+ onEnd: store.streamEnd,
21
+ });
22
+ }, [enabled]);
23
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from "./types";
2
+ export { tokens, gaps, control, type as typeScale, radius, z, applyTheme, readTheme, designTokens, } from "./tokens";
3
+ export { cn, writeClipboard, readClipboard, loadJSON, saveJSON, LS_KEYS, } from "./utils";
4
+ export * from "./primitives";
5
+ export * from "./blocks";
6
+ export * from "./hooks";
7
+ export * from "./stores";
8
+ export * from "./domain";
9
+ export * from "./adapters";
10
+ export * from "./reactbits";
package/lib/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export * from "./types";
2
+ export { tokens, gaps, control, type as typeScale, radius, z, applyTheme, readTheme, designTokens, } from "./tokens";
3
+ export { cn, writeClipboard, readClipboard, loadJSON, saveJSON, LS_KEYS, } from "./utils";
4
+ export * from "./primitives";
5
+ export * from "./blocks";
6
+ export * from "./hooks";
7
+ export * from "./stores";
8
+ export * from "./domain";
9
+ export * from "./adapters";
10
+ export * from "./reactbits";
@@ -0,0 +1,7 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ export type BadgeVariant = "default" | "secondary" | "outline" | "success" | "warning" | "destructive";
3
+ export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
4
+ variant?: BadgeVariant;
5
+ children?: ReactNode;
6
+ }
7
+ export declare function Badge({ variant, className, children, ...props }: BadgeProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../utils/cn";
3
+ const VARIANTS = {
4
+ default: "bg-primary/15 text-primary border border-transparent",
5
+ secondary: "bg-secondary text-secondary-foreground border border-transparent",
6
+ outline: "border border-border text-muted-foreground",
7
+ success: "bg-[var(--accent-success)]/15 text-[var(--accent-success)] border border-transparent",
8
+ warning: "bg-[var(--accent-warning)]/15 text-[var(--accent-warning)] border border-transparent",
9
+ destructive: "bg-[var(--accent-danger)]/15 text-[var(--accent-danger)] border border-transparent",
10
+ };
11
+ export function Badge({ variant = "default", className, children, ...props }) {
12
+ return (_jsx("span", { className: cn("inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-micro font-medium leading-none", VARIANTS[variant], className), ...props, children: children }));
13
+ }
@@ -0,0 +1,9 @@
1
+ import { type ButtonHTMLAttributes, type ReactNode } from "react";
2
+ export type ButtonVariant = "default" | "secondary" | "outline" | "ghost" | "destructive";
3
+ export type ButtonSize = "lg" | "md" | "sm" | "xs" | "icon" | "icon-sm";
4
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
5
+ variant?: ButtonVariant;
6
+ size?: ButtonSize;
7
+ children?: ReactNode;
8
+ }
9
+ export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../utils/cn";
4
+ const VARIANTS = {
5
+ default: "bg-primary text-primary-foreground hover:bg-primary/90 border border-transparent",
6
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 border border-transparent",
7
+ outline: "border border-border bg-transparent hover:bg-secondary/60 text-foreground",
8
+ ghost: "bg-transparent hover:bg-secondary/60 text-foreground",
9
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 border border-transparent",
10
+ };
11
+ const SIZES = {
12
+ lg: "h-[var(--control-lg)] px-4 text-body",
13
+ md: "h-[var(--control-md)] px-3 text-body",
14
+ sm: "h-[var(--control-sm)] px-2.5 text-meta",
15
+ xs: "h-[var(--control-xs)] px-2 text-micro",
16
+ icon: "h-[var(--control-md)] w-[var(--control-md)] p-0",
17
+ "icon-sm": "h-[var(--control-xs)] w-[var(--control-xs)] p-0",
18
+ };
19
+ export const Button = forwardRef(function Button({ variant = "default", size = "md", className, children, ...props }, ref) {
20
+ return (_jsx("button", { ref: ref, type: "button", className: cn("press inline-flex shrink-0 items-center justify-center gap-1.5 rounded-[var(--radius-md)] font-medium transition-colors", "focus-visible:outline-2 focus-visible:outline-[var(--accent-primary-border)]", "disabled:pointer-events-none disabled:opacity-50", VARIANTS[variant], SIZES[size], className), ...props, children: children }));
21
+ });
@@ -0,0 +1,12 @@
1
+ import { type ReactNode } from "react";
2
+ export interface CollapsibleProps {
3
+ open?: boolean;
4
+ defaultOpen?: boolean;
5
+ onOpenChange?: (open: boolean) => void;
6
+ trigger: (state: {
7
+ open: boolean;
8
+ }) => ReactNode;
9
+ children: ReactNode;
10
+ className?: string;
11
+ }
12
+ export declare function Collapsible({ open: controlled, defaultOpen, onOpenChange, trigger, children, className, }: CollapsibleProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { cn } from "../utils/cn";
4
+ export function Collapsible({ open: controlled, defaultOpen = false, onOpenChange, trigger, children, className, }) {
5
+ const [uncontrolled, setUncontrolled] = useState(defaultOpen);
6
+ const open = controlled ?? uncontrolled;
7
+ const toggle = () => {
8
+ const next = !open;
9
+ if (controlled === undefined)
10
+ setUncontrolled(next);
11
+ onOpenChange?.(next);
12
+ };
13
+ return (_jsxs("div", { "data-state": open ? "open" : "closed", className: cn("w-full", className), children: [_jsx("button", { type: "button", "aria-expanded": open, onClick: toggle, className: "w-full text-left", children: trigger({ open }) }), open ? children : null] }));
14
+ }
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ export interface DialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ title?: ReactNode;
6
+ description?: ReactNode;
7
+ children?: ReactNode;
8
+ className?: string;
9
+ }
10
+ export declare function Dialog({ open, onOpenChange, title, description, children, className, }: DialogProps): import("react").ReactPortal | null;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { cn } from "../utils/cn";
5
+ export function Dialog({ open, onOpenChange, title, description, children, className, }) {
6
+ useEffect(() => {
7
+ if (!open)
8
+ return;
9
+ const onKey = (e) => {
10
+ if (e.key === "Escape")
11
+ onOpenChange(false);
12
+ };
13
+ document.addEventListener("keydown", onKey);
14
+ return () => document.removeEventListener("keydown", onKey);
15
+ }, [open, onOpenChange]);
16
+ if (!open || typeof document === "undefined")
17
+ return null;
18
+ return createPortal(_jsxs("div", { className: "fixed inset-0 z-modal flex items-center justify-center", children: [_jsx("div", { className: "absolute inset-0 bg-black/50", onClick: () => onOpenChange(false), "aria-hidden": "true" }), _jsxs("div", { role: "dialog", "aria-modal": "true", "data-slot": "dialog-content", className: cn("relative z-modal max-h-[85vh] w-full max-w-lg overflow-auto rounded-[var(--radius-lg)] border border-border p-4 shadow-lg", className), children: [title != null ? (_jsx("h2", { className: "text-heading font-semibold text-foreground", children: title })) : null, description != null ? (_jsx("p", { className: "mt-1 text-meta text-muted-foreground", children: description })) : null, children] })] }), document.body);
19
+ }
@@ -0,0 +1,28 @@
1
+ import { type ReactNode } from "react";
2
+ export interface DropdownMenuProps {
3
+ trigger: (state: {
4
+ open: boolean;
5
+ }) => ReactNode;
6
+ children: (state: {
7
+ close: () => void;
8
+ }) => ReactNode;
9
+ align?: "start" | "end";
10
+ side?: "top" | "bottom";
11
+ className?: string;
12
+ contentClassName?: string;
13
+ }
14
+ export declare function DropdownMenu({ trigger, children, align, side, className, contentClassName, }: DropdownMenuProps): import("react").JSX.Element;
15
+ export interface DropdownMenuItemProps {
16
+ onSelect?: () => void;
17
+ disabled?: boolean;
18
+ selected?: boolean;
19
+ danger?: boolean;
20
+ children: ReactNode;
21
+ className?: string;
22
+ }
23
+ export declare function DropdownMenuItem({ onSelect, disabled, selected, danger, children, className, }: DropdownMenuItemProps): import("react").JSX.Element;
24
+ export declare function DropdownMenuLabel({ children, className, }: {
25
+ children: ReactNode;
26
+ className?: string;
27
+ }): import("react").JSX.Element;
28
+ export declare function DropdownMenuSeparator(): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState, } from "react";
3
+ import { cn } from "../utils/cn";
4
+ export function DropdownMenu({ trigger, children, align = "start", side = "bottom", className, contentClassName, }) {
5
+ const [open, setOpen] = useState(false);
6
+ const rootRef = useRef(null);
7
+ useEffect(() => {
8
+ if (!open)
9
+ return;
10
+ const onDown = (e) => {
11
+ if (!rootRef.current?.contains(e.target))
12
+ setOpen(false);
13
+ };
14
+ const onKey = (e) => {
15
+ if (e.key === "Escape")
16
+ setOpen(false);
17
+ };
18
+ document.addEventListener("mousedown", onDown);
19
+ document.addEventListener("keydown", onKey);
20
+ return () => {
21
+ document.removeEventListener("mousedown", onDown);
22
+ document.removeEventListener("keydown", onKey);
23
+ };
24
+ }, [open]);
25
+ return (_jsxs("div", { ref: rootRef, className: cn("relative inline-flex", className), children: [_jsx("button", { type: "button", "aria-expanded": open, "aria-haspopup": "menu", onClick: () => setOpen((v) => !v), className: "inline-flex", children: trigger({ open }) }), open ? (_jsx("div", { role: "menu", className: cn("absolute z-tooltip min-w-48 rounded-[var(--radius-md)] border border-border bg-popover p-1 shadow-md", side === "top" ? "bottom-full mb-1" : "top-full mt-1", align === "end" ? "right-0" : "left-0", contentClassName), children: children({ close: () => setOpen(false) }) })) : null] }));
26
+ }
27
+ export function DropdownMenuItem({ onSelect, disabled, selected, danger, children, className, }) {
28
+ return (_jsx("button", { type: "button", role: "menuitem", disabled: disabled, "data-state": selected ? "selected" : "idle", onClick: onSelect, className: cn("flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1.5 text-left text-body", selected
29
+ ? "bg-secondary text-foreground"
30
+ : "text-foreground hover:bg-secondary/60", danger && "text-[var(--accent-danger)]", disabled && "pointer-events-none opacity-50", className), children: children }));
31
+ }
32
+ export function DropdownMenuLabel({ children, className, }) {
33
+ return (_jsx("div", { className: `px-2 py-1 text-micro font-medium uppercase tracking-wide text-muted-foreground${className ? ` ${className}` : ""}`, children: children }));
34
+ }
35
+ export function DropdownMenuSeparator() {
36
+ return _jsx("div", { role: "separator", className: "my-1 h-px bg-border" });
37
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ export interface EmptyProps {
3
+ title: string;
4
+ hint?: string;
5
+ icon?: ReactNode;
6
+ className?: string;
7
+ }
8
+ export declare function Empty({ title, hint, icon, className }: EmptyProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MessageSquare } from "lucide-react";
3
+ import { cn } from "../utils/cn";
4
+ export function Empty({ title, hint, icon, className }) {
5
+ return (_jsxs("div", { "data-state": "empty", className: cn("relative flex flex-1 flex-col items-center justify-center gap-3 overflow-hidden py-16 text-center", className), children: [_jsx("span", { "aria-hidden": "true", className: "grid size-10 place-items-center rounded-[var(--radius-md)] border border-[var(--border-700)] bg-[var(--surface-900)] text-muted-foreground", children: icon ?? _jsx(MessageSquare, { size: 20 }) }), _jsx("p", { className: "text-sm font-medium text-foreground", children: title }), hint ? (_jsx("p", { className: "w-72 max-w-full px-4 text-xs text-muted-foreground", children: hint })) : null] }));
6
+ }
@@ -0,0 +1,3 @@
1
+ import { type InputHTMLAttributes } from "react";
2
+ export type InputProps = InputHTMLAttributes<HTMLInputElement>;
3
+ export declare const Input: import("react").ForwardRefExoticComponent<InputProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../utils/cn";
4
+ export const Input = forwardRef(function Input({ className, ...props }, ref) {
5
+ return (_jsx("input", { ref: ref, className: cn("h-[var(--control-md)] w-full rounded-[var(--radius-md)] border border-border bg-transparent px-2.5 text-body text-foreground", "placeholder:text-muted-foreground", "focus-visible:outline-2 focus-visible:outline-[var(--accent-primary-border)]", "disabled:cursor-not-allowed disabled:opacity-50", className), ...props }));
6
+ });
@@ -0,0 +1,2 @@
1
+ import type { HTMLAttributes } from "react";
2
+ export declare function Kbd({ className, ...props }: HTMLAttributes<HTMLElement>): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../utils/cn";
3
+ export function Kbd({ className, ...props }) {
4
+ return (_jsx("kbd", { className: cn("inline-flex h-[var(--control-xs)] min-w-[var(--control-xs)] items-center justify-center rounded border border-border bg-secondary px-1 font-mono text-micro text-muted-foreground", className), ...props }));
5
+ }
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from "react";
2
+ export interface PopoverProps {
3
+ trigger: (state: {
4
+ open: boolean;
5
+ }) => ReactNode;
6
+ children: ReactNode;
7
+ side?: "top" | "bottom";
8
+ align?: "start" | "end";
9
+ sideOffset?: number;
10
+ className?: string;
11
+ contentClassName?: string;
12
+ open?: boolean;
13
+ onOpenChange?: (open: boolean) => void;
14
+ }
15
+ export declare function Popover({ trigger, children, side, align, sideOffset, className, contentClassName, open: openProp, onOpenChange, }: PopoverProps): import("react").JSX.Element;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState, } from "react";
3
+ import { cn } from "../utils/cn";
4
+ export function Popover({ trigger, children, side = "bottom", align = "end", sideOffset = 8, className, contentClassName, open: openProp, onOpenChange, }) {
5
+ const [uncontrolled, setUncontrolled] = useState(false);
6
+ const open = openProp ?? uncontrolled;
7
+ const setOpen = (next) => {
8
+ if (openProp === undefined)
9
+ setUncontrolled(next);
10
+ onOpenChange?.(next);
11
+ };
12
+ const rootRef = useRef(null);
13
+ useEffect(() => {
14
+ if (!open)
15
+ return;
16
+ const onDown = (e) => {
17
+ if (!rootRef.current?.contains(e.target))
18
+ setOpen(false);
19
+ };
20
+ const onKey = (e) => {
21
+ if (e.key === "Escape")
22
+ setOpen(false);
23
+ };
24
+ document.addEventListener("mousedown", onDown);
25
+ document.addEventListener("keydown", onKey);
26
+ return () => {
27
+ document.removeEventListener("mousedown", onDown);
28
+ document.removeEventListener("keydown", onKey);
29
+ };
30
+ }, [open]);
31
+ return (_jsxs("div", { ref: rootRef, className: cn("relative inline-flex", className), children: [_jsx("button", { type: "button", "aria-expanded": open, onClick: () => setOpen(!open), className: "inline-flex", children: trigger({ open }) }), open ? (_jsx("div", { role: "dialog", className: cn("absolute z-tooltip rounded-[var(--radius-md)] border border-border bg-popover shadow-md", side === "top" ? "bottom-full" : "top-full", align === "end" ? "right-0" : "left-0", contentClassName), style: { marginBlockStart: side === "top" ? -sideOffset : sideOffset }, children: children })) : null] }));
32
+ }
@@ -0,0 +1,5 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ export interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {
3
+ children: ReactNode;
4
+ }
5
+ export declare function ScrollArea({ className, children, ...props }: ScrollAreaProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../utils/cn";
3
+ export function ScrollArea({ className, children, ...props }) {
4
+ return (_jsx("div", { className: cn("thin_scroll min-h-0 overflow-y-auto", className), ...props, children: children }));
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { HTMLAttributes } from "react";
2
+ export interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {
3
+ orientation?: "horizontal" | "vertical";
4
+ }
5
+ export declare function Separator({ orientation, className, ...props }: SeparatorProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../utils/cn";
3
+ export function Separator({ orientation = "horizontal", className, ...props }) {
4
+ return (_jsx("div", { role: "separator", "aria-orientation": orientation, className: cn("shrink-0 bg-border", orientation === "horizontal" ? "h-px w-full" : "h-full w-px", className), ...props }));
5
+ }
@@ -0,0 +1,5 @@
1
+ export interface SpinnerProps {
2
+ className?: string;
3
+ label?: string;
4
+ }
5
+ export declare function Spinner({ className, label }: SpinnerProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../utils/cn";
3
+ export function Spinner({ className, label }) {
4
+ return (_jsx("span", { role: "status", "aria-label": label ?? "加载中", className: cn("inline-block size-3.5 animate-spin rounded-full border-2 border-border border-t-[var(--accent-primary)]", className) }));
5
+ }
@@ -0,0 +1,3 @@
1
+ import { type TextareaHTMLAttributes } from "react";
2
+ export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
3
+ export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../utils/cn";
4
+ export const Textarea = forwardRef(function Textarea({ className, ...props }, ref) {
5
+ return (_jsx("textarea", { ref: ref, className: cn("w-full resize-none rounded-[var(--radius-md)] border border-border bg-transparent px-2.5 py-1.5 text-body text-foreground", "placeholder:text-muted-foreground", "focus-visible:outline-2 focus-visible:outline-[var(--accent-primary-border)]", "disabled:cursor-not-allowed disabled:opacity-50", className), ...props }));
6
+ });
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ export interface TooltipProps {
3
+ content: ReactNode;
4
+ children: ReactNode;
5
+ side?: "top" | "bottom";
6
+ className?: string;
7
+ }
8
+ export declare function Tooltip({ content, children, side, className }: TooltipProps): import("react").JSX.Element;