@particle-academy/react-fancy 5.34.0 → 5.35.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.
@@ -0,0 +1,157 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+
4
+ /**
5
+ * Which modifier key the user actually has.
6
+ *
7
+ * This exists because a keyboard hint is a CLAIM about the reader's hardware.
8
+ * `⌘ + Enter to send` on Windows or Linux names a key that is not on the
9
+ * keyboard, and a reader who cannot act on a hint learns to ignore hints — so
10
+ * the cost is not one wrong glyph, it is the credibility of every hint beside
11
+ * it. (Reported from a real Electron app running on Linux, where the glyph had
12
+ * no font behind it either and rendered as tofu.)
13
+ *
14
+ * Two deliberate choices:
15
+ *
16
+ * 1. **The platform is an ARGUMENT, never a global read.** `detectPlatform`
17
+ * takes the hints; `modifierKeyLabel` takes the verdict. Both branches are
18
+ * therefore reachable from one machine, which is the only way a test can
19
+ * cover the platform you are not sitting at. A function that reads
20
+ * `navigator` internally can only ever be tested on the developer's own OS.
21
+ * 2. **`generic` is the fallback, and that is not arbitrary.** Every Fancy
22
+ * surface that submits on a modifier accepts `metaKey || ctrlKey`, so
23
+ * `Ctrl` is a TRUE label on an Apple keyboard while `⌘` is a FALSE one
24
+ * everywhere else. The honest default is the one that is pressable on all
25
+ * of them.
26
+ */
27
+ type FancyPlatform = "apple" | "generic";
28
+ /**
29
+ * The parts of `navigator` the detection reads — nothing more, so a caller can
30
+ * hand it a literal (a test, an Electron main process forwarding `process.platform`,
31
+ * a server with a stored preference).
32
+ */
33
+ interface PlatformHints {
34
+ /** `navigator.userAgentData` — the modern, non-spoofed answer where it exists. */
35
+ userAgentData?: {
36
+ platform?: string;
37
+ } | null;
38
+ /** `navigator.platform` — deprecated, still the most reliable legacy signal. */
39
+ platform?: string | null;
40
+ /** `navigator.userAgent` — last resort. */
41
+ userAgent?: string | null;
42
+ }
43
+ /** Resolve a platform from navigator-shaped hints. Unknown or absent ⇒ `generic`. */
44
+ declare function detectPlatform(hints?: PlatformHints | null): FancyPlatform;
45
+ /**
46
+ * The modifier key to NAME for a platform.
47
+ *
48
+ * `⌘` is emitted for `apple` and nothing else. A test enumerates the platform
49
+ * values and counts that no other one can produce it, because a hint that names
50
+ * the wrong key is worse than no hint.
51
+ */
52
+ declare function modifierKeyLabel(platform: FancyPlatform): "⌘" | "Ctrl";
53
+ /** SSR-safe read of the live browser hints. `null` off a browser. */
54
+ declare function browserPlatformHints(): PlatformHints | null;
55
+
56
+ /**
57
+ * PromptInput — the chat composer every AI app rebuilds. Auto-growing
58
+ * multi-line input with:
59
+ *
60
+ * • slash-command picker (`/` triggers a filtered command palette,
61
+ * ↑/↓ navigate, Enter inserts)
62
+ * • mention picker (`@` triggers, filtered against `mentions`)
63
+ * • drop-to-attach (drag files anywhere on the surface), paste-to-attach
64
+ * (a pasted screenshot becomes an attachment) + chip bar
65
+ * • submit on ⌘/Ctrl+Enter (plain Enter inserts a newline)
66
+ * • live token-budget meter (green → amber → red as headroom drops)
67
+ *
68
+ * Wire it up:
69
+ *
70
+ * <PromptInput
71
+ * budgetTokens={32000}
72
+ * commands={[{ name: "/rewrite", hint: "rewrite the selection" }]}
73
+ * mentions={[{ id: "ada", name: "Ada", kind: "person" }]}
74
+ * onSubmit={(text, attachments) => sendToAgent(text, attachments)}
75
+ * />
76
+ */
77
+ type PromptCmd = {
78
+ name: string;
79
+ hint: string;
80
+ };
81
+ type PromptMention = {
82
+ id: string;
83
+ name: string;
84
+ kind: "agent" | "file" | "person" | string;
85
+ };
86
+ type PromptAttachment = {
87
+ id: string;
88
+ name: string;
89
+ bytes: number;
90
+ /**
91
+ * The file the user actually attached.
92
+ *
93
+ * Optional because an attachment restored from a server has no File — but for
94
+ * anything the user dropped or picked it is always present. Without it a host
95
+ * can render a chip and nothing else: no POST, no FormData, no send-to-model.
96
+ */
97
+ file?: File;
98
+ /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */
99
+ type?: string;
100
+ };
101
+ /**
102
+ * `...rest` reaches the root surface, so a consumer or a bridge can tag the
103
+ * composer with its own `data-*` handle. The component named every prop it
104
+ * used and had no rest spread, which means a `data-handle` put on it was
105
+ * DROPPED — silently, since React discards unknown props and the component
106
+ * still renders perfectly. That is issue #22 all over again, and the contract
107
+ * calls it out: "Each interactive element has a stable identity. Agents never
108
+ * guess DOM."
109
+ */
110
+ interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmit" | "className"> {
111
+ /** Extra classes on the root surface. MERGED, never replaced. */
112
+ className?: string;
113
+ /** Token budget for the meter. */
114
+ budgetTokens: number;
115
+ /** Slash-commands. Names must start with `/`. */
116
+ commands?: PromptCmd[];
117
+ /** @-mentions. */
118
+ mentions?: PromptMention[];
119
+ /** Show the keyboard hint ("Ctrl + Enter to send", "⌘ + Enter" on a Mac). */
120
+ showHint?: boolean;
121
+ /**
122
+ * Which modifier the hint and the default placeholder NAME. Omit it and the
123
+ * component reads the platform after mount, which is the right answer for an
124
+ * ordinary web app.
125
+ *
126
+ * Pass it when the host knows better than `navigator` does — an Electron
127
+ * renderer forwarding `process.platform`, a remote session where the
128
+ * keyboard is not the one running the browser, or a test that needs the
129
+ * branch it is not sitting on. The pressed keys are unaffected either way:
130
+ * submit is `⌘/Ctrl+Enter` on every platform, so this only decides which
131
+ * true key gets named.
132
+ */
133
+ platform?: FancyPlatform;
134
+ /** Called on ⌘/Ctrl+Enter or send button. */
135
+ onSubmit: (text: string, attachments: PromptAttachment[]) => void;
136
+ /**
137
+ * Placeholder text. Defaults to a hint naming the modifier THIS platform
138
+ * actually has, so leaving it unset is the better option — a hardcoded
139
+ * placeholder is the other place a `⌘` ends up in front of a Windows user.
140
+ */
141
+ placeholder?: string;
142
+ /** Rough estimator: chars-per-token. Defaults to 4. */
143
+ charsPerToken?: number;
144
+ /** Color → CSS chip mapping for mention kinds. */
145
+ mentionColor?: Record<string, string>;
146
+ /** Optional max textarea height in px. Defaults to 280. */
147
+ maxHeight?: number;
148
+ /**
149
+ * Rendered INSIDE the rounded shell, ABOVE the attachments bar and
150
+ * textarea. Use this slot for a drawer of tools/files/prompts/etc. so
151
+ * the drawer and composer share one visual panel. See {@link ChatDrawer}.
152
+ */
153
+ aboveInput?: ReactNode;
154
+ }
155
+ declare function PromptInput({ budgetTokens, commands, mentions, showHint, platform, onSubmit, placeholder, charsPerToken, mentionColor, maxHeight, aboveInput, className, ...rest }: PromptInputProps): react.JSX.Element;
156
+
157
+ export { type FancyPlatform as F, type PlatformHints as P, type PromptAttachment as a, type PromptCmd as b, PromptInput as c, type PromptInputProps as d, type PromptMention as e, browserPlatformHints as f, detectPlatform as g, modifierKeyLabel as m };
@@ -0,0 +1,157 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+
4
+ /**
5
+ * Which modifier key the user actually has.
6
+ *
7
+ * This exists because a keyboard hint is a CLAIM about the reader's hardware.
8
+ * `⌘ + Enter to send` on Windows or Linux names a key that is not on the
9
+ * keyboard, and a reader who cannot act on a hint learns to ignore hints — so
10
+ * the cost is not one wrong glyph, it is the credibility of every hint beside
11
+ * it. (Reported from a real Electron app running on Linux, where the glyph had
12
+ * no font behind it either and rendered as tofu.)
13
+ *
14
+ * Two deliberate choices:
15
+ *
16
+ * 1. **The platform is an ARGUMENT, never a global read.** `detectPlatform`
17
+ * takes the hints; `modifierKeyLabel` takes the verdict. Both branches are
18
+ * therefore reachable from one machine, which is the only way a test can
19
+ * cover the platform you are not sitting at. A function that reads
20
+ * `navigator` internally can only ever be tested on the developer's own OS.
21
+ * 2. **`generic` is the fallback, and that is not arbitrary.** Every Fancy
22
+ * surface that submits on a modifier accepts `metaKey || ctrlKey`, so
23
+ * `Ctrl` is a TRUE label on an Apple keyboard while `⌘` is a FALSE one
24
+ * everywhere else. The honest default is the one that is pressable on all
25
+ * of them.
26
+ */
27
+ type FancyPlatform = "apple" | "generic";
28
+ /**
29
+ * The parts of `navigator` the detection reads — nothing more, so a caller can
30
+ * hand it a literal (a test, an Electron main process forwarding `process.platform`,
31
+ * a server with a stored preference).
32
+ */
33
+ interface PlatformHints {
34
+ /** `navigator.userAgentData` — the modern, non-spoofed answer where it exists. */
35
+ userAgentData?: {
36
+ platform?: string;
37
+ } | null;
38
+ /** `navigator.platform` — deprecated, still the most reliable legacy signal. */
39
+ platform?: string | null;
40
+ /** `navigator.userAgent` — last resort. */
41
+ userAgent?: string | null;
42
+ }
43
+ /** Resolve a platform from navigator-shaped hints. Unknown or absent ⇒ `generic`. */
44
+ declare function detectPlatform(hints?: PlatformHints | null): FancyPlatform;
45
+ /**
46
+ * The modifier key to NAME for a platform.
47
+ *
48
+ * `⌘` is emitted for `apple` and nothing else. A test enumerates the platform
49
+ * values and counts that no other one can produce it, because a hint that names
50
+ * the wrong key is worse than no hint.
51
+ */
52
+ declare function modifierKeyLabel(platform: FancyPlatform): "⌘" | "Ctrl";
53
+ /** SSR-safe read of the live browser hints. `null` off a browser. */
54
+ declare function browserPlatformHints(): PlatformHints | null;
55
+
56
+ /**
57
+ * PromptInput — the chat composer every AI app rebuilds. Auto-growing
58
+ * multi-line input with:
59
+ *
60
+ * • slash-command picker (`/` triggers a filtered command palette,
61
+ * ↑/↓ navigate, Enter inserts)
62
+ * • mention picker (`@` triggers, filtered against `mentions`)
63
+ * • drop-to-attach (drag files anywhere on the surface), paste-to-attach
64
+ * (a pasted screenshot becomes an attachment) + chip bar
65
+ * • submit on ⌘/Ctrl+Enter (plain Enter inserts a newline)
66
+ * • live token-budget meter (green → amber → red as headroom drops)
67
+ *
68
+ * Wire it up:
69
+ *
70
+ * <PromptInput
71
+ * budgetTokens={32000}
72
+ * commands={[{ name: "/rewrite", hint: "rewrite the selection" }]}
73
+ * mentions={[{ id: "ada", name: "Ada", kind: "person" }]}
74
+ * onSubmit={(text, attachments) => sendToAgent(text, attachments)}
75
+ * />
76
+ */
77
+ type PromptCmd = {
78
+ name: string;
79
+ hint: string;
80
+ };
81
+ type PromptMention = {
82
+ id: string;
83
+ name: string;
84
+ kind: "agent" | "file" | "person" | string;
85
+ };
86
+ type PromptAttachment = {
87
+ id: string;
88
+ name: string;
89
+ bytes: number;
90
+ /**
91
+ * The file the user actually attached.
92
+ *
93
+ * Optional because an attachment restored from a server has no File — but for
94
+ * anything the user dropped or picked it is always present. Without it a host
95
+ * can render a chip and nothing else: no POST, no FormData, no send-to-model.
96
+ */
97
+ file?: File;
98
+ /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */
99
+ type?: string;
100
+ };
101
+ /**
102
+ * `...rest` reaches the root surface, so a consumer or a bridge can tag the
103
+ * composer with its own `data-*` handle. The component named every prop it
104
+ * used and had no rest spread, which means a `data-handle` put on it was
105
+ * DROPPED — silently, since React discards unknown props and the component
106
+ * still renders perfectly. That is issue #22 all over again, and the contract
107
+ * calls it out: "Each interactive element has a stable identity. Agents never
108
+ * guess DOM."
109
+ */
110
+ interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmit" | "className"> {
111
+ /** Extra classes on the root surface. MERGED, never replaced. */
112
+ className?: string;
113
+ /** Token budget for the meter. */
114
+ budgetTokens: number;
115
+ /** Slash-commands. Names must start with `/`. */
116
+ commands?: PromptCmd[];
117
+ /** @-mentions. */
118
+ mentions?: PromptMention[];
119
+ /** Show the keyboard hint ("Ctrl + Enter to send", "⌘ + Enter" on a Mac). */
120
+ showHint?: boolean;
121
+ /**
122
+ * Which modifier the hint and the default placeholder NAME. Omit it and the
123
+ * component reads the platform after mount, which is the right answer for an
124
+ * ordinary web app.
125
+ *
126
+ * Pass it when the host knows better than `navigator` does — an Electron
127
+ * renderer forwarding `process.platform`, a remote session where the
128
+ * keyboard is not the one running the browser, or a test that needs the
129
+ * branch it is not sitting on. The pressed keys are unaffected either way:
130
+ * submit is `⌘/Ctrl+Enter` on every platform, so this only decides which
131
+ * true key gets named.
132
+ */
133
+ platform?: FancyPlatform;
134
+ /** Called on ⌘/Ctrl+Enter or send button. */
135
+ onSubmit: (text: string, attachments: PromptAttachment[]) => void;
136
+ /**
137
+ * Placeholder text. Defaults to a hint naming the modifier THIS platform
138
+ * actually has, so leaving it unset is the better option — a hardcoded
139
+ * placeholder is the other place a `⌘` ends up in front of a Windows user.
140
+ */
141
+ placeholder?: string;
142
+ /** Rough estimator: chars-per-token. Defaults to 4. */
143
+ charsPerToken?: number;
144
+ /** Color → CSS chip mapping for mention kinds. */
145
+ mentionColor?: Record<string, string>;
146
+ /** Optional max textarea height in px. Defaults to 280. */
147
+ maxHeight?: number;
148
+ /**
149
+ * Rendered INSIDE the rounded shell, ABOVE the attachments bar and
150
+ * textarea. Use this slot for a drawer of tools/files/prompts/etc. so
151
+ * the drawer and composer share one visual panel. See {@link ChatDrawer}.
152
+ */
153
+ aboveInput?: ReactNode;
154
+ }
155
+ declare function PromptInput({ budgetTokens, commands, mentions, showHint, platform, onSubmit, placeholder, charsPerToken, mentionColor, maxHeight, aboveInput, className, ...rest }: PromptInputProps): react.JSX.Element;
156
+
157
+ export { type FancyPlatform as F, type PlatformHints as P, type PromptAttachment as a, type PromptCmd as b, PromptInput as c, type PromptInputProps as d, type PromptMention as e, browserPlatformHints as f, detectPlatform as g, modifierKeyLabel as m };
package/dist/index.cjs CHANGED
@@ -29,7 +29,7 @@ var chunkZ3NSPDTW_cjs = require('./chunk-Z3NSPDTW.cjs');
29
29
  var chunk3Q4NSAEA_cjs = require('./chunk-3Q4NSAEA.cjs');
30
30
  var chunkPFEFKLUV_cjs = require('./chunk-PFEFKLUV.cjs');
31
31
  var chunkRWOH2ADL_cjs = require('./chunk-RWOH2ADL.cjs');
32
- var chunkJ2KYNK63_cjs = require('./chunk-J2KYNK63.cjs');
32
+ var chunkDNV7MUEE_cjs = require('./chunk-DNV7MUEE.cjs');
33
33
  var chunkISPCXIJZ_cjs = require('./chunk-ISPCXIJZ.cjs');
34
34
  var chunkWKAEAGMT_cjs = require('./chunk-WKAEAGMT.cjs');
35
35
  var chunkMTUH2X3W_cjs = require('./chunk-MTUH2X3W.cjs');
@@ -237,7 +237,19 @@ Object.defineProperty(exports, "Progress", {
237
237
  });
238
238
  Object.defineProperty(exports, "PromptInput", {
239
239
  enumerable: true,
240
- get: function () { return chunkJ2KYNK63_cjs.PromptInput; }
240
+ get: function () { return chunkDNV7MUEE_cjs.PromptInput; }
241
+ });
242
+ Object.defineProperty(exports, "browserPlatformHints", {
243
+ enumerable: true,
244
+ get: function () { return chunkDNV7MUEE_cjs.browserPlatformHints; }
245
+ });
246
+ Object.defineProperty(exports, "detectPlatform", {
247
+ enumerable: true,
248
+ get: function () { return chunkDNV7MUEE_cjs.detectPlatform; }
249
+ });
250
+ Object.defineProperty(exports, "modifierKeyLabel", {
251
+ enumerable: true,
252
+ get: function () { return chunkDNV7MUEE_cjs.modifierKeyLabel; }
241
253
  });
242
254
  Object.defineProperty(exports, "Tooltip", {
243
255
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -79,12 +79,12 @@ export { Kanban, KanbanCardMoveHandler, KanbanCardProps, KanbanColumnHandleProps
79
79
  export { DropPosition, TreeNav, TreeNavContextValue, TreeNavProps, TreeNodeData, TreeNodeProps, useTreeNav } from './tree-nav.cjs';
80
80
  export { FileBrowser, FileBrowserContextValue, FileBrowserNewFolder, FileBrowserNodeProps, FileBrowserPathBarProps, FileBrowserProps, FileBrowserProvider, FileBrowserRow, FileBrowserToolbarProps, FileBrowserTreeProps, FileEntry, FileKind, FileLoadStatus, FileSelectMode, FileSnapshotNode, FileSort, FileSortDirection, FileSortField, useFileBrowser, validateFolderName } from './file-browser.cjs';
81
81
  export { M as MediaKind, a as MediaViewer, b as MediaViewerProps, R as ResolveMediaTypeInput, r as resolveMediaType } from './index-CgPpIw_O.cjs';
82
+ export { F as FancyPlatform, P as PlatformHints, a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention, f as browserPlatformHints, g as detectPlatform, m as modifierKeyLabel } from './index-DXUAAZzE.cjs';
82
83
  import { P as Placement } from './types-DJI492iY.cjs';
83
84
  export { A as ActionColor, B as ButtonColor, C as Color, S as Size, V as Variant } from './types-DJI492iY.cjs';
84
85
  import { RefObject } from 'react';
85
86
  export { ReasonTag, ReasonTagProps, ReasonTagSource, ReasonTagTheme } from './reason-tag.cjs';
86
87
  export { MoodMeter, MoodMeterProps } from './mood-meter.cjs';
87
- export { PromptAttachment, PromptCmd, PromptInput, PromptInputProps, PromptMention } from './prompt-input.cjs';
88
88
  export { ChatDrawer, ChatDrawerProps, ChatDrawerTab } from './chat-drawer.cjs';
89
89
  export { ControlledAdapterHandle, InputTag, InputTagAdapter, InputTagAdapterState, InputTagProps, InputTagTrigger, InputTagTriggers, contentEditableAdapter, controlledAdapter, inputAdapter, textareaAdapter } from './input-tag.cjs';
90
90
  export { MagicWand, MagicWandAction, MagicWandAppearance, MagicWandProps, MagicWandSelection } from './magic-wand.cjs';
package/dist/index.d.ts CHANGED
@@ -79,12 +79,12 @@ export { Kanban, KanbanCardMoveHandler, KanbanCardProps, KanbanColumnHandleProps
79
79
  export { DropPosition, TreeNav, TreeNavContextValue, TreeNavProps, TreeNodeData, TreeNodeProps, useTreeNav } from './tree-nav.js';
80
80
  export { FileBrowser, FileBrowserContextValue, FileBrowserNewFolder, FileBrowserNodeProps, FileBrowserPathBarProps, FileBrowserProps, FileBrowserProvider, FileBrowserRow, FileBrowserToolbarProps, FileBrowserTreeProps, FileEntry, FileKind, FileLoadStatus, FileSelectMode, FileSnapshotNode, FileSort, FileSortDirection, FileSortField, useFileBrowser, validateFolderName } from './file-browser.js';
81
81
  export { M as MediaKind, a as MediaViewer, b as MediaViewerProps, R as ResolveMediaTypeInput, r as resolveMediaType } from './index-IeivGsNg.js';
82
+ export { F as FancyPlatform, P as PlatformHints, a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention, f as browserPlatformHints, g as detectPlatform, m as modifierKeyLabel } from './index-DXUAAZzE.js';
82
83
  import { P as Placement } from './types-DJI492iY.js';
83
84
  export { A as ActionColor, B as ButtonColor, C as Color, S as Size, V as Variant } from './types-DJI492iY.js';
84
85
  import { RefObject } from 'react';
85
86
  export { ReasonTag, ReasonTagProps, ReasonTagSource, ReasonTagTheme } from './reason-tag.js';
86
87
  export { MoodMeter, MoodMeterProps } from './mood-meter.js';
87
- export { PromptAttachment, PromptCmd, PromptInput, PromptInputProps, PromptMention } from './prompt-input.js';
88
88
  export { ChatDrawer, ChatDrawerProps, ChatDrawerTab } from './chat-drawer.js';
89
89
  export { ControlledAdapterHandle, InputTag, InputTagAdapter, InputTagAdapterState, InputTagProps, InputTagTrigger, InputTagTriggers, contentEditableAdapter, controlledAdapter, inputAdapter, textareaAdapter } from './input-tag.js';
90
90
  export { MagicWand, MagicWandAction, MagicWandAppearance, MagicWandProps, MagicWandSelection } from './magic-wand.js';
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ export { Table } from './chunk-P7Q2YIIS.js';
27
27
  export { Tabs, useTabs } from './chunk-FNEPZ33A.js';
28
28
  export { Profile } from './chunk-KQQA7JBC.js';
29
29
  export { Progress } from './chunk-P7PKK5BO.js';
30
- export { PromptInput } from './chunk-KM4R4XWB.js';
30
+ export { PromptInput, browserPlatformHints, detectPlatform, modifierKeyLabel } from './chunk-O3WEV562.js';
31
31
  export { Tooltip } from './chunk-35EN5AYE.js';
32
32
  export { PullQuote } from './chunk-NDUEPDNS.js';
33
33
  export { ReasonTag } from './chunk-DCDW3LW6.js';
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkJ2KYNK63_cjs = require('./chunk-J2KYNK63.cjs');
3
+ var chunkDNV7MUEE_cjs = require('./chunk-DNV7MUEE.cjs');
4
4
  require('./chunk-ISPCXIJZ.cjs');
5
+ require('./chunk-AIA5UNQ5.cjs');
5
6
  require('./chunk-M5MUGSON.cjs');
6
7
  require('./chunk-RMJGF7MN.cjs');
7
8
  require('./chunk-BFKCVSNK.cjs');
@@ -15,7 +16,7 @@ require('./chunk-ZU5Q77OT.cjs');
15
16
 
16
17
  Object.defineProperty(exports, "PromptInput", {
17
18
  enumerable: true,
18
- get: function () { return chunkJ2KYNK63_cjs.PromptInput; }
19
+ get: function () { return chunkDNV7MUEE_cjs.PromptInput; }
19
20
  });
20
21
  //# sourceMappingURL=prompt-input.cjs.map
21
22
  //# sourceMappingURL=prompt-input.cjs.map
@@ -1,77 +1,2 @@
1
- import * as react from 'react';
2
- import { ReactNode } from 'react';
3
-
4
- /**
5
- * PromptInput — the chat composer every AI app rebuilds. Auto-growing
6
- * multi-line input with:
7
- *
8
- * • slash-command picker (`/` triggers a filtered command palette,
9
- * ↑/↓ navigate, Enter inserts)
10
- * • mention picker (`@` triggers, filtered against `mentions`)
11
- * • drop-to-attach (drag files anywhere on the surface), paste-to-attach
12
- * (a pasted screenshot becomes an attachment) + chip bar
13
- * • submit on ⌘/Ctrl+Enter (plain Enter inserts a newline)
14
- * • live token-budget meter (green → amber → red as headroom drops)
15
- *
16
- * Wire it up:
17
- *
18
- * <PromptInput
19
- * budgetTokens={32000}
20
- * commands={[{ name: "/rewrite", hint: "rewrite the selection" }]}
21
- * mentions={[{ id: "ada", name: "Ada", kind: "person" }]}
22
- * onSubmit={(text, attachments) => sendToAgent(text, attachments)}
23
- * />
24
- */
25
- type PromptCmd = {
26
- name: string;
27
- hint: string;
28
- };
29
- type PromptMention = {
30
- id: string;
31
- name: string;
32
- kind: "agent" | "file" | "person" | string;
33
- };
34
- type PromptAttachment = {
35
- id: string;
36
- name: string;
37
- bytes: number;
38
- /**
39
- * The file the user actually attached.
40
- *
41
- * Optional because an attachment restored from a server has no File — but for
42
- * anything the user dropped or picked it is always present. Without it a host
43
- * can render a chip and nothing else: no POST, no FormData, no send-to-model.
44
- */
45
- file?: File;
46
- /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */
47
- type?: string;
48
- };
49
- interface PromptInputProps {
50
- /** Token budget for the meter. */
51
- budgetTokens: number;
52
- /** Slash-commands. Names must start with `/`. */
53
- commands?: PromptCmd[];
54
- /** @-mentions. */
55
- mentions?: PromptMention[];
56
- /** Show the keyboard hint ("⌘ + Enter to send"). */
57
- showHint?: boolean;
58
- /** Called on ⌘/Ctrl+Enter or send button. */
59
- onSubmit: (text: string, attachments: PromptAttachment[]) => void;
60
- /** Placeholder text. */
61
- placeholder?: string;
62
- /** Rough estimator: chars-per-token. Defaults to 4. */
63
- charsPerToken?: number;
64
- /** Color → CSS chip mapping for mention kinds. */
65
- mentionColor?: Record<string, string>;
66
- /** Optional max textarea height in px. Defaults to 280. */
67
- maxHeight?: number;
68
- /**
69
- * Rendered INSIDE the rounded shell, ABOVE the attachments bar and
70
- * textarea. Use this slot for a drawer of tools/files/prompts/etc. so
71
- * the drawer and composer share one visual panel. See {@link ChatDrawer}.
72
- */
73
- aboveInput?: ReactNode;
74
- }
75
- declare function PromptInput({ budgetTokens, commands, mentions, showHint, onSubmit, placeholder, charsPerToken, mentionColor, maxHeight, aboveInput, }: PromptInputProps): react.JSX.Element;
76
-
77
- export { type PromptAttachment, type PromptCmd, PromptInput, type PromptInputProps, type PromptMention };
1
+ export { a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention } from './index-DXUAAZzE.cjs';
2
+ import 'react';
@@ -1,77 +1,2 @@
1
- import * as react from 'react';
2
- import { ReactNode } from 'react';
3
-
4
- /**
5
- * PromptInput — the chat composer every AI app rebuilds. Auto-growing
6
- * multi-line input with:
7
- *
8
- * • slash-command picker (`/` triggers a filtered command palette,
9
- * ↑/↓ navigate, Enter inserts)
10
- * • mention picker (`@` triggers, filtered against `mentions`)
11
- * • drop-to-attach (drag files anywhere on the surface), paste-to-attach
12
- * (a pasted screenshot becomes an attachment) + chip bar
13
- * • submit on ⌘/Ctrl+Enter (plain Enter inserts a newline)
14
- * • live token-budget meter (green → amber → red as headroom drops)
15
- *
16
- * Wire it up:
17
- *
18
- * <PromptInput
19
- * budgetTokens={32000}
20
- * commands={[{ name: "/rewrite", hint: "rewrite the selection" }]}
21
- * mentions={[{ id: "ada", name: "Ada", kind: "person" }]}
22
- * onSubmit={(text, attachments) => sendToAgent(text, attachments)}
23
- * />
24
- */
25
- type PromptCmd = {
26
- name: string;
27
- hint: string;
28
- };
29
- type PromptMention = {
30
- id: string;
31
- name: string;
32
- kind: "agent" | "file" | "person" | string;
33
- };
34
- type PromptAttachment = {
35
- id: string;
36
- name: string;
37
- bytes: number;
38
- /**
39
- * The file the user actually attached.
40
- *
41
- * Optional because an attachment restored from a server has no File — but for
42
- * anything the user dropped or picked it is always present. Without it a host
43
- * can render a chip and nothing else: no POST, no FormData, no send-to-model.
44
- */
45
- file?: File;
46
- /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */
47
- type?: string;
48
- };
49
- interface PromptInputProps {
50
- /** Token budget for the meter. */
51
- budgetTokens: number;
52
- /** Slash-commands. Names must start with `/`. */
53
- commands?: PromptCmd[];
54
- /** @-mentions. */
55
- mentions?: PromptMention[];
56
- /** Show the keyboard hint ("⌘ + Enter to send"). */
57
- showHint?: boolean;
58
- /** Called on ⌘/Ctrl+Enter or send button. */
59
- onSubmit: (text: string, attachments: PromptAttachment[]) => void;
60
- /** Placeholder text. */
61
- placeholder?: string;
62
- /** Rough estimator: chars-per-token. Defaults to 4. */
63
- charsPerToken?: number;
64
- /** Color → CSS chip mapping for mention kinds. */
65
- mentionColor?: Record<string, string>;
66
- /** Optional max textarea height in px. Defaults to 280. */
67
- maxHeight?: number;
68
- /**
69
- * Rendered INSIDE the rounded shell, ABOVE the attachments bar and
70
- * textarea. Use this slot for a drawer of tools/files/prompts/etc. so
71
- * the drawer and composer share one visual panel. See {@link ChatDrawer}.
72
- */
73
- aboveInput?: ReactNode;
74
- }
75
- declare function PromptInput({ budgetTokens, commands, mentions, showHint, onSubmit, placeholder, charsPerToken, mentionColor, maxHeight, aboveInput, }: PromptInputProps): react.JSX.Element;
76
-
77
- export { type PromptAttachment, type PromptCmd, PromptInput, type PromptInputProps, type PromptMention };
1
+ export { a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention } from './index-DXUAAZzE.js';
2
+ import 'react';
@@ -1,5 +1,6 @@
1
- export { PromptInput } from './chunk-KM4R4XWB.js';
1
+ export { PromptInput } from './chunk-O3WEV562.js';
2
2
  import './chunk-35EN5AYE.js';
3
+ import './chunk-TJ66S5K6.js';
3
4
  import './chunk-LQ4ICCN2.js';
4
5
  import './chunk-46IPQEUP.js';
5
6
  import './chunk-XJXQG34J.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/react-fancy",
3
- "version": "5.34.0",
3
+ "version": "5.35.0",
4
4
  "description": "React UI component library for Human+ UX — controlled, agent-bridgeable primitives",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/PromptInput/PromptInput.tsx"],"names":["useState","useRef","useMemo","useEffect","jsxs","jsx","Tooltip","Button"],"mappings":"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","file":"chunk-J2KYNK63.cjs","sourcesContent":["import { useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { Button } from \"../Button\";\nimport { Tooltip } from \"../Tooltip\";\n\n/**\n * PromptInput — the chat composer every AI app rebuilds. Auto-growing\n * multi-line input with:\n *\n * • slash-command picker (`/` triggers a filtered command palette,\n * ↑/↓ navigate, Enter inserts)\n * • mention picker (`@` triggers, filtered against `mentions`)\n * • drop-to-attach (drag files anywhere on the surface), paste-to-attach\n * (a pasted screenshot becomes an attachment) + chip bar\n * • submit on ⌘/Ctrl+Enter (plain Enter inserts a newline)\n * • live token-budget meter (green → amber → red as headroom drops)\n *\n * Wire it up:\n *\n * <PromptInput\n * budgetTokens={32000}\n * commands={[{ name: \"/rewrite\", hint: \"rewrite the selection\" }]}\n * mentions={[{ id: \"ada\", name: \"Ada\", kind: \"person\" }]}\n * onSubmit={(text, attachments) => sendToAgent(text, attachments)}\n * />\n */\nexport type PromptCmd = { name: string; hint: string };\nexport type PromptMention = {\n id: string;\n name: string;\n kind: \"agent\" | \"file\" | \"person\" | string;\n};\nexport type PromptAttachment = {\n id: string;\n name: string;\n bytes: number;\n /**\n * The file the user actually attached.\n *\n * Optional because an attachment restored from a server has no File — but for\n * anything the user dropped or picked it is always present. Without it a host\n * can render a chip and nothing else: no POST, no FormData, no send-to-model.\n */\n file?: File;\n /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */\n type?: string;\n};\n\nexport interface PromptInputProps {\n /** Token budget for the meter. */\n budgetTokens: number;\n /** Slash-commands. Names must start with `/`. */\n commands?: PromptCmd[];\n /** @-mentions. */\n mentions?: PromptMention[];\n /** Show the keyboard hint (\"⌘ + Enter to send\"). */\n showHint?: boolean;\n /** Called on ⌘/Ctrl+Enter or send button. */\n onSubmit: (text: string, attachments: PromptAttachment[]) => void;\n /** Placeholder text. */\n placeholder?: string;\n /** Rough estimator: chars-per-token. Defaults to 4. */\n charsPerToken?: number;\n /** Color → CSS chip mapping for mention kinds. */\n mentionColor?: Record<string, string>;\n /** Optional max textarea height in px. Defaults to 280. */\n maxHeight?: number;\n /**\n * Rendered INSIDE the rounded shell, ABOVE the attachments bar and\n * textarea. Use this slot for a drawer of tools/files/prompts/etc. so\n * the drawer and composer share one visual panel. See {@link ChatDrawer}.\n */\n aboveInput?: ReactNode;\n}\n\nconst DEFAULT_MENTION_COLOR: Record<string, string> = {\n agent: \"#a855f7\",\n file: \"#10b981\",\n person: \"#3b82f6\",\n};\n\nexport function PromptInput({\n budgetTokens,\n commands = [],\n mentions = [],\n showHint = true,\n onSubmit,\n placeholder = \"Ask anything. Type / for commands, @ for mentions. ⌘/Ctrl+Enter to send.\",\n charsPerToken = 4,\n mentionColor,\n maxHeight = 280,\n aboveInput,\n}: PromptInputProps) {\n const [text, setText] = useState(\"\");\n const [attachments, setAttachments] = useState<PromptAttachment[]>([]);\n const [picker, setPicker] = useState<null | {\n kind: \"cmd\" | \"mention\";\n start: number;\n query: string;\n cursor: number;\n }>(null);\n const taRef = useRef<HTMLTextAreaElement>(null);\n const fileRef = useRef<HTMLInputElement>(null);\n const [dragOver, setDragOver] = useState(false);\n\n const colors = mentionColor ?? DEFAULT_MENTION_COLOR;\n\n const tokens = useMemo(\n () => Math.ceil(text.length / Math.max(1, charsPerToken)),\n [text, charsPerToken],\n );\n const ratio = Math.min(1, tokens / budgetTokens);\n const meterColor = ratio < 0.6 ? \"#10b981\" : ratio < 0.85 ? \"#f59e0b\" : \"#ef4444\";\n\n const filteredCmds = useMemo(\n () =>\n picker?.kind === \"cmd\"\n ? commands.filter((c) =>\n c.name.slice(1).toLowerCase().startsWith(picker.query.toLowerCase()),\n )\n : [],\n [picker, commands],\n );\n const filteredMentions = useMemo(\n () =>\n picker?.kind === \"mention\"\n ? mentions.filter((m) =>\n m.name.toLowerCase().includes(picker.query.toLowerCase()),\n )\n : [],\n [picker, mentions],\n );\n const items: Array<PromptCmd | PromptMention> =\n picker?.kind === \"cmd\"\n ? filteredCmds\n : picker?.kind === \"mention\"\n ? filteredMentions\n : [];\n\n useEffect(() => {\n const ta = taRef.current;\n if (!ta) return;\n ta.style.height = \"auto\";\n ta.style.height = Math.min(maxHeight, ta.scrollHeight) + \"px\";\n }, [text, maxHeight]);\n\n const updateText = (next: string, caret: number) => {\n setText(next);\n let triggerIdx = -1;\n let triggerKind: \"cmd\" | \"mention\" | null = null;\n for (let i = caret - 1; i >= 0; i--) {\n const ch = next[i];\n if (ch === \"@\") {\n triggerKind = \"mention\";\n triggerIdx = i;\n break;\n }\n if (ch === \"/\" && (i === 0 || /\\s/.test(next[i - 1] ?? \"\"))) {\n triggerKind = \"cmd\";\n triggerIdx = i;\n break;\n }\n if (/\\s/.test(ch)) break;\n }\n if (triggerKind !== null && triggerIdx >= 0) {\n const query = next.slice(triggerIdx + 1, caret);\n setPicker({ kind: triggerKind, start: triggerIdx, query, cursor: 0 });\n } else {\n setPicker(null);\n }\n };\n\n const insertChoice = (i: number) => {\n if (!picker || items.length === 0) return;\n const choice = items[i] ?? items[0];\n const insert =\n picker.kind === \"cmd\"\n ? (choice as PromptCmd).name + \" \"\n : `@${(choice as PromptMention).id} `;\n const before = text.slice(0, picker.start);\n const after = text.slice(picker.start + 1 + picker.query.length);\n const next = before + insert + after;\n setText(next);\n setPicker(null);\n requestAnimationFrame(() => {\n const ta = taRef.current;\n if (!ta) return;\n ta.focus();\n const pos = before.length + insert.length;\n ta.setSelectionRange(pos, pos);\n });\n };\n\n const submit = () => {\n if (!text.trim() && attachments.length === 0) return;\n onSubmit(text, attachments);\n setText(\"\");\n setAttachments([]);\n setPicker(null);\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (picker) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setPicker((p) =>\n p ? { ...p, cursor: Math.min(items.length - 1, p.cursor + 1) } : p,\n );\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setPicker((p) => (p ? { ...p, cursor: Math.max(0, p.cursor - 1) } : p));\n return;\n }\n if (e.key === \"Enter\" && !e.metaKey && !e.ctrlKey && items.length > 0) {\n e.preventDefault();\n insertChoice(picker.cursor);\n return;\n }\n if (e.key === \"Escape\") {\n e.preventDefault();\n setPicker(null);\n return;\n }\n }\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n submit();\n }\n };\n\n /**\n * Build attachments from a FileList, KEEPING the File.\n *\n * Shared by the drop handler and the picker so the two cannot drift — the\n * picker being a second, subtly different copy of this is exactly how one of\n * them would end up dropping the File again.\n */\n const attach = (list: FileList | File[] | null) => {\n const files = Array.from(list ?? []);\n if (files.length === 0) return;\n\n setAttachments((cur) => [\n ...cur,\n ...files.map((f) => ({\n id: crypto.randomUUID(),\n name: f.name,\n bytes: f.size,\n file: f,\n type: f.type,\n })),\n ]);\n };\n\n const onDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setDragOver(false);\n attach(e.dataTransfer.files);\n };\n\n /**\n * Paste a screenshot, get an attachment.\n *\n * There was no paste handler at all, so a pasted image did nothing — no chip,\n * no error — and a host that had swapped its own paste-capable textarea for\n * this component lost the feature without noticing.\n *\n * Files attach only when the clipboard carries NO plain text. Word, Excel and\n * most editors put a picture of the selection beside the text; attaching that\n * would turn every pasted sentence into a screenshot of the sentence. A pure\n * image paste has nothing for the browser to insert, so the default is\n * prevented only then, and a text paste is never touched.\n */\n const onPaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {\n const data = e.clipboardData;\n const files = Array.from(data?.files ?? []);\n\n if (files.length === 0 || (data?.getData(\"text/plain\") ?? \"\") !== \"\") return;\n\n e.preventDefault();\n attach(files);\n };\n\n return (\n <div\n onDragOver={(e) => {\n e.preventDefault();\n setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={onDrop}\n className={`relative rounded-md border transition ${\n dragOver\n ? \"border-violet-400 bg-violet-50/50 dark:border-violet-600 dark:bg-violet-950/30\"\n : \"border-zinc-200 dark:border-zinc-800\"\n } bg-white dark:bg-zinc-900`}\n >\n {aboveInput && (\n <div className=\"border-b border-zinc-200 dark:border-zinc-800\">\n {aboveInput}\n </div>\n )}\n {attachments.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5 border-b border-zinc-200 px-3 py-2 dark:border-zinc-800\">\n {attachments.map((a) => (\n <span\n key={a.id}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-zinc-100 px-2 py-0.5 text-[11px] dark:bg-zinc-800\"\n >\n <span>📎</span>\n <span className=\"font-mono\">{a.name}</span>\n <span className=\"text-zinc-400\">{fmtSize(a.bytes)}</span>\n <button\n onClick={() =>\n setAttachments((cur) => cur.filter((x) => x.id !== a.id))\n }\n className=\"opacity-50 hover:opacity-100\"\n aria-label=\"Remove attachment\"\n >\n ×\n </button>\n </span>\n ))}\n </div>\n )}\n\n <div className=\"relative\">\n <textarea\n ref={taRef}\n value={text}\n onChange={(e) => updateText(e.target.value, e.target.selectionStart)}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n placeholder={placeholder}\n spellCheck={false}\n className=\"block w-full resize-none bg-transparent px-3 py-2.5 text-[14px] leading-relaxed outline-none placeholder:text-zinc-400\"\n rows={3}\n />\n\n {picker && items.length > 0 && (\n <div className=\"absolute bottom-full left-2 z-10 mb-1 w-72 overflow-hidden rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900\">\n <div className=\"border-b border-zinc-100 bg-zinc-50 px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950\">\n {picker.kind === \"cmd\" ? \"Commands\" : \"Mention\"} · {items.length}\n </div>\n <ul className=\"max-h-56 overflow-y-auto\">\n {items.map((item, i) => {\n const active = i === picker.cursor;\n if (picker.kind === \"cmd\") {\n const c = item as PromptCmd;\n return (\n <li\n key={c.name}\n onMouseDown={(e) => {\n e.preventDefault();\n insertChoice(i);\n }}\n onMouseEnter={() =>\n setPicker((p) => (p ? { ...p, cursor: i } : p))\n }\n className={`cursor-pointer px-2 py-1.5 text-[12px] ${\n active\n ? \"bg-violet-100 dark:bg-violet-900/30\"\n : \"hover:bg-zinc-50 dark:hover:bg-zinc-800\"\n }`}\n >\n <div className=\"font-mono font-medium text-violet-700 dark:text-violet-300\">\n {c.name}\n </div>\n <div className=\"text-[11px] text-zinc-500\">{c.hint}</div>\n </li>\n );\n }\n const m = item as PromptMention;\n return (\n <li\n key={m.id}\n onMouseDown={(e) => {\n e.preventDefault();\n insertChoice(i);\n }}\n onMouseEnter={() =>\n setPicker((p) => (p ? { ...p, cursor: i } : p))\n }\n className={`flex cursor-pointer items-center gap-2 px-2 py-1.5 text-[12px] ${\n active\n ? \"bg-violet-100 dark:bg-violet-900/30\"\n : \"hover:bg-zinc-50 dark:hover:bg-zinc-800\"\n }`}\n >\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: colors[m.kind] ?? \"#71717a\" }}\n />\n <span className=\"font-medium\">{m.name}</span>\n <span className=\"ml-auto text-[10px] uppercase tracking-wider text-zinc-400\">\n {m.kind}\n </span>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n\n <div className=\"flex items-center gap-2 border-t border-zinc-200 bg-zinc-50/60 px-3 py-2 dark:border-zinc-800 dark:bg-zinc-900/40\">\n <Tooltip content=\"Drop files here, or click\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0\"\n onClick={() => fileRef.current?.click()}\n >\n 📎 attach\n </Button>\n </Tooltip>\n {/*\n The button above shipped with a no-op onClick and a tooltip reading\n \"Drop files here, or click\" — an affordance that lied. Drop-only also\n meant keyboard and touch users could not attach at all.\n */}\n <input\n ref={fileRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={(e) => {\n attach(e.target.files);\n // Let the same file be picked twice in a row.\n e.target.value = \"\";\n }}\n />\n <div className=\"ml-2 flex min-w-0 items-center gap-1.5 overflow-hidden\">\n <div className=\"h-1.5 w-24 shrink overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-700\">\n <div\n className=\"h-full rounded-full transition-all\"\n style={{ width: `${ratio * 100}%`, backgroundColor: meterColor }}\n />\n </div>\n <span className=\"font-mono text-[11px]\" style={{ color: meterColor }}>\n {fmtTokens(tokens)} / {fmtTokens(budgetTokens)}\n </span>\n </div>\n <div className=\"ml-auto flex shrink-0 items-center gap-2\">\n {showHint && (\n <span className=\"hidden text-[10px] text-zinc-500 sm:inline\">\n <kbd className=\"rounded border border-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300\">\n ⌘\n </kbd>{\" \"}\n +{\" \"}\n <kbd className=\"rounded border border-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300\">\n Enter\n </kbd>{\" \"}\n to send\n </span>\n )}\n <Button color=\"violet\" size=\"sm\" className=\"shrink-0\" onClick={submit}>\n send →\n </Button>\n </div>\n </div>\n </div>\n );\n}\n\nfunction fmtTokens(n: number): string {\n if (n < 1000) return `${n}`;\n return `${(n / 1000).toFixed(1)}k`;\n}\n\nfunction fmtSize(b: number): string {\n if (b < 1024) return `${b} B`;\n if (b < 1024 * 1024) return `${(b / 1024).toFixed(0)} KB`;\n return `${(b / 1024 / 1024).toFixed(1)} MB`;\n}\n"]}