@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.d.mts +299 -0
- package/dist/index.d.ts +299 -0
- package/dist/index.js +932 -0
- package/dist/index.mjs +892 -0
- package/package.json +61 -0
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
|
+
};
|