@supernal/interface-nextjs 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,892 @@
1
+ "use client";
2
+
3
+ // src/contexts/ChatInputContext.tsx
4
+ import { createContext, useContext, useCallback, useRef } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var ChatInputContext = createContext(null);
7
+ function useChatInput() {
8
+ const context = useContext(ChatInputContext);
9
+ if (!context) {
10
+ throw new Error("useChatInput must be used within a ChatInputProvider");
11
+ }
12
+ return context;
13
+ }
14
+ function ChatInputProvider({ children }) {
15
+ const inputCallbackRef = useRef(null);
16
+ const registerInput = useCallback((callback) => {
17
+ inputCallbackRef.current = callback;
18
+ }, []);
19
+ const insertText = useCallback((text, submit = false) => {
20
+ if (inputCallbackRef.current) {
21
+ inputCallbackRef.current(text, submit);
22
+ }
23
+ }, []);
24
+ return /* @__PURE__ */ jsx(ChatInputContext.Provider, { value: { insertText, registerInput }, children });
25
+ }
26
+
27
+ // src/contexts/ChatProvider.tsx
28
+ import { createContext as createContext2, useContext as useContext2, useState, useCallback as useCallback2, useEffect } from "react";
29
+
30
+ // src/lib/ChatAIInterface.ts
31
+ import {
32
+ AIInterface
33
+ } from "@supernal/interface";
34
+
35
+ // src/lib/ToolManager.ts
36
+ import { setDefaultToolReporter, setGlobalToolReporter } from "@supernal/interface/browser";
37
+ var ToolManagerClass = class {
38
+ constructor() {
39
+ this.listeners = [];
40
+ this.lastReport = null;
41
+ setGlobalToolReporter({
42
+ reportExecution: (result) => {
43
+ this.reportExecution({
44
+ toolName: result.toolName,
45
+ elementId: result.elementId,
46
+ actionType: result.actionType,
47
+ success: result.success,
48
+ message: result.message,
49
+ data: result.data
50
+ });
51
+ }
52
+ });
53
+ setDefaultToolReporter((result) => {
54
+ this.reportExecution({
55
+ toolName: "Tool",
56
+ success: result.success,
57
+ message: result.message || (result.error ? result.error.message : "Tool executed"),
58
+ data: result.data,
59
+ error: result.error
60
+ });
61
+ });
62
+ }
63
+ /**
64
+ * Subscribe to tool execution results
65
+ */
66
+ subscribe(listener) {
67
+ this.listeners.push(listener);
68
+ return () => {
69
+ this.listeners = this.listeners.filter((l) => l !== listener);
70
+ };
71
+ }
72
+ /**
73
+ * Tools call this to report execution results (auto-called by HOCs)
74
+ */
75
+ reportExecution(result) {
76
+ const fullResult = {
77
+ ...result,
78
+ timestamp: result.timestamp || (/* @__PURE__ */ new Date()).toISOString()
79
+ };
80
+ this.lastReport = fullResult;
81
+ this.listeners.forEach((listener) => {
82
+ try {
83
+ listener(fullResult);
84
+ } catch {
85
+ }
86
+ });
87
+ }
88
+ /**
89
+ * Get the last reported tool execution (for AIInterface)
90
+ */
91
+ getLastReport() {
92
+ return this.lastReport;
93
+ }
94
+ /**
95
+ * Clear the last report
96
+ */
97
+ clearLastReport() {
98
+ this.lastReport = null;
99
+ }
100
+ /**
101
+ * Helper to create a success result (for custom messages)
102
+ */
103
+ success(toolName, message, data) {
104
+ return {
105
+ toolName,
106
+ success: true,
107
+ message,
108
+ data,
109
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
110
+ };
111
+ }
112
+ /**
113
+ * Helper to create a failure result (for custom messages)
114
+ */
115
+ failure(toolName, message, data) {
116
+ return {
117
+ toolName,
118
+ success: false,
119
+ message,
120
+ data,
121
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
122
+ };
123
+ }
124
+ };
125
+ var ToolManager = new ToolManagerClass();
126
+
127
+ // src/lib/ChatAIInterface.ts
128
+ var DemoAIInterface = class extends AIInterface {
129
+ constructor() {
130
+ super();
131
+ this.toolExecutionListeners = [];
132
+ ToolManager.subscribe((result) => {
133
+ this.notifyToolExecutionListeners(result);
134
+ });
135
+ }
136
+ /**
137
+ * Subscribe to tool execution results (for chat UI)
138
+ */
139
+ onToolExecution(callback) {
140
+ this.toolExecutionListeners.push(callback);
141
+ return () => {
142
+ this.toolExecutionListeners = this.toolExecutionListeners.filter((cb) => cb !== callback);
143
+ };
144
+ }
145
+ /**
146
+ * Notify all listeners about tool execution
147
+ */
148
+ notifyToolExecutionListeners(result) {
149
+ this.toolExecutionListeners.forEach((listener) => {
150
+ try {
151
+ listener(result);
152
+ } catch (error) {
153
+ console.error("Error in tool execution listener:", error);
154
+ }
155
+ });
156
+ }
157
+ /**
158
+ * Find and execute command - backward compatible wrapper
159
+ *
160
+ * Convenience method that combines findToolsForCommand and executeCommand
161
+ */
162
+ async findAndExecuteCommand(query, _currentContainer) {
163
+ const response = await this.processQuery(query);
164
+ return {
165
+ success: response.success,
166
+ message: response.message,
167
+ tool: response.executedTool
168
+ };
169
+ }
170
+ };
171
+
172
+ // src/contexts/ChatProvider.tsx
173
+ import { jsx as jsx2 } from "react/jsx-runtime";
174
+ var ChatContext = createContext2(null);
175
+ function useChatContext() {
176
+ const context = useContext2(ChatContext);
177
+ if (!context) {
178
+ throw new Error("useChatContext must be used within ChatProvider");
179
+ }
180
+ return context;
181
+ }
182
+ var STORAGE_KEY = "supernal-chat-messages";
183
+ var MAX_MESSAGES = 100;
184
+ function getInitialMessages() {
185
+ return [
186
+ {
187
+ id: "1",
188
+ text: "\u{1F44B} Welcome to @supernal-interface Demo!",
189
+ type: "system",
190
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
191
+ },
192
+ {
193
+ id: "2",
194
+ text: "This is NOT real AI - it's a demo showing how AI would interact with @Tool decorated methods.",
195
+ type: "system",
196
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
197
+ },
198
+ {
199
+ id: "3",
200
+ text: '\u{1F3AE} Try these commands:\n\u2022 "open menu" or "close menu"\n\u2022 "toggle notifications"\n\u2022 "set priority high"',
201
+ type: "system",
202
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
203
+ },
204
+ {
205
+ id: "4",
206
+ text: '\u{1F5FA}\uFE0F Navigate pages:\n\u2022 "architecture" or "dashboard"\n\u2022 "demo" or "home"\n\u2022 "docs" or "examples"',
207
+ type: "system",
208
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
209
+ },
210
+ {
211
+ id: "5",
212
+ text: "\u{1F4AC} Your chat history persists across pages and refreshes!",
213
+ type: "system",
214
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
215
+ },
216
+ {
217
+ id: "6",
218
+ text: "\u{1F4BE} Advanced Demo: Uses StateManager with localStorage - your widget state persists too!",
219
+ type: "system",
220
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
221
+ }
222
+ ];
223
+ }
224
+ function ChatProvider({
225
+ children,
226
+ mode = "fuzzy",
227
+ apiKey,
228
+ onToolExecute
229
+ }) {
230
+ const [messages, setMessages] = useState([]);
231
+ useEffect(() => {
232
+ if (typeof window === "undefined") return;
233
+ try {
234
+ const saved = localStorage.getItem(STORAGE_KEY);
235
+ if (saved) {
236
+ const loaded = JSON.parse(saved).map((m) => ({
237
+ ...m,
238
+ timestamp: new Date(m.timestamp).toISOString()
239
+ }));
240
+ setMessages(loaded);
241
+ } else {
242
+ setMessages(getInitialMessages());
243
+ }
244
+ } catch (error) {
245
+ console.error("Failed to load messages:", error);
246
+ setMessages(getInitialMessages());
247
+ }
248
+ }, []);
249
+ const [isLoading, setIsLoading] = useState(false);
250
+ const [aiInterface] = useState(() => new DemoAIInterface());
251
+ useEffect(() => {
252
+ if (onToolExecute) {
253
+ return aiInterface.onToolExecution((result) => {
254
+ onToolExecute(result.toolName, result);
255
+ });
256
+ }
257
+ }, [aiInterface, onToolExecute]);
258
+ useEffect(() => {
259
+ try {
260
+ const toSave = messages.slice(-MAX_MESSAGES);
261
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
262
+ } catch (error) {
263
+ console.error("Failed to save messages:", error);
264
+ }
265
+ }, [messages]);
266
+ const sendMessage = useCallback2(async (text) => {
267
+ const userMessage = {
268
+ id: Date.now().toString(),
269
+ text,
270
+ type: "user",
271
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
272
+ };
273
+ setMessages((prev) => [...prev, userMessage]);
274
+ setIsLoading(true);
275
+ try {
276
+ const result = await aiInterface.findAndExecuteCommand(text);
277
+ const aiMessage = {
278
+ id: (Date.now() + 1).toString(),
279
+ text: result.message,
280
+ type: result.success ? "ai" : "system",
281
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
282
+ };
283
+ setMessages((prev) => [...prev, aiMessage]);
284
+ } catch (error) {
285
+ const errorMessage = {
286
+ id: (Date.now() + 1).toString(),
287
+ text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
288
+ type: "system",
289
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
290
+ };
291
+ setMessages((prev) => [...prev, errorMessage]);
292
+ } finally {
293
+ setIsLoading(false);
294
+ }
295
+ }, [aiInterface]);
296
+ const clearMessages = useCallback2(() => {
297
+ setMessages([]);
298
+ localStorage.removeItem(STORAGE_KEY);
299
+ }, []);
300
+ return /* @__PURE__ */ jsx2(ChatContext.Provider, { value: { messages, sendMessage, clearMessages, isLoading }, children });
301
+ }
302
+
303
+ // src/components/ChatBubble.tsx
304
+ import { useState as useState2, useRef as useRef2, useEffect as useEffect2 } from "react";
305
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
306
+ var ChatNames = {
307
+ bubble: "chat-bubble",
308
+ input: "chat-input",
309
+ sendButton: "chat-send-button",
310
+ clearButton: "chat-clear-button",
311
+ messages: "chat-messages"
312
+ };
313
+ var CHAT_EXPANDED_KEY = "supernal-chat-expanded";
314
+ var ChatBubble = ({
315
+ theme = "auto",
316
+ position = "bottom-right",
317
+ welcomeMessage
318
+ }) => {
319
+ const { messages, sendMessage, clearMessages } = useChatContext();
320
+ const [isExpanded, setIsExpanded] = useState2(false);
321
+ const [inputValue, setInputValue] = useState2("");
322
+ const [lastReadMessageCount, setLastReadMessageCount] = useState2(0);
323
+ const [showWelcome, setShowWelcome] = useState2(true);
324
+ const [isClient, setIsClient] = useState2(false);
325
+ const messagesEndRef = useRef2(null);
326
+ const inputRef = useRef2(null);
327
+ useEffect2(() => {
328
+ setIsClient(true);
329
+ if (messages.length > 0) {
330
+ setShowWelcome(false);
331
+ }
332
+ }, [messages.length]);
333
+ useEffect2(() => {
334
+ try {
335
+ const stored = localStorage.getItem(CHAT_EXPANDED_KEY);
336
+ if (stored !== null) {
337
+ setIsExpanded(JSON.parse(stored));
338
+ }
339
+ } catch {
340
+ }
341
+ }, []);
342
+ const { registerInput } = useChatInput();
343
+ useEffect2(() => {
344
+ registerInput((text, submit = false) => {
345
+ setInputValue(text);
346
+ if (!isExpanded) {
347
+ setIsExpanded(true);
348
+ }
349
+ setTimeout(() => {
350
+ inputRef.current?.focus();
351
+ if (submit) {
352
+ sendMessage(text);
353
+ setInputValue("");
354
+ }
355
+ }, 100);
356
+ });
357
+ }, [registerInput, sendMessage, isExpanded]);
358
+ const unreadCount = Math.max(0, messages.length - lastReadMessageCount);
359
+ const hasUnread = unreadCount > 0 && !isExpanded;
360
+ useEffect2(() => {
361
+ if (isExpanded) {
362
+ messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
363
+ setLastReadMessageCount(messages.length);
364
+ if (messages.length > 0) {
365
+ setShowWelcome(false);
366
+ }
367
+ inputRef.current?.focus();
368
+ }
369
+ }, [messages, isExpanded]);
370
+ useEffect2(() => {
371
+ if (isExpanded) {
372
+ inputRef.current?.focus();
373
+ }
374
+ }, [isExpanded]);
375
+ useEffect2(() => {
376
+ const handleKeyDown = (e) => {
377
+ if ((e.metaKey || e.ctrlKey) && e.key === "/") {
378
+ e.preventDefault();
379
+ if (!isExpanded) {
380
+ setIsExpanded(true);
381
+ }
382
+ inputRef.current?.focus();
383
+ }
384
+ };
385
+ window.addEventListener("keydown", handleKeyDown);
386
+ return () => window.removeEventListener("keydown", handleKeyDown);
387
+ }, [isExpanded]);
388
+ const handleSend = (e) => {
389
+ e.preventDefault();
390
+ if (!inputValue.trim()) return;
391
+ sendMessage(inputValue.trim());
392
+ setInputValue("");
393
+ setTimeout(() => inputRef.current?.focus(), 0);
394
+ };
395
+ const handleToggle = () => {
396
+ const newExpandedState = !isExpanded;
397
+ setIsExpanded(newExpandedState);
398
+ try {
399
+ localStorage.setItem(CHAT_EXPANDED_KEY, JSON.stringify(newExpandedState));
400
+ } catch {
401
+ }
402
+ };
403
+ return /* @__PURE__ */ jsx3(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "fixed bottom-4 right-4 sm:bottom-6 sm:right-6 z-[9999]", children: [
404
+ isExpanded && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-16 right-0 w-[calc(100vw-2rem)] sm:w-[500px] lg:w-[600px] h-[calc(100vh-10rem)] sm:h-[min(600px,calc(100vh-6rem))] lg:h-[min(700px,calc(100vh-6rem))] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col", children: [
405
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-3 sm:p-4 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-purple-50 rounded-t-lg", children: [
406
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2 sm:space-x-3", children: [
407
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
408
+ /* @__PURE__ */ jsx3("div", { className: "w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx3("span", { className: "text-white text-base sm:text-lg", children: "\u{1F916}" }) }),
409
+ /* @__PURE__ */ jsx3("div", { className: "absolute -bottom-1 -right-1 w-3 h-3 sm:w-4 sm:h-4 bg-green-500 rounded-full border-2 border-white" })
410
+ ] }),
411
+ /* @__PURE__ */ jsxs("div", { children: [
412
+ /* @__PURE__ */ jsx3("h3", { className: "font-bold text-gray-900 text-sm sm:text-base", children: "Supernal Intelligence Interface" }),
413
+ /* @__PURE__ */ jsx3("p", { className: "text-xs text-gray-600 hidden sm:block", children: "I'm a TOOL system AI can use to control this site" })
414
+ ] })
415
+ ] }),
416
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-1", children: [
417
+ /* @__PURE__ */ jsx3(
418
+ "button",
419
+ {
420
+ onClick: clearMessages,
421
+ className: "p-1 text-gray-400 hover:text-gray-600 transition-colors",
422
+ title: "Clear chat",
423
+ "data-testid": ChatNames.clearButton,
424
+ children: /* @__PURE__ */ jsx3("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) })
425
+ }
426
+ ),
427
+ /* @__PURE__ */ jsx3(
428
+ "button",
429
+ {
430
+ onClick: handleToggle,
431
+ className: "p-1 text-gray-400 hover:text-gray-600 transition-colors",
432
+ title: "Minimize chat",
433
+ children: /* @__PURE__ */ jsx3("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })
434
+ }
435
+ )
436
+ ] })
437
+ ] }),
438
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-3 sm:p-4 space-y-3", "data-testid": ChatNames.messages, children: [
439
+ showWelcome && messages.length === 0 && /* @__PURE__ */ jsxs("div", { className: "bg-gradient-to-br from-blue-50 to-purple-50 p-3 sm:p-4 rounded-lg border border-blue-200 mb-4", children: [
440
+ /* @__PURE__ */ jsx3("h4", { className: "font-bold text-gray-900 mb-2 text-xs sm:text-sm", children: "\u{1F44B} Welcome! I'm NOT an AI" }),
441
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-700 mb-3", children: [
442
+ "I'm a ",
443
+ /* @__PURE__ */ jsx3("strong", { children: "tool system" }),
444
+ " that AI assistants (like Claude, GPT) can use to navigate and control this site. This enables ",
445
+ /* @__PURE__ */ jsx3("strong", { children: "agentic UX" }),
446
+ " \u2014 instead of clicking around, you tell an AI what you want, and it uses me to do it."
447
+ ] }),
448
+ /* @__PURE__ */ jsxs("div", { className: "bg-white p-2 sm:p-3 rounded border border-gray-200 mb-3", children: [
449
+ /* @__PURE__ */ jsx3("p", { className: "text-xs font-medium text-gray-900 mb-2", children: "Try these commands:" }),
450
+ /* @__PURE__ */ jsx3("div", { className: "space-y-1", children: [
451
+ { text: "open the docs", desc: "Navigate to documentation" },
452
+ { text: "show me the story system", desc: "View story system guide" },
453
+ { text: "go to examples", desc: "Browse code examples" }
454
+ ].map((cmd) => /* @__PURE__ */ jsxs(
455
+ "button",
456
+ {
457
+ onClick: () => {
458
+ setInputValue(cmd.text);
459
+ setShowWelcome(false);
460
+ setTimeout(() => inputRef.current?.focus(), 0);
461
+ },
462
+ className: "w-full text-left px-2 py-1.5 rounded hover:bg-blue-50 transition-colors group",
463
+ children: [
464
+ /* @__PURE__ */ jsxs("div", { className: "text-xs font-mono text-blue-700 group-hover:text-blue-900", children: [
465
+ '"',
466
+ cmd.text,
467
+ '"'
468
+ ] }),
469
+ /* @__PURE__ */ jsx3("div", { className: "text-xs text-gray-500 hidden sm:block", children: cmd.desc })
470
+ ]
471
+ },
472
+ cmd.text
473
+ )) })
474
+ ] }),
475
+ /* @__PURE__ */ jsx3("p", { className: "text-xs text-gray-600 italic", children: "Type a command or click a suggestion above to start" })
476
+ ] }),
477
+ messages.map((message) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
478
+ /* @__PURE__ */ jsx3(
479
+ "div",
480
+ {
481
+ className: `inline-block px-3 py-2 rounded-lg max-w-[85%] sm:max-w-xs text-xs sm:text-sm ${message.type === "user" ? "bg-blue-600 text-white ml-auto" : message.type === "ai" ? "bg-gray-200 text-gray-800" : "bg-yellow-100 text-yellow-800 text-xs"}`,
482
+ "data-testid": `chat-message-${message.type}`,
483
+ children: message.text
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsx3("div", { className: "text-xs text-gray-400 mt-1 px-1", children: new Date(message.timestamp).toLocaleTimeString() })
487
+ ] }, message.id)),
488
+ /* @__PURE__ */ jsx3("div", { ref: messagesEndRef })
489
+ ] }),
490
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSend, className: "p-3 sm:p-4 border-t border-gray-200", children: [
491
+ /* @__PURE__ */ jsxs("div", { className: "flex space-x-2", children: [
492
+ /* @__PURE__ */ jsx3(
493
+ "input",
494
+ {
495
+ ref: inputRef,
496
+ type: "text",
497
+ value: inputValue,
498
+ onChange: (e) => setInputValue(e.target.value),
499
+ placeholder: "Try: toggle notifications",
500
+ className: "flex-1 px-3 py-2 text-xs sm:text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800",
501
+ "data-testid": ChatNames.input
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsx3(
505
+ "button",
506
+ {
507
+ type: "submit",
508
+ disabled: !inputValue.trim(),
509
+ className: "px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors text-xs sm:text-sm font-medium",
510
+ "data-testid": ChatNames.sendButton,
511
+ children: "Execute"
512
+ }
513
+ )
514
+ ] }),
515
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 text-xs text-gray-500 hidden sm:block", children: [
516
+ /* @__PURE__ */ jsx3("strong", { children: "Demo:" }),
517
+ " Commands execute @Tool methods and update widgets above"
518
+ ] })
519
+ ] })
520
+ ] }),
521
+ /* @__PURE__ */ jsx3(
522
+ "button",
523
+ {
524
+ onClick: handleToggle,
525
+ className: "absolute bottom-0 right-0 w-12 h-12 sm:w-14 sm:h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-lg transition-all duration-200 flex items-center justify-center",
526
+ "data-testid": ChatNames.bubble,
527
+ title: isExpanded ? "Minimize chat" : "Open AI chat",
528
+ children: isExpanded ? /* @__PURE__ */ jsx3("svg", { className: "w-5 h-5 sm:w-6 sm:h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }) : /* @__PURE__ */ jsx3("svg", { className: "w-5 h-5 sm:w-6 sm:h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" }) })
529
+ }
530
+ ),
531
+ hasUnread && /* @__PURE__ */ jsx3("div", { className: "absolute -top-1 -right-1 w-4 h-4 sm:w-5 sm:h-5 bg-red-500 rounded-full flex items-center justify-center animate-pulse", "data-testid": "unread-indicator", children: /* @__PURE__ */ jsx3("span", { className: "text-xs text-white font-bold", children: unreadCount > 9 ? "9+" : unreadCount }) })
532
+ ] }) });
533
+ };
534
+
535
+ // src/components/AutoNavigationContext.tsx
536
+ import { useEffect as useEffect4, useState as useState4 } from "react";
537
+
538
+ // src/hooks/useNavigationGraph.tsx
539
+ import { useEffect as useEffect3, useState as useState3, useCallback as useCallback3, createContext as createContext3, useContext as useContext3 } from "react";
540
+ import {
541
+ NavigationGraph
542
+ } from "@supernal/interface/browser";
543
+ import { jsx as jsx4 } from "react/jsx-runtime";
544
+ var NavigationContextContext = createContext3("global");
545
+ function NavigationContextProvider({
546
+ value,
547
+ children
548
+ }) {
549
+ const graph = useNavigationGraph();
550
+ useEffect3(() => {
551
+ graph.setCurrentContext(value);
552
+ }, [value, graph]);
553
+ return /* @__PURE__ */ jsx4(NavigationContextContext.Provider, { value, children });
554
+ }
555
+ function useNavigationGraph() {
556
+ return NavigationGraph.getInstance();
557
+ }
558
+ function useCurrentContext() {
559
+ const contextFromProvider = useContext3(NavigationContextContext);
560
+ const graph = useNavigationGraph();
561
+ const [graphContext, setGraphContext] = useState3("");
562
+ useEffect3(() => {
563
+ const interval = setInterval(() => {
564
+ const current = graph.getCurrentContext?.() || "";
565
+ if (current !== graphContext) {
566
+ setGraphContext(current);
567
+ }
568
+ }, 100);
569
+ return () => clearInterval(interval);
570
+ }, [graph, graphContext]);
571
+ return contextFromProvider !== "global" ? contextFromProvider : graphContext;
572
+ }
573
+ function useRegisterTool(toolId, contextId, metadata) {
574
+ const graph = useNavigationGraph();
575
+ const currentContext = useCurrentContext();
576
+ useEffect3(() => {
577
+ let targetContext = contextId || currentContext;
578
+ if (!contextId && metadata) {
579
+ const detection = graph.detectToolContext?.(toolId, metadata);
580
+ if (detection && detection.confidence > 0.5) {
581
+ targetContext = detection.contextId;
582
+ }
583
+ }
584
+ graph.registerToolInContext(toolId, targetContext);
585
+ }, [toolId, contextId, currentContext, metadata, graph]);
586
+ }
587
+ function useNavigationPath(targetContextOrToolId, isToolId = false) {
588
+ const graph = useNavigationGraph();
589
+ const currentContext = useCurrentContext();
590
+ const [path, setPath] = useState3(null);
591
+ const [loading, setLoading] = useState3(true);
592
+ const [error, setError] = useState3();
593
+ useEffect3(() => {
594
+ try {
595
+ let targetContext = targetContextOrToolId;
596
+ if (isToolId) {
597
+ const toolContext = graph.getToolContext?.(targetContextOrToolId);
598
+ if (!toolContext) {
599
+ setError(`Tool "${targetContextOrToolId}" not found in any context`);
600
+ setLoading(false);
601
+ return;
602
+ }
603
+ targetContext = toolContext;
604
+ }
605
+ const computedPath = graph.computePath?.(currentContext, targetContext);
606
+ setPath(computedPath);
607
+ setLoading(false);
608
+ if (!computedPath) {
609
+ setError(`No path from "${currentContext}" to "${targetContext}"`);
610
+ }
611
+ } catch (err) {
612
+ setError(err instanceof Error ? err.message : String(err));
613
+ setLoading(false);
614
+ }
615
+ }, [targetContextOrToolId, isToolId, currentContext, graph]);
616
+ return { path, loading, error };
617
+ }
618
+ function useNavigate() {
619
+ const graph = useNavigationGraph();
620
+ const currentContext = useCurrentContext();
621
+ const [navigating, setNavigating] = useState3(false);
622
+ const [error, setError] = useState3();
623
+ const navigateTo = useCallback3(async (targetContextOrToolId, isToolId = false, executeNavigation) => {
624
+ setNavigating(true);
625
+ setError(void 0);
626
+ try {
627
+ let targetContext = targetContextOrToolId;
628
+ if (isToolId) {
629
+ const toolContext = graph.getToolContext?.(targetContextOrToolId);
630
+ if (!toolContext) {
631
+ throw new Error(`Tool "${targetContextOrToolId}" not found in any context`);
632
+ }
633
+ targetContext = toolContext;
634
+ }
635
+ if (currentContext === targetContext) {
636
+ setNavigating(false);
637
+ return true;
638
+ }
639
+ const path = graph.computePath?.(currentContext, targetContext);
640
+ if (!path) {
641
+ throw new Error(`No path from "${currentContext}" to "${targetContext}"`);
642
+ }
643
+ for (const step of path.steps) {
644
+ if (executeNavigation) {
645
+ await executeNavigation(step.navigationTool);
646
+ }
647
+ const changed = await graph.waitForContextChange?.(step.to, 3e3);
648
+ if (!changed) {
649
+ throw new Error(`Navigation to "${step.to}" failed - context did not change`);
650
+ }
651
+ }
652
+ setNavigating(false);
653
+ return true;
654
+ } catch (err) {
655
+ const errorMsg = err instanceof Error ? err.message : String(err);
656
+ setError(errorMsg);
657
+ setNavigating(false);
658
+ return false;
659
+ }
660
+ }, [currentContext, graph]);
661
+ return { navigateTo, navigating, error };
662
+ }
663
+ function useAllContexts() {
664
+ const graph = useNavigationGraph();
665
+ const [contexts, setContexts] = useState3(graph.getAllContexts?.());
666
+ useEffect3(() => {
667
+ const interval = setInterval(() => {
668
+ setContexts(graph.getAllContexts?.());
669
+ }, 1e3);
670
+ return () => clearInterval(interval);
671
+ }, [graph]);
672
+ return contexts;
673
+ }
674
+
675
+ // src/components/AutoNavigationContext.tsx
676
+ import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
677
+ function AutoNavigationContext({
678
+ children,
679
+ routes,
680
+ onNavigate
681
+ }) {
682
+ const [pathname, setPathname] = useState4("/");
683
+ useEffect4(() => {
684
+ if (typeof window !== "undefined") {
685
+ setPathname(window.location.pathname);
686
+ }
687
+ }, []);
688
+ const context = routes ? inferContextFromPath(pathname, routes) : null;
689
+ useEffect4(() => {
690
+ if (onNavigate && context) {
691
+ onNavigate(context);
692
+ }
693
+ }, [context, onNavigate]);
694
+ if (!context) {
695
+ return /* @__PURE__ */ jsx5(Fragment2, { children });
696
+ }
697
+ return /* @__PURE__ */ jsx5(NavigationContextProvider, { value: context, children });
698
+ }
699
+ function inferContextFromPath(path, customRoutes) {
700
+ if (customRoutes) {
701
+ for (const [name, routePath] of Object.entries(customRoutes)) {
702
+ if (path === routePath || path.startsWith(routePath + "/")) {
703
+ return name;
704
+ }
705
+ }
706
+ }
707
+ const patterns = {
708
+ "/": "home",
709
+ "/blog": "blog",
710
+ "/docs": "docs",
711
+ "/examples": "examples",
712
+ "/demo": "demo"
713
+ };
714
+ if (patterns[path]) return patterns[path];
715
+ const sortedPatterns = Object.entries(patterns).sort(
716
+ (a, b) => b[0].length - a[0].length
717
+ );
718
+ for (const [pattern, context] of sortedPatterns) {
719
+ if (path.startsWith(pattern) && pattern !== "/") {
720
+ return context;
721
+ }
722
+ }
723
+ return "global";
724
+ }
725
+
726
+ // src/components/SupernalProvider.tsx
727
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
728
+ function SupernalProvider({
729
+ children,
730
+ theme = "auto",
731
+ position = "bottom-right",
732
+ mode = "fuzzy",
733
+ apiKey,
734
+ welcomeMessage,
735
+ routes,
736
+ disabled = false,
737
+ onNavigate,
738
+ onToolExecute
739
+ }) {
740
+ const shouldRenderChatBubble = !disabled;
741
+ return /* @__PURE__ */ jsx6(ChatInputProvider, { children: /* @__PURE__ */ jsxs2(ChatProvider, { mode, apiKey, onToolExecute, children: [
742
+ /* @__PURE__ */ jsx6(AutoNavigationContext, { routes, onNavigate, children }),
743
+ shouldRenderChatBubble ? /* @__PURE__ */ jsx6(
744
+ ChatBubble,
745
+ {
746
+ theme,
747
+ position,
748
+ welcomeMessage
749
+ }
750
+ ) : null
751
+ ] }) });
752
+ }
753
+
754
+ // src/lib/FuzzyMatcher.ts
755
+ var FuzzyMatcher = class {
756
+ /**
757
+ * Find item that best matches the query
758
+ */
759
+ static findBest(items, query, getSearchableText) {
760
+ const lowerQuery = query.toLowerCase().trim();
761
+ if (!lowerQuery) return null;
762
+ let match = items.find((item) => {
763
+ const texts = getSearchableText(item);
764
+ return texts.some((text) => text.toLowerCase() === lowerQuery);
765
+ });
766
+ if (match) return match;
767
+ match = items.find((item) => {
768
+ const texts = getSearchableText(item);
769
+ return texts.some((text) => text.toLowerCase().includes(lowerQuery));
770
+ });
771
+ if (match) return match;
772
+ const queryWords = lowerQuery.split(/\s+/);
773
+ match = items.find((item) => {
774
+ const texts = getSearchableText(item);
775
+ return texts.some((text) => {
776
+ const lowerText = text.toLowerCase();
777
+ return queryWords.some((word) => lowerText.includes(word));
778
+ });
779
+ });
780
+ return match || null;
781
+ }
782
+ /**
783
+ * Find all items that match the query (ranked by relevance)
784
+ */
785
+ static findAll(items, query, getSearchableText) {
786
+ const lowerQuery = query.toLowerCase().trim();
787
+ if (!lowerQuery) return [];
788
+ const scored = items.map((item) => {
789
+ const texts = getSearchableText(item);
790
+ let score = 0;
791
+ for (const text of texts) {
792
+ const lowerText = text.toLowerCase();
793
+ if (lowerText === lowerQuery) {
794
+ score += 100;
795
+ }
796
+ if (lowerText.startsWith(lowerQuery)) {
797
+ score += 50;
798
+ }
799
+ if (lowerText.includes(lowerQuery)) {
800
+ score += 25;
801
+ }
802
+ const queryWords = lowerQuery.split(/\s+/);
803
+ const matchedWords = queryWords.filter((word) => lowerText.includes(word));
804
+ score += matchedWords.length * 10;
805
+ }
806
+ return { item, score };
807
+ });
808
+ return scored.filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).map(({ item }) => item);
809
+ }
810
+ };
811
+
812
+ // src/lib/fuzzyMatchTools.ts
813
+ function scoreToolMatch(query, tool) {
814
+ const lowerQuery = query.toLowerCase().trim();
815
+ let score = 0;
816
+ if (tool.examples && Array.isArray(tool.examples)) {
817
+ for (const example of tool.examples) {
818
+ if (example && typeof example === "string") {
819
+ const exampleLower = example.replace(/\{[^}]+\}/g, "").replace(/\s+/g, " ").trim().toLowerCase();
820
+ if (!exampleLower) {
821
+ continue;
822
+ }
823
+ const queryWords = lowerQuery.split(/\s+/);
824
+ const exampleWords = exampleLower.split(/\s+/);
825
+ if (exampleLower === lowerQuery) {
826
+ score += 20;
827
+ } else if (exampleLower.startsWith(lowerQuery + " ") || exampleLower.startsWith(lowerQuery)) {
828
+ score += 15;
829
+ } else if (queryWords.length > 1 && queryWords.every((qw) => exampleWords.includes(qw))) {
830
+ score += 12;
831
+ } else if (lowerQuery.length >= 4 && exampleWords.some((ew) => ew === lowerQuery)) {
832
+ score += 8;
833
+ } else if (lowerQuery.length >= 5 && (exampleLower.includes(lowerQuery) || lowerQuery.includes(exampleLower))) {
834
+ score += 3;
835
+ }
836
+ }
837
+ }
838
+ }
839
+ if (tool.description && typeof tool.description === "string") {
840
+ const descLower = tool.description.toLowerCase();
841
+ if (descLower.includes(lowerQuery) && lowerQuery.length >= 4) {
842
+ score += 2;
843
+ }
844
+ }
845
+ if (tool.methodName && typeof tool.methodName === "string") {
846
+ const methodLower = tool.methodName.toLowerCase();
847
+ const queryNoSpaces = lowerQuery.replace(/\s+/g, "");
848
+ if (methodLower === queryNoSpaces) {
849
+ score += 5;
850
+ } else if (methodLower.includes(queryNoSpaces) && queryNoSpaces.length >= 4) {
851
+ score += 2;
852
+ }
853
+ }
854
+ return score;
855
+ }
856
+ function findBestMatch(query, tools) {
857
+ let bestTool;
858
+ let bestScore = 0;
859
+ for (const tool of tools) {
860
+ const score = scoreToolMatch(query, tool);
861
+ if (score > bestScore) {
862
+ bestScore = score;
863
+ bestTool = tool;
864
+ }
865
+ }
866
+ return {
867
+ tool: bestTool,
868
+ score: bestScore,
869
+ confidence: bestScore > 0 ? Math.min(bestScore / 10, 1) : 0
870
+ };
871
+ }
872
+ export {
873
+ AutoNavigationContext,
874
+ ChatBubble,
875
+ ChatInputProvider,
876
+ ChatProvider,
877
+ DemoAIInterface,
878
+ FuzzyMatcher,
879
+ NavigationContextProvider,
880
+ SupernalProvider,
881
+ ToolManager,
882
+ findBestMatch,
883
+ scoreToolMatch,
884
+ useAllContexts,
885
+ useChatContext,
886
+ useChatInput,
887
+ useCurrentContext,
888
+ useNavigate,
889
+ useNavigationGraph,
890
+ useNavigationPath,
891
+ useRegisterTool
892
+ };