@admin-layout/tailwind-ui 12.2.4-alpha.62 → 12.2.4-alpha.66

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 (55) hide show
  1. package/lib/components/AIToolbar/components/completion/Files.d.ts +3 -3
  2. package/lib/components/AIToolbar/components/completion/Files.d.ts.map +1 -1
  3. package/lib/components/AIToolbar/components/completion/Files.js +66 -36
  4. package/lib/components/AIToolbar/components/completion/Files.js.map +1 -1
  5. package/lib/components/AIToolbar/components/completion/Input.js +1 -1
  6. package/lib/components/AIToolbar/components/speech/Warning.d.ts.map +1 -1
  7. package/lib/components/AIToolbar/components/speech/Warning.js +101 -29
  8. package/lib/components/AIToolbar/components/speech/Warning.js.map +1 -1
  9. package/lib/components/AIToolbar/index.d.ts +2 -1
  10. package/lib/components/AIToolbar/index.d.ts.map +1 -1
  11. package/lib/components/CharacterSlotPicker/CharacterSlotPicker.d.ts +20 -0
  12. package/lib/components/CharacterSlotPicker/CharacterSlotPicker.d.ts.map +1 -0
  13. package/lib/components/CharacterSlotPicker/CharacterSlotPicker.js +249 -0
  14. package/lib/components/CharacterSlotPicker/CharacterSlotPicker.js.map +1 -0
  15. package/lib/components/CharacterSlotPicker/index.d.ts +3 -0
  16. package/lib/components/CharacterSlotPicker/index.d.ts.map +1 -0
  17. package/lib/components/Inbox/Channels/ChannelBar.d.ts +33 -0
  18. package/lib/components/Inbox/Channels/ChannelBar.d.ts.map +1 -0
  19. package/lib/components/Inbox/Channels/ChannelBar.js +79 -0
  20. package/lib/components/Inbox/Channels/ChannelBar.js.map +1 -0
  21. package/lib/components/Inbox/Channels/ChannelConnectModal.d.ts +24 -0
  22. package/lib/components/Inbox/Channels/ChannelConnectModal.d.ts.map +1 -0
  23. package/lib/components/Inbox/Channels/ChannelConnectModal.js +177 -0
  24. package/lib/components/Inbox/Channels/ChannelConnectModal.js.map +1 -0
  25. package/lib/components/Inbox/Channels/channelsMeta.d.ts +16 -0
  26. package/lib/components/Inbox/Channels/channelsMeta.d.ts.map +1 -0
  27. package/lib/components/Inbox/Channels/channelsMeta.js +127 -0
  28. package/lib/components/Inbox/Channels/channelsMeta.js.map +1 -0
  29. package/lib/components/Inbox/Channels/index.d.ts +7 -0
  30. package/lib/components/Inbox/Channels/index.d.ts.map +1 -0
  31. package/lib/components/Inbox/index.d.ts +2 -0
  32. package/lib/components/Inbox/index.d.ts.map +1 -0
  33. package/lib/components/index.d.ts +2 -0
  34. package/lib/components/index.d.ts.map +1 -1
  35. package/lib/hooks/activeCharacterPreference.d.ts +2 -1
  36. package/lib/hooks/activeCharacterPreference.d.ts.map +1 -1
  37. package/lib/hooks/activeCharacterPreference.js +9 -2
  38. package/lib/hooks/activeCharacterPreference.js.map +1 -1
  39. package/lib/hooks/characterOverrides.d.ts +29 -0
  40. package/lib/hooks/characterOverrides.d.ts.map +1 -0
  41. package/lib/hooks/characterOverrides.js +130 -0
  42. package/lib/hooks/characterOverrides.js.map +1 -0
  43. package/lib/hooks/characterSlotsPreference.d.ts +33 -0
  44. package/lib/hooks/characterSlotsPreference.d.ts.map +1 -0
  45. package/lib/hooks/characterSlotsPreference.js +141 -0
  46. package/lib/hooks/characterSlotsPreference.js.map +1 -0
  47. package/lib/hooks/index.d.ts +3 -0
  48. package/lib/hooks/index.d.ts.map +1 -1
  49. package/lib/hooks/useGlobalShortcutBridge.d.ts +21 -0
  50. package/lib/hooks/useGlobalShortcutBridge.d.ts.map +1 -0
  51. package/lib/hooks/useGlobalShortcutBridge.js +68 -0
  52. package/lib/hooks/useGlobalShortcutBridge.js.map +1 -0
  53. package/lib/index.js +1 -1
  54. package/lib/shardui/command.d.ts +1 -1
  55. package/package.json +3 -3
@@ -0,0 +1,249 @@
1
+ import {jsx,jsxs}from'react/jsx-runtime';import*as React from'react';import {Star,X,Settings}from'lucide-react';import {Popover,PopoverTrigger,PopoverContent}from'../../shardui/popover.js';import {useCharacterPicker}from'../AIToolbar/hooks/useActiveCharacter.js';import {useCharacterSlots,SLOT_NUMBERS}from'../../hooks/characterSlotsPreference.js';import {useCharacterOverrides,setCharacterOverrides}from'../../hooks/characterOverrides.js';// helpers
2
+ function findCharacterByFile(list, fileName) {
3
+ if (!fileName) return null;
4
+ return list.find(c => c.fileName === fileName) ?? null;
5
+ }
6
+ function SlotSettingsForm({
7
+ fileName,
8
+ characterName,
9
+ onClose
10
+ }) {
11
+ const overrides = useCharacterOverrides(fileName);
12
+ const [voiceId, setVoiceId] = React.useState(overrides.voiceId);
13
+ const [addendum, setAddendum] = React.useState(overrides.addendum);
14
+ // Keep local state in sync if the popover re-opens for a different snapshot.
15
+ React.useEffect(() => {
16
+ setVoiceId(overrides.voiceId);
17
+ setAddendum(overrides.addendum);
18
+ }, [overrides.voiceId, overrides.addendum]);
19
+ const handleSave = () => {
20
+ setCharacterOverrides(fileName, {
21
+ voiceId: voiceId.trim(),
22
+ addendum: addendum.trim()
23
+ });
24
+ onClose();
25
+ };
26
+ return jsxs("div", {
27
+ className: "flex flex-col gap-2 p-1",
28
+ children: [jsx("div", {
29
+ className: "px-1 text-xs font-semibold text-foreground/80",
30
+ children: characterName
31
+ }), jsxs("label", {
32
+ className: "flex flex-col gap-1",
33
+ children: [jsx("span", {
34
+ className: "text-xs text-muted-foreground",
35
+ children: "TTS voice ID"
36
+ }), jsx("input", {
37
+ type: "text",
38
+ value: voiceId,
39
+ onChange: e => setVoiceId(e.target.value),
40
+ placeholder: "e.g. ElevenLabs voice id",
41
+ spellCheck: false,
42
+ className: "w-full rounded-md border border-border bg-background px-2 py-1 text-xs font-mono outline-none focus:border-primary"
43
+ })]
44
+ }), jsxs("label", {
45
+ className: "flex flex-col gap-1",
46
+ children: [jsx("span", {
47
+ className: "text-xs text-muted-foreground",
48
+ children: "Prompt addendum"
49
+ }), jsx("textarea", {
50
+ value: addendum,
51
+ onChange: e => setAddendum(e.target.value),
52
+ rows: 4,
53
+ placeholder: "Extra instructions appended to this character's persona\u2026",
54
+ className: "w-full resize-y rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary"
55
+ })]
56
+ }), jsxs("div", {
57
+ className: "flex justify-end gap-2 pt-1",
58
+ children: [jsx("button", {
59
+ type: "button",
60
+ onClick: onClose,
61
+ className: "rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-muted",
62
+ children: "Cancel"
63
+ }), jsx("button", {
64
+ type: "button",
65
+ onClick: handleSave,
66
+ className: "rounded-md bg-primary px-2 py-1 text-xs font-medium text-primary-foreground hover:opacity-90",
67
+ children: "Save"
68
+ })]
69
+ })]
70
+ });
71
+ }
72
+ function SlotTile({
73
+ slot,
74
+ fileName,
75
+ character,
76
+ isDefault,
77
+ onPick,
78
+ onClear,
79
+ onActivate,
80
+ onSetDefault,
81
+ candidates
82
+ }) {
83
+ const [open, setOpen] = React.useState(false);
84
+ const [settingsOpen, setSettingsOpen] = React.useState(false);
85
+ const filled = Boolean(fileName);
86
+ const handleTileClick = () => {
87
+ if (filled) onActivate(slot);else setOpen(true);
88
+ };
89
+ const handleContextMenu = e => {
90
+ if (!filled) return;
91
+ e.preventDefault();
92
+ onClear(slot);
93
+ };
94
+ return jsxs(Popover, {
95
+ open: open && !filled,
96
+ onOpenChange: setOpen,
97
+ children: [jsx(PopoverTrigger, {
98
+ asChild: true,
99
+ children: jsxs("button", {
100
+ type: "button",
101
+ onClick: handleTileClick,
102
+ onContextMenu: handleContextMenu,
103
+ title: filled ? `Slot ${slot} · ${character?.name ?? fileName} (click to activate, right-click to clear)` : `Slot ${slot} · empty (click to assign)`,
104
+ className: ['group relative aspect-square w-full overflow-hidden rounded-xl border bg-muted/30 transition', 'flex items-center justify-center', filled ? 'border-border hover:border-primary/60' : 'border-dashed border-border/60 hover:border-primary/50 hover:bg-muted/50', isDefault ? 'ring-2 ring-primary ring-offset-2 ring-offset-background' : ''].join(' '),
105
+ children: [jsx("span", {
106
+ className: "absolute left-1 top-1 z-10 rounded bg-background/80 px-1 text-xs font-semibold text-foreground/70",
107
+ children: slot
108
+ }), filled && character?.avatarSrc ? jsx("img", {
109
+ src: character.avatarSrc,
110
+ alt: character.name,
111
+ className: "h-full w-full object-cover"
112
+ }) : filled ? jsx("span", {
113
+ className: "px-2 text-center text-sm font-medium text-foreground/80",
114
+ children: character?.name ?? fileName
115
+ }) : jsx("span", {
116
+ className: "text-2xl text-muted-foreground/60",
117
+ children: "+"
118
+ }), filled && jsx("button", {
119
+ type: "button",
120
+ onClick: e => {
121
+ e.stopPropagation();
122
+ onSetDefault(slot);
123
+ },
124
+ title: isDefault ? 'Default slot' : 'Set as default',
125
+ className: ['absolute right-1 top-1 z-10 rounded-full p-0.5 transition', isDefault ? 'bg-primary text-primary-foreground' : 'bg-background/80 text-muted-foreground opacity-0 group-hover:opacity-100 hover:text-foreground'].join(' '),
126
+ children: jsx(Star, {
127
+ className: "h-3 w-3",
128
+ fill: isDefault ? 'currentColor' : 'none'
129
+ })
130
+ }), filled && jsx("button", {
131
+ type: "button",
132
+ onClick: e => {
133
+ e.stopPropagation();
134
+ onClear(slot);
135
+ },
136
+ title: "Clear slot",
137
+ className: "absolute bottom-1 right-1 z-10 rounded-full bg-background/80 p-0.5 text-muted-foreground opacity-0 transition group-hover:opacity-100 hover:text-destructive",
138
+ children: jsx(X, {
139
+ className: "h-3 w-3"
140
+ })
141
+ }), filled && fileName && jsxs(Popover, {
142
+ open: settingsOpen,
143
+ onOpenChange: setSettingsOpen,
144
+ children: [jsx(PopoverTrigger, {
145
+ asChild: true,
146
+ children: jsx("button", {
147
+ type: "button",
148
+ onClick: e => {
149
+ e.stopPropagation();
150
+ setSettingsOpen(true);
151
+ },
152
+ title: "Slot settings (voice id & prompt addendum)",
153
+ className: "absolute bottom-1 left-1 z-10 rounded-full bg-background/80 p-0.5 text-muted-foreground opacity-0 transition group-hover:opacity-100 hover:text-foreground",
154
+ children: jsx(Settings, {
155
+ className: "h-3 w-3"
156
+ })
157
+ })
158
+ }), jsx(PopoverContent, {
159
+ align: "start",
160
+ sideOffset: 6,
161
+ className: "w-72 p-2",
162
+ onClick: e => e.stopPropagation(),
163
+ children: jsx(SlotSettingsForm, {
164
+ fileName: fileName,
165
+ characterName: character?.name ?? fileName,
166
+ onClose: () => setSettingsOpen(false)
167
+ })
168
+ })]
169
+ })]
170
+ })
171
+ }), jsx(PopoverContent, {
172
+ align: "start",
173
+ sideOffset: 6,
174
+ className: "w-56 p-1",
175
+ children: jsx("div", {
176
+ className: "max-h-64 overflow-y-auto",
177
+ children: candidates.length === 0 ? jsx("p", {
178
+ className: "px-2 py-4 text-center text-sm text-muted-foreground",
179
+ children: "No characters installed."
180
+ }) : candidates.map(c => jsxs("button", {
181
+ type: "button",
182
+ onClick: () => {
183
+ onPick(slot, c.name, c.fileName);
184
+ setOpen(false);
185
+ },
186
+ className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-muted",
187
+ children: [c.avatarSrc ? jsx("img", {
188
+ src: c.avatarSrc,
189
+ alt: "",
190
+ className: "h-6 w-6 rounded object-cover"
191
+ }) : jsx("span", {
192
+ className: "inline-block h-6 w-6 rounded bg-muted"
193
+ }), jsx("span", {
194
+ className: "truncate",
195
+ children: c.name
196
+ })]
197
+ }, c.fileName ?? c.name))
198
+ })
199
+ })]
200
+ });
201
+ }
202
+ function CharacterSlotPicker({
203
+ className
204
+ }) {
205
+ const picker = useCharacterPicker();
206
+ const {
207
+ slots,
208
+ defaultSlot,
209
+ setSlot,
210
+ setDefaultSlot
211
+ } = useCharacterSlots();
212
+ const handlePick = React.useCallback((slot, name, fileName) => {
213
+ if (!fileName) return;
214
+ void setSlot(slot, fileName);
215
+ }, [setSlot]);
216
+ const handleClear = React.useCallback(slot => {
217
+ void setSlot(slot, null);
218
+ }, [setSlot]);
219
+ const handleActivate = React.useCallback(slot => {
220
+ const fileName = slots[slot];
221
+ const c = findCharacterByFile(picker.characters, fileName);
222
+ if (c?.name) picker.setActive(c.name);
223
+ }, [picker, slots]);
224
+ const handleSetDefault = React.useCallback(slot => {
225
+ void setDefaultSlot(slot);
226
+ // Also activate the default slot's character so the change is visible.
227
+ const fileName = slots[slot];
228
+ const c = findCharacterByFile(picker.characters, fileName);
229
+ if (c?.name) picker.setActive(c.name);
230
+ }, [picker, slots, setDefaultSlot]);
231
+ return jsx("div", {
232
+ className: ['grid grid-cols-3 gap-2', className ?? ''].join(' '),
233
+ children: SLOT_NUMBERS.map(slot => {
234
+ const fileName = slots[slot];
235
+ const character = findCharacterByFile(picker.characters, fileName);
236
+ return jsx(SlotTile, {
237
+ slot: slot,
238
+ fileName: fileName,
239
+ character: character,
240
+ isDefault: slot === defaultSlot,
241
+ onPick: handlePick,
242
+ onClear: handleClear,
243
+ onActivate: handleActivate,
244
+ onSetDefault: handleSetDefault,
245
+ candidates: picker.characters
246
+ }, slot);
247
+ })
248
+ });
249
+ }export{CharacterSlotPicker};//# sourceMappingURL=CharacterSlotPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CharacterSlotPicker.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,3 @@
1
+ export { CharacterSlotPicker } from './CharacterSlotPicker';
2
+ export type { CharacterSlotPickerProps } from './CharacterSlotPicker';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/CharacterSlotPicker/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { type ChannelMeta } from './channelsMeta';
3
+ import { type PairingTokenResult } from './ChannelConnectModal';
4
+ export interface ChannelBarProps {
5
+ /** Override the default channel list (Telegram/Discord/Slack/WhatsApp/Teams) */
6
+ channels?: ChannelMeta[];
7
+ /** Ids of currently connected channels (controlled) */
8
+ connectedChannelIds?: string[];
9
+ /** Called when a disconnected channel is clicked. If onConnect returns a PairingTokenResult
10
+ * (or a Promise that resolves to one), the modal switches to pairing mode and displays the
11
+ * QR + deep-link. Return void to render the token-entry / placeholder modal. */
12
+ onConnect?: (channel: ChannelMeta) => void | PairingTokenResult | Promise<void | PairingTokenResult>;
13
+ /** Called when the modal submits a token (non-pairing flow). */
14
+ onSubmitToken?: (channel: ChannelMeta, token: string) => void | Promise<void>;
15
+ /** Called when a connected channel is clicked. */
16
+ onDisconnect?: (channel: ChannelMeta) => void | Promise<void>;
17
+ /** Called when the modal is closed (cancel/X/backdrop). */
18
+ onModalClose?: (channel: ChannelMeta) => void;
19
+ /** External loading flag for the modal (e.g. while a parent gateway call is in flight) */
20
+ loading?: boolean;
21
+ /** External error string for the modal */
22
+ error?: string;
23
+ /** When supplied while modal is open, modal renders in pairing mode (QR + deep link). */
24
+ pairing?: PairingTokenResult;
25
+ className?: string;
26
+ }
27
+ declare function ChannelBar({ channels, connectedChannelIds, onConnect, onSubmitToken, onDisconnect, onModalClose, loading, error, pairing, className, }: ChannelBarProps): import("react/jsx-runtime").JSX.Element;
28
+ declare namespace ChannelBar {
29
+ var displayName: string;
30
+ }
31
+ declare const _default: React.MemoExoticComponent<typeof ChannelBar>;
32
+ export default _default;
33
+ //# sourceMappingURL=ChannelBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelBar.d.ts","sourceRoot":"","sources":["../../../../src/components/Inbox/Channels/ChannelBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAA4B,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAIrF,MAAM,WAAW,eAAe;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B;;qFAEiF;IACjF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,kBAAkB,GAAG,OAAO,CAAC,IAAI,GAAG,kBAAkB,CAAC,CAAC;IACrG,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,kDAAkD;IAClD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,0FAA0F;IAC1F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,iBAAS,UAAU,CAAC,EAChB,QAAmB,EACnB,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,KAAK,EACL,OAAO,EACP,SAAS,GACZ,EAAE,eAAe,2CA8FjB;kBAzGQ,UAAU;;;;AA6GnB,wBAAsC"}
@@ -0,0 +1,79 @@
1
+ import {jsxs,Fragment,jsx}from'react/jsx-runtime';import React__default,{useState,useMemo,useCallback}from'react';import {CHANNELS}from'./channelsMeta.js';import ChannelConnectModal from'./ChannelConnectModal.js';// main component
2
+ function ChannelBar({
3
+ channels = CHANNELS,
4
+ connectedChannelIds,
5
+ onConnect,
6
+ onSubmitToken,
7
+ onDisconnect,
8
+ onModalClose,
9
+ loading,
10
+ error,
11
+ pairing,
12
+ className
13
+ }) {
14
+ const [selected, setSelected] = useState(null);
15
+ const [internalPairing, setInternalPairing] = useState(null);
16
+ const [internalLoading, setInternalLoading] = useState(false);
17
+ const connectedSet = useMemo(() => new Set(connectedChannelIds ?? []), [connectedChannelIds]);
18
+ const handleClose = useCallback(() => {
19
+ const ch = selected;
20
+ setSelected(null);
21
+ setInternalPairing(null);
22
+ setInternalLoading(false);
23
+ if (ch) onModalClose?.(ch);
24
+ }, [selected, onModalClose]);
25
+ const handleClick = useCallback(async channel => {
26
+ if (connectedSet.has(channel.id)) {
27
+ await onDisconnect?.(channel);
28
+ return;
29
+ }
30
+ setSelected(channel);
31
+ setInternalPairing(null);
32
+ if (!onConnect) return;
33
+ setInternalLoading(true);
34
+ try {
35
+ const result = await onConnect(channel);
36
+ if (result && typeof result === 'object' && 'token' in result) {
37
+ setInternalPairing(result);
38
+ }
39
+ } finally {
40
+ setInternalLoading(false);
41
+ }
42
+ }, [connectedSet, onConnect, onDisconnect]);
43
+ const handleSubmitToken = useCallback(async token => {
44
+ if (!selected) return;
45
+ await onSubmitToken?.(selected, token);
46
+ handleClose();
47
+ }, [selected, onSubmitToken, handleClose]);
48
+ const effectivePairing = pairing ?? internalPairing ?? undefined;
49
+ const effectiveLoading = loading || internalLoading;
50
+ return jsxs(Fragment, {
51
+ children: [jsx("div", {
52
+ className: `flex items-center justify-center gap-1 mt-2 flex-wrap max-w-xs mx-auto ${className ?? ''}`,
53
+ children: channels.map(channel => {
54
+ const isConnected = connectedSet.has(channel.id);
55
+ return jsxs("button", {
56
+ type: "button",
57
+ onClick: () => handleClick(channel),
58
+ className: `relative group flex items-center justify-center w-8 h-8 rounded-lg transition-all ${isConnected ? 'text-green-600 bg-green-500/10 hover:bg-green-500/20' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-100'}`,
59
+ title: `${channel.name}${isConnected ? ' (connected — click to disconnect)' : ''}`,
60
+ "aria-label": `Connect ${channel.name}`,
61
+ children: [channel.icon, isConnected && jsx("span", {
62
+ className: "absolute -bottom-0.5 -right-0.5 block w-3 h-3 rounded-full bg-green-500 ring-2 ring-white",
63
+ "aria-hidden": "true"
64
+ })]
65
+ }, channel.id);
66
+ })
67
+ }), selected && jsx(ChannelConnectModal, {
68
+ isOpen: !!selected,
69
+ channel: selected,
70
+ loading: effectiveLoading,
71
+ error: error,
72
+ pairing: effectivePairing,
73
+ onClose: handleClose,
74
+ onConnect: handleSubmitToken
75
+ })]
76
+ });
77
+ }
78
+ ChannelBar.displayName = 'ChannelBar';
79
+ var ChannelBar_default = React__default.memo(ChannelBar);export{ChannelBar_default as default};//# sourceMappingURL=ChannelBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelBar.js","sources":["../../../../src/components/Inbox/Channels/ChannelBar.tsx"],"sourcesContent":[null],"names":[],"mappings":";AAQI,SAAA,UAAW,CAAA;UACX,GAAA,QAAA;AACA,EAAA,mBAAA;AACA,EAAA,SAAA;;AAEiF,EAAA,YAAA;AACjF,EAAA,YAAU;SACV;AACA,EAAA,KAAA;SACA;AACA,EAAA;;QAEA,CAAA,QAAa,EAAE,WAAU,CAAA,GAAA,QAAW,CAAA;QACpC,CAAA,eAAA,EAAA,kBAAA,CAAA,GAAA,QAAA,CAAA,IAAA,CAAA;QACA,CAAA,eAAkB,EAAA,kBAAA,CAAA,GAAA,QAAA,CAAA,KAAA,CAAA;QAClB,YAAA,GAAA,OAAA,CAAA,MAAA,IAAA,GAAA,CAAA,mBAA0C,IAAA,EAAA,CAAA,EAAA,CAAA,mBAAA,CAAA,CAAA;QAC1C,WAAe,GAAA,WAAA,CAAA,MAAA;IACf,MAAA,EAAA,GAAA,QAAA;IACA,WAAU,CAAA,IAAA,CAAA;IACV,kBAAkB,CAAC,IAAA,CAAA;AACtB,IAAA,kBAAA,CAAA,KAAA,CAAA;AAID,IAAA,IAAA,EAAA,EAAA,YAAS,GAAU,EAAC,CAChB;4BADe,CAAA,CAAA;;;;AA6GnB,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { type ChannelMeta } from './channelsMeta';
3
+ export interface PairingTokenResult {
4
+ token: string;
5
+ deepLink?: string;
6
+ expiresAt?: number;
7
+ }
8
+ export interface ChannelConnectModalProps {
9
+ isOpen: boolean;
10
+ channel: ChannelMeta;
11
+ loading?: boolean;
12
+ error?: string;
13
+ /** When present, modal renders QR + deep-link pairing UI instead of token entry. */
14
+ pairing?: PairingTokenResult;
15
+ onClose: () => void;
16
+ onConnect: (token: string) => Promise<void> | void;
17
+ }
18
+ declare function ChannelConnectModal({ isOpen, channel, loading, error, pairing, onClose, onConnect, }: ChannelConnectModalProps): import("react/jsx-runtime").JSX.Element;
19
+ declare namespace ChannelConnectModal {
20
+ var displayName: string;
21
+ }
22
+ declare const _default: React.MemoExoticComponent<typeof ChannelConnectModal>;
23
+ export default _default;
24
+ //# sourceMappingURL=ChannelConnectModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelConnectModal.d.ts","sourceRoot":"","sources":["../../../../src/components/Inbox/Channels/ChannelConnectModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAIlD,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACtD;AAUD,iBAAS,mBAAmB,CAAC,EACzB,MAAM,EACN,OAAO,EACP,OAAO,EACP,KAAK,EACL,OAAO,EACP,OAAO,EACP,SAAS,GACZ,EAAE,wBAAwB,2CAuM1B;kBA/MQ,mBAAmB;;;;AAmN5B,wBAA+C"}
@@ -0,0 +1,177 @@
1
+ import {jsx,jsxs,Fragment}from'react/jsx-runtime';import React__default,{useState,useEffect,useCallback}from'react';// helper functions
2
+ function buildQrUrl(data) {
3
+ return `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(data)}`;
4
+ }
5
+ // main component
6
+ function ChannelConnectModal({
7
+ isOpen,
8
+ channel,
9
+ loading,
10
+ error,
11
+ pairing,
12
+ onClose,
13
+ onConnect
14
+ }) {
15
+ const [token, setToken] = useState('');
16
+ const [copied, setCopied] = useState(false);
17
+ useEffect(() => {
18
+ if (isOpen) {
19
+ setToken('');
20
+ setCopied(false);
21
+ }
22
+ }, [isOpen, channel.id]);
23
+ const handleSubmit = useCallback(e => {
24
+ e.preventDefault();
25
+ onConnect(token.trim());
26
+ }, [token, onConnect]);
27
+ const handleBackdropClick = useCallback(e => {
28
+ if (e.target === e.currentTarget && !loading) onClose();
29
+ }, [onClose, loading]);
30
+ const handleCopy = useCallback(() => {
31
+ if (!pairing?.token) return;
32
+ navigator.clipboard?.writeText(pairing.token).then(() => {
33
+ setCopied(true);
34
+ setTimeout(() => setCopied(false), 1500);
35
+ });
36
+ }, [pairing]);
37
+ if (!isOpen) return null;
38
+ const inPairingMode = !!pairing;
39
+ const tokenRequired = !inPairingMode && channel.tokenLabel.length > 0;
40
+ const canSubmit = tokenRequired ? token.trim().length > 0 && !loading : !loading;
41
+ const qrPayload = pairing ? pairing.deepLink ?? pairing.token : '';
42
+ return jsx("div", {
43
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm px-4",
44
+ onClick: handleBackdropClick,
45
+ role: "dialog",
46
+ "aria-modal": "true",
47
+ "aria-labelledby": "channel-connect-title",
48
+ children: jsx("div", {
49
+ className: "w-full max-w-md bg-white rounded-2xl shadow-xl overflow-hidden",
50
+ children: jsxs("form", {
51
+ onSubmit: handleSubmit,
52
+ children: [jsxs("div", {
53
+ className: "px-6 py-5 border-b border-gray-100 flex items-center gap-3",
54
+ children: [jsx("div", {
55
+ className: "flex items-center justify-center w-10 h-10 rounded-lg bg-gray-50 text-gray-700",
56
+ children: channel.icon
57
+ }), jsxs("div", {
58
+ className: "flex-1 min-w-0",
59
+ children: [jsxs("h3", {
60
+ id: "channel-connect-title",
61
+ className: "text-base font-semibold text-gray-900 truncate",
62
+ children: ["Connect ", channel.name]
63
+ }), jsx("p", {
64
+ className: "text-xs text-gray-500 truncate",
65
+ children: channel.description
66
+ })]
67
+ }), jsx("button", {
68
+ type: "button",
69
+ onClick: onClose,
70
+ disabled: loading,
71
+ className: "text-gray-400 hover:text-gray-700 disabled:opacity-50",
72
+ "aria-label": "Close",
73
+ children: jsx("svg", {
74
+ width: 20,
75
+ height: 20,
76
+ viewBox: "0 0 24 24",
77
+ fill: "none",
78
+ stroke: "currentColor",
79
+ strokeWidth: 2,
80
+ children: jsx("path", {
81
+ d: "M18 6L6 18M6 6l12 12"
82
+ })
83
+ })
84
+ })]
85
+ }), jsxs("div", {
86
+ className: "px-6 py-5 space-y-4",
87
+ children: [inPairingMode ? jsxs("div", {
88
+ className: "flex flex-col items-center gap-3",
89
+ children: [jsx("div", {
90
+ className: "flex items-center justify-center p-3 rounded-lg bg-white border border-gray-200",
91
+ children: jsx("img", {
92
+ src: buildQrUrl(qrPayload),
93
+ alt: `QR code to pair ${channel.name}`,
94
+ width: 200,
95
+ height: 200,
96
+ className: "block"
97
+ })
98
+ }), pairing?.deepLink && jsxs("a", {
99
+ href: pairing.deepLink,
100
+ target: "_blank",
101
+ rel: "noopener noreferrer",
102
+ className: "inline-flex items-center gap-1 px-3 py-1.5 text-sm rounded-lg bg-blue-600 text-white hover:bg-blue-700",
103
+ children: ["Open ", channel.name]
104
+ }), pairing?.token && jsx("button", {
105
+ type: "button",
106
+ onClick: handleCopy,
107
+ className: "text-xs text-gray-600 hover:text-gray-900 underline",
108
+ children: copied ? 'Copied!' : 'Copy pairing token'
109
+ }), jsxs("div", {
110
+ className: "flex items-center gap-2 text-xs text-gray-500",
111
+ children: [jsx("svg", {
112
+ className: "animate-spin",
113
+ width: 14,
114
+ height: 14,
115
+ viewBox: "0 0 24 24",
116
+ fill: "none",
117
+ stroke: "currentColor",
118
+ strokeWidth: 2,
119
+ children: jsx("path", {
120
+ d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"
121
+ })
122
+ }), "Waiting for pairing\u2026"]
123
+ })]
124
+ }) : tokenRequired ? jsxs("div", {
125
+ children: [jsx("label", {
126
+ htmlFor: "channel-token-input",
127
+ className: "block text-sm font-medium text-gray-700 mb-1.5",
128
+ children: channel.tokenLabel
129
+ }), jsx("input", {
130
+ id: "channel-token-input",
131
+ type: "text",
132
+ autoComplete: "off",
133
+ spellCheck: false,
134
+ value: token,
135
+ onChange: e => setToken(e.target.value),
136
+ placeholder: channel.tokenPlaceholder,
137
+ disabled: loading,
138
+ className: "w-full px-3 py-2 text-sm rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500 disabled:bg-gray-50"
139
+ })]
140
+ }) : jsx("div", {
141
+ className: "flex items-center justify-center h-32 rounded-lg border border-dashed border-gray-300 bg-gray-50 text-xs text-gray-500",
142
+ children: loading ? 'Generating pairing token…' : 'Click Connect to start pairing.'
143
+ }), jsxs("p", {
144
+ className: "text-xs text-gray-500 leading-relaxed whitespace-pre-line",
145
+ children: [channel.tokenHelp, channel.docsLink && jsxs(Fragment, {
146
+ children: [' ', jsx("a", {
147
+ href: channel.docsLink,
148
+ target: "_blank",
149
+ rel: "noopener noreferrer",
150
+ className: "text-blue-600 hover:underline",
151
+ children: "Learn more"
152
+ })]
153
+ })]
154
+ }), error && jsx("div", {
155
+ className: "text-xs text-red-600 bg-red-50 border border-red-200 rounded-lg px-3 py-2",
156
+ children: error
157
+ })]
158
+ }), jsxs("div", {
159
+ className: "px-6 py-4 bg-gray-50 flex items-center justify-end gap-2",
160
+ children: [jsx("button", {
161
+ type: "button",
162
+ onClick: onClose,
163
+ className: "px-4 py-2 text-sm rounded-lg text-gray-700 hover:bg-gray-200",
164
+ children: inPairingMode ? 'Close' : 'Cancel'
165
+ }), !inPairingMode && jsx("button", {
166
+ type: "submit",
167
+ disabled: !canSubmit,
168
+ className: "px-4 py-2 text-sm rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed",
169
+ children: loading ? 'Connecting…' : 'Connect'
170
+ })]
171
+ })]
172
+ })
173
+ })
174
+ });
175
+ }
176
+ ChannelConnectModal.displayName = 'ChannelConnectModal';
177
+ var ChannelConnectModal$1 = React__default.memo(ChannelConnectModal);export{ChannelConnectModal$1 as default};//# sourceMappingURL=ChannelConnectModal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelConnectModal.js","sources":["../../../../src/components/Inbox/Channels/ChannelConnectModal.tsx"],"sourcesContent":[null],"names":[],"mappings":"oHAKA;SACS,UAAS,CAAA,IAAA,EAAA;SACd,CAAA,8DAAkB,EAAA,kBAAA,CAAA,IAAA,CAAA,CAAA,CAAA;;AAErB;AAED,4BAAiB,CAAA;QACb;SACA;SACA;OACA;SACA;SACA;;AAEA,CAAA,EAAA;AACH,EAAA,MAAA,CAAA,KAAA,EAAA,QAAA,CAAA,GAAA,QAAA,CAAA,EAAA,CAAA;AAUD,EAAA,MAAA,CAAA,MAAA,EAAS,qBAAoB,CACzB,MAAM;kBADD;;;;AAmNT,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,16 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface ChannelMeta {
3
+ id: string;
4
+ name: string;
5
+ description: string;
6
+ icon: ReactNode;
7
+ /** Backend channel-type identifier consumers can map to their own gateway (e.g. 'telegram-webhook'). */
8
+ channelType?: string;
9
+ /** Token field label (empty string => pairing-only flow with no manual token entry) */
10
+ tokenLabel: string;
11
+ tokenPlaceholder: string;
12
+ tokenHelp: string;
13
+ docsLink?: string;
14
+ }
15
+ export declare const CHANNELS: ChannelMeta[];
16
+ //# sourceMappingURL=channelsMeta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channelsMeta.d.ts","sourceRoot":"","sources":["../../../../src/components/Inbox/Channels/channelsMeta.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI9C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,wGAAwG;IACxG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAkED,eAAO,MAAM,QAAQ,EAAE,WAAW,EA0DjC,CAAC"}