kanha-ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +573 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +57 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +566 -0
- package/dist/index.js.map +1 -0
- package/dist/widget.d.ts +65 -0
- package/dist/widget.js +414 -0
- package/dist/widget.js.map +1 -0
- package/package.json +54 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface Message {
|
|
4
|
+
role: "user" | "assistant" | "system";
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
type InferenceMode = "detecting" | "loading" | "ready" | "error";
|
|
8
|
+
interface KanhaChatConfig {
|
|
9
|
+
/** Base URL for model artifacts in storage (e.g. Supabase Storage public URL) */
|
|
10
|
+
modelUrl: string;
|
|
11
|
+
/** URL to the WASM library file. If omitted, auto-detected from modelSize. */
|
|
12
|
+
modelLib?: string;
|
|
13
|
+
/** Model size tier — used for WASM auto-detection. Default: "small" */
|
|
14
|
+
modelSize?: "small" | "medium" | "large";
|
|
15
|
+
/** System prompt for the bot */
|
|
16
|
+
systemPrompt?: string;
|
|
17
|
+
/** Sampling temperature (0-2). Default: 0.7 */
|
|
18
|
+
temperature?: number;
|
|
19
|
+
/** Max tokens to generate per response. Default: 1024 */
|
|
20
|
+
maxTokens?: number;
|
|
21
|
+
/** Context window size override */
|
|
22
|
+
contextWindowSize?: number;
|
|
23
|
+
}
|
|
24
|
+
interface KanhaBotTheme {
|
|
25
|
+
/** Primary brand color (hex). Default: "#0d9488" */
|
|
26
|
+
primaryColor?: string;
|
|
27
|
+
/** Widget position. Default: "bottom-right" */
|
|
28
|
+
position?: "bottom-right" | "bottom-left";
|
|
29
|
+
}
|
|
30
|
+
interface KanhaBotProps extends KanhaChatConfig {
|
|
31
|
+
/** Bot display name */
|
|
32
|
+
botName?: string;
|
|
33
|
+
/** Welcome message shown before first interaction */
|
|
34
|
+
welcomeMessage?: string;
|
|
35
|
+
/** Suggested prompts shown in empty state */
|
|
36
|
+
suggestions?: string[];
|
|
37
|
+
/** Theme customization */
|
|
38
|
+
theme?: KanhaBotTheme;
|
|
39
|
+
}
|
|
40
|
+
interface KanhaChatReturn {
|
|
41
|
+
messages: Message[];
|
|
42
|
+
input: string;
|
|
43
|
+
setInput: (value: string) => void;
|
|
44
|
+
isLoading: boolean;
|
|
45
|
+
isThinking: boolean;
|
|
46
|
+
mode: InferenceMode;
|
|
47
|
+
loadProgress: number;
|
|
48
|
+
error: string | null;
|
|
49
|
+
send: () => void;
|
|
50
|
+
clear: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
declare function KanhaBot({ modelUrl, modelLib, modelSize, systemPrompt, temperature, maxTokens, contextWindowSize, botName, welcomeMessage, suggestions, theme, }: KanhaBotProps): react_jsx_runtime.JSX.Element;
|
|
54
|
+
|
|
55
|
+
declare function useKanhaChat(config: KanhaChatConfig): KanhaChatReturn;
|
|
56
|
+
|
|
57
|
+
export { type InferenceMode, KanhaBot, type KanhaBotProps, type KanhaBotTheme, type KanhaChatConfig, type KanhaChatReturn, type Message, useKanhaChat };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface Message {
|
|
4
|
+
role: "user" | "assistant" | "system";
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
type InferenceMode = "detecting" | "loading" | "ready" | "error";
|
|
8
|
+
interface KanhaChatConfig {
|
|
9
|
+
/** Base URL for model artifacts in storage (e.g. Supabase Storage public URL) */
|
|
10
|
+
modelUrl: string;
|
|
11
|
+
/** URL to the WASM library file. If omitted, auto-detected from modelSize. */
|
|
12
|
+
modelLib?: string;
|
|
13
|
+
/** Model size tier — used for WASM auto-detection. Default: "small" */
|
|
14
|
+
modelSize?: "small" | "medium" | "large";
|
|
15
|
+
/** System prompt for the bot */
|
|
16
|
+
systemPrompt?: string;
|
|
17
|
+
/** Sampling temperature (0-2). Default: 0.7 */
|
|
18
|
+
temperature?: number;
|
|
19
|
+
/** Max tokens to generate per response. Default: 1024 */
|
|
20
|
+
maxTokens?: number;
|
|
21
|
+
/** Context window size override */
|
|
22
|
+
contextWindowSize?: number;
|
|
23
|
+
}
|
|
24
|
+
interface KanhaBotTheme {
|
|
25
|
+
/** Primary brand color (hex). Default: "#0d9488" */
|
|
26
|
+
primaryColor?: string;
|
|
27
|
+
/** Widget position. Default: "bottom-right" */
|
|
28
|
+
position?: "bottom-right" | "bottom-left";
|
|
29
|
+
}
|
|
30
|
+
interface KanhaBotProps extends KanhaChatConfig {
|
|
31
|
+
/** Bot display name */
|
|
32
|
+
botName?: string;
|
|
33
|
+
/** Welcome message shown before first interaction */
|
|
34
|
+
welcomeMessage?: string;
|
|
35
|
+
/** Suggested prompts shown in empty state */
|
|
36
|
+
suggestions?: string[];
|
|
37
|
+
/** Theme customization */
|
|
38
|
+
theme?: KanhaBotTheme;
|
|
39
|
+
}
|
|
40
|
+
interface KanhaChatReturn {
|
|
41
|
+
messages: Message[];
|
|
42
|
+
input: string;
|
|
43
|
+
setInput: (value: string) => void;
|
|
44
|
+
isLoading: boolean;
|
|
45
|
+
isThinking: boolean;
|
|
46
|
+
mode: InferenceMode;
|
|
47
|
+
loadProgress: number;
|
|
48
|
+
error: string | null;
|
|
49
|
+
send: () => void;
|
|
50
|
+
clear: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
declare function KanhaBot({ modelUrl, modelLib, modelSize, systemPrompt, temperature, maxTokens, contextWindowSize, botName, welcomeMessage, suggestions, theme, }: KanhaBotProps): react_jsx_runtime.JSX.Element;
|
|
54
|
+
|
|
55
|
+
declare function useKanhaChat(config: KanhaChatConfig): KanhaChatReturn;
|
|
56
|
+
|
|
57
|
+
export { type InferenceMode, KanhaBot, type KanhaBotProps, type KanhaBotTheme, type KanhaChatConfig, type KanhaChatReturn, type Message, useKanhaChat };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import ReactMarkdown from 'react-markdown';
|
|
3
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/KanhaBot.tsx
|
|
6
|
+
var MODEL_LIB_PREFIX = "https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_80";
|
|
7
|
+
var WASM_BY_SIZE = {
|
|
8
|
+
small: `${MODEL_LIB_PREFIX}/Qwen3-0.6B-q4f16_1-ctx4k_cs1k-webgpu.wasm`,
|
|
9
|
+
medium: `${MODEL_LIB_PREFIX}/Qwen3-1.7B-q4f16_1-ctx4k_cs1k-webgpu.wasm`,
|
|
10
|
+
large: `${MODEL_LIB_PREFIX}/Qwen3-4B-q4f16_1-ctx4k_cs1k-webgpu.wasm`
|
|
11
|
+
};
|
|
12
|
+
function resolveModelLib(config) {
|
|
13
|
+
if (config.modelLib) return config.modelLib;
|
|
14
|
+
const size = config.modelSize ?? "small";
|
|
15
|
+
const lib = WASM_BY_SIZE[size];
|
|
16
|
+
if (!lib) throw new Error(`Unknown model size "${size}". Provide modelLib explicitly.`);
|
|
17
|
+
return lib;
|
|
18
|
+
}
|
|
19
|
+
function toWebLLMModelUrl(modelUrl) {
|
|
20
|
+
let url = modelUrl.endsWith("/") ? modelUrl.slice(0, -1) : modelUrl;
|
|
21
|
+
if (!url.includes("/resolve/")) {
|
|
22
|
+
url += "/resolve/v1";
|
|
23
|
+
}
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
async function checkWebGPU() {
|
|
27
|
+
try {
|
|
28
|
+
const nav = navigator;
|
|
29
|
+
if (!nav.gpu) return false;
|
|
30
|
+
const adapter = await nav.gpu.requestAdapter();
|
|
31
|
+
if (!adapter) return false;
|
|
32
|
+
const device = await adapter.requestDevice();
|
|
33
|
+
return !!device;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function stripThinkTokens(text) {
|
|
39
|
+
let cleaned = text.replace(/<think>[\s\S]*?<\/think>/g, "");
|
|
40
|
+
cleaned = cleaned.replace(/<think>[\s\S]*$/g, "");
|
|
41
|
+
return cleaned.trimStart();
|
|
42
|
+
}
|
|
43
|
+
function useKanhaChat(config) {
|
|
44
|
+
const [messages, setMessages] = useState([]);
|
|
45
|
+
const [input, setInput] = useState("");
|
|
46
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
47
|
+
const [isThinking, setIsThinking] = useState(false);
|
|
48
|
+
const [mode, setMode] = useState("detecting");
|
|
49
|
+
const [loadProgress, setLoadProgress] = useState(0);
|
|
50
|
+
const [error, setError] = useState(null);
|
|
51
|
+
const engineRef = useRef(null);
|
|
52
|
+
const configRef = useRef(config);
|
|
53
|
+
configRef.current = config;
|
|
54
|
+
const updateAssistantMessage = useCallback((text) => {
|
|
55
|
+
const cleaned = stripThinkTokens(text);
|
|
56
|
+
if (!cleaned) {
|
|
57
|
+
setIsThinking(true);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setIsThinking(false);
|
|
61
|
+
setMessages((prev) => {
|
|
62
|
+
const msgs = [...prev];
|
|
63
|
+
if (msgs[msgs.length - 1]?.role === "assistant") {
|
|
64
|
+
msgs[msgs.length - 1] = { role: "assistant", content: cleaned };
|
|
65
|
+
} else {
|
|
66
|
+
msgs.push({ role: "assistant", content: cleaned });
|
|
67
|
+
}
|
|
68
|
+
return msgs;
|
|
69
|
+
});
|
|
70
|
+
}, []);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (typeof window === "undefined") return;
|
|
73
|
+
let cancelled = false;
|
|
74
|
+
const init = async () => {
|
|
75
|
+
setMode("detecting");
|
|
76
|
+
const hasWebGPU = await checkWebGPU();
|
|
77
|
+
if (!hasWebGPU) {
|
|
78
|
+
setMode("error");
|
|
79
|
+
setError("WebGPU is not supported in this browser. Try Chrome 113+ or Edge 113+.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (cancelled) return;
|
|
83
|
+
setMode("loading");
|
|
84
|
+
try {
|
|
85
|
+
const webllm = await import('@mlc-ai/web-llm');
|
|
86
|
+
const modelId = "kanha-custom-model";
|
|
87
|
+
const modelLib = resolveModelLib(configRef.current);
|
|
88
|
+
const modelUrl = toWebLLMModelUrl(configRef.current.modelUrl);
|
|
89
|
+
const engine = await webllm.CreateMLCEngine(modelId, {
|
|
90
|
+
appConfig: {
|
|
91
|
+
model_list: [
|
|
92
|
+
{
|
|
93
|
+
model: modelUrl,
|
|
94
|
+
model_id: modelId,
|
|
95
|
+
model_lib: modelLib,
|
|
96
|
+
overrides: {
|
|
97
|
+
context_window_size: configRef.current.contextWindowSize ?? 4096
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
initProgressCallback: (report) => {
|
|
103
|
+
if (!cancelled) setLoadProgress(Math.round(report.progress * 100));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
if (cancelled) return;
|
|
107
|
+
engineRef.current = engine;
|
|
108
|
+
setMode("ready");
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (cancelled) return;
|
|
111
|
+
setMode("error");
|
|
112
|
+
setError(
|
|
113
|
+
err instanceof Error ? err.message : "Failed to load AI model."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
init();
|
|
118
|
+
return () => {
|
|
119
|
+
cancelled = true;
|
|
120
|
+
};
|
|
121
|
+
}, [config.modelUrl, config.modelLib, config.modelSize]);
|
|
122
|
+
const send = useCallback(async () => {
|
|
123
|
+
if (!input.trim() || mode !== "ready" || isLoading || !engineRef.current) return;
|
|
124
|
+
const userMsg = { role: "user", content: input.trim() };
|
|
125
|
+
const allMessages = [...messages, userMsg];
|
|
126
|
+
setMessages(allMessages);
|
|
127
|
+
setInput("");
|
|
128
|
+
setIsLoading(true);
|
|
129
|
+
setIsThinking(true);
|
|
130
|
+
setError(null);
|
|
131
|
+
try {
|
|
132
|
+
const systemMessages = configRef.current.systemPrompt ? [{ role: "system", content: configRef.current.systemPrompt }] : [];
|
|
133
|
+
const completion = await engineRef.current.chat.completions.create({
|
|
134
|
+
messages: [...systemMessages, ...allMessages],
|
|
135
|
+
temperature: configRef.current.temperature ?? 0.7,
|
|
136
|
+
max_tokens: configRef.current.maxTokens ?? 1024,
|
|
137
|
+
stream: true
|
|
138
|
+
});
|
|
139
|
+
let text = "";
|
|
140
|
+
for await (const chunk of completion) {
|
|
141
|
+
const delta = chunk.choices[0]?.delta?.content || "";
|
|
142
|
+
text += delta;
|
|
143
|
+
updateAssistantMessage(text);
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
setError("Failed to generate response. Please try again.");
|
|
147
|
+
setMessages((prev) => [
|
|
148
|
+
...prev,
|
|
149
|
+
{ role: "assistant", content: "Sorry, I encountered an error. Please try again." }
|
|
150
|
+
]);
|
|
151
|
+
} finally {
|
|
152
|
+
setIsLoading(false);
|
|
153
|
+
setIsThinking(false);
|
|
154
|
+
}
|
|
155
|
+
}, [input, mode, isLoading, messages, updateAssistantMessage]);
|
|
156
|
+
const clear = useCallback(() => {
|
|
157
|
+
setMessages([]);
|
|
158
|
+
setError(null);
|
|
159
|
+
}, []);
|
|
160
|
+
return {
|
|
161
|
+
messages,
|
|
162
|
+
input,
|
|
163
|
+
setInput,
|
|
164
|
+
isLoading,
|
|
165
|
+
isThinking,
|
|
166
|
+
mode,
|
|
167
|
+
loadProgress,
|
|
168
|
+
error,
|
|
169
|
+
send,
|
|
170
|
+
clear
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/styles.ts
|
|
175
|
+
function fabStyle(primaryColor, position) {
|
|
176
|
+
return {
|
|
177
|
+
position: "fixed",
|
|
178
|
+
bottom: 24,
|
|
179
|
+
[position === "bottom-right" ? "right" : "left"]: 24,
|
|
180
|
+
width: 56,
|
|
181
|
+
height: 56,
|
|
182
|
+
borderRadius: "50%",
|
|
183
|
+
background: primaryColor,
|
|
184
|
+
color: "#fff",
|
|
185
|
+
border: "none",
|
|
186
|
+
cursor: "pointer",
|
|
187
|
+
display: "flex",
|
|
188
|
+
alignItems: "center",
|
|
189
|
+
justifyContent: "center",
|
|
190
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
191
|
+
zIndex: 9999,
|
|
192
|
+
transition: "transform 0.15s ease, box-shadow 0.15s ease"
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function panelStyle(position) {
|
|
196
|
+
return {
|
|
197
|
+
position: "fixed",
|
|
198
|
+
bottom: 96,
|
|
199
|
+
[position === "bottom-right" ? "right" : "left"]: 24,
|
|
200
|
+
width: 384,
|
|
201
|
+
maxWidth: "calc(100vw - 48px)",
|
|
202
|
+
maxHeight: "calc(100vh - 120px)",
|
|
203
|
+
borderRadius: 16,
|
|
204
|
+
overflow: "hidden",
|
|
205
|
+
display: "flex",
|
|
206
|
+
flexDirection: "column",
|
|
207
|
+
boxShadow: "0 8px 30px rgba(0,0,0,0.12)",
|
|
208
|
+
border: "1px solid #e5e7eb",
|
|
209
|
+
background: "#ffffff",
|
|
210
|
+
zIndex: 9999,
|
|
211
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
var headerStyle = {
|
|
215
|
+
padding: "16px",
|
|
216
|
+
color: "#fff",
|
|
217
|
+
display: "flex",
|
|
218
|
+
alignItems: "center",
|
|
219
|
+
justifyContent: "space-between"
|
|
220
|
+
};
|
|
221
|
+
var headerTitleStyle = {
|
|
222
|
+
fontWeight: 600,
|
|
223
|
+
fontSize: 15,
|
|
224
|
+
margin: 0
|
|
225
|
+
};
|
|
226
|
+
var headerSubtitleStyle = {
|
|
227
|
+
fontSize: 12,
|
|
228
|
+
opacity: 0.85,
|
|
229
|
+
margin: "4px 0 0"
|
|
230
|
+
};
|
|
231
|
+
var messagesContainerStyle = {
|
|
232
|
+
flex: 1,
|
|
233
|
+
overflowY: "auto",
|
|
234
|
+
padding: 16,
|
|
235
|
+
display: "flex",
|
|
236
|
+
flexDirection: "column",
|
|
237
|
+
gap: 12,
|
|
238
|
+
minHeight: 300,
|
|
239
|
+
maxHeight: 384,
|
|
240
|
+
background: "#fafafa"
|
|
241
|
+
};
|
|
242
|
+
var emptyStateStyle = {
|
|
243
|
+
display: "flex",
|
|
244
|
+
flexDirection: "column",
|
|
245
|
+
alignItems: "center",
|
|
246
|
+
justifyContent: "center",
|
|
247
|
+
height: "100%",
|
|
248
|
+
textAlign: "center",
|
|
249
|
+
color: "#6b7280",
|
|
250
|
+
fontSize: 14,
|
|
251
|
+
padding: 16
|
|
252
|
+
};
|
|
253
|
+
function userBubbleStyle(primaryColor) {
|
|
254
|
+
return {
|
|
255
|
+
maxWidth: "80%",
|
|
256
|
+
padding: "8px 14px",
|
|
257
|
+
borderRadius: 16,
|
|
258
|
+
background: primaryColor,
|
|
259
|
+
color: "#fff",
|
|
260
|
+
fontSize: 14,
|
|
261
|
+
lineHeight: 1.5,
|
|
262
|
+
alignSelf: "flex-end",
|
|
263
|
+
wordBreak: "break-word"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
var assistantBubbleStyle = {
|
|
267
|
+
maxWidth: "80%",
|
|
268
|
+
padding: "8px 14px",
|
|
269
|
+
borderRadius: 16,
|
|
270
|
+
background: "#fff",
|
|
271
|
+
color: "#1f2937",
|
|
272
|
+
fontSize: 14,
|
|
273
|
+
lineHeight: 1.5,
|
|
274
|
+
border: "1px solid #e5e7eb",
|
|
275
|
+
alignSelf: "flex-start",
|
|
276
|
+
wordBreak: "break-word"
|
|
277
|
+
};
|
|
278
|
+
var inputBarStyle = {
|
|
279
|
+
display: "flex",
|
|
280
|
+
gap: 8,
|
|
281
|
+
padding: "12px 16px",
|
|
282
|
+
borderTop: "1px solid #e5e7eb",
|
|
283
|
+
background: "#fff"
|
|
284
|
+
};
|
|
285
|
+
var textareaStyle = {
|
|
286
|
+
flex: 1,
|
|
287
|
+
padding: "8px 12px",
|
|
288
|
+
border: "1px solid #e5e7eb",
|
|
289
|
+
borderRadius: 10,
|
|
290
|
+
fontSize: 14,
|
|
291
|
+
resize: "none",
|
|
292
|
+
overflow: "hidden",
|
|
293
|
+
outline: "none",
|
|
294
|
+
fontFamily: "inherit",
|
|
295
|
+
minHeight: 40,
|
|
296
|
+
maxHeight: 120,
|
|
297
|
+
lineHeight: 1.5
|
|
298
|
+
};
|
|
299
|
+
function sendButtonStyle(primaryColor) {
|
|
300
|
+
return {
|
|
301
|
+
padding: "8px 14px",
|
|
302
|
+
background: primaryColor,
|
|
303
|
+
color: "#fff",
|
|
304
|
+
border: "none",
|
|
305
|
+
borderRadius: 10,
|
|
306
|
+
cursor: "pointer",
|
|
307
|
+
display: "flex",
|
|
308
|
+
alignItems: "center",
|
|
309
|
+
justifyContent: "center",
|
|
310
|
+
flexShrink: 0,
|
|
311
|
+
transition: "opacity 0.15s ease"
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
var iconButtonStyle = {
|
|
315
|
+
background: "none",
|
|
316
|
+
border: "none",
|
|
317
|
+
color: "inherit",
|
|
318
|
+
cursor: "pointer",
|
|
319
|
+
padding: 4,
|
|
320
|
+
borderRadius: 8,
|
|
321
|
+
display: "flex",
|
|
322
|
+
alignItems: "center",
|
|
323
|
+
justifyContent: "center",
|
|
324
|
+
opacity: 0.8,
|
|
325
|
+
transition: "opacity 0.15s ease"
|
|
326
|
+
};
|
|
327
|
+
function suggestionButtonStyle(primaryColor) {
|
|
328
|
+
return {
|
|
329
|
+
fontSize: 13,
|
|
330
|
+
padding: "8px 12px",
|
|
331
|
+
borderRadius: 10,
|
|
332
|
+
border: "1px solid #e5e7eb",
|
|
333
|
+
background: "#fff",
|
|
334
|
+
color: "#374151",
|
|
335
|
+
cursor: "pointer",
|
|
336
|
+
textAlign: "left",
|
|
337
|
+
width: "100%",
|
|
338
|
+
transition: "border-color 0.15s ease"
|
|
339
|
+
// hover handled inline
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
var progressBarContainer = {
|
|
343
|
+
width: "60%",
|
|
344
|
+
height: 4,
|
|
345
|
+
borderRadius: 2,
|
|
346
|
+
background: "rgba(255,255,255,0.3)",
|
|
347
|
+
marginTop: 8,
|
|
348
|
+
overflow: "hidden"
|
|
349
|
+
};
|
|
350
|
+
function progressBarFill(progress, primaryColor) {
|
|
351
|
+
return {
|
|
352
|
+
width: `${progress}%`,
|
|
353
|
+
height: "100%",
|
|
354
|
+
borderRadius: 2,
|
|
355
|
+
background: primaryColor,
|
|
356
|
+
transition: "width 0.3s ease"
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
var ChatIcon = () => /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
|
|
360
|
+
var CloseIcon = () => /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
361
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
362
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
363
|
+
] });
|
|
364
|
+
var SendIcon = () => /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
365
|
+
/* @__PURE__ */ jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
366
|
+
/* @__PURE__ */ jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
367
|
+
] });
|
|
368
|
+
var TrashIcon = () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
369
|
+
/* @__PURE__ */ jsx("polyline", { points: "3 6 5 6 21 6" }),
|
|
370
|
+
/* @__PURE__ */ jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
|
|
371
|
+
] });
|
|
372
|
+
var Spinner = ({ size = 18 }) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { animation: "kanha-spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
|
|
373
|
+
var spinKeyframes = `@keyframes kanha-spin { to { transform: rotate(360deg); } }`;
|
|
374
|
+
var bounceKeyframes = `
|
|
375
|
+
@keyframes kanha-bounce {
|
|
376
|
+
0%, 80%, 100% { transform: translateY(0); }
|
|
377
|
+
40% { transform: translateY(-4px); }
|
|
378
|
+
}`;
|
|
379
|
+
function ThinkingDots() {
|
|
380
|
+
const dotStyle = (delay) => ({
|
|
381
|
+
width: 6,
|
|
382
|
+
height: 6,
|
|
383
|
+
borderRadius: "50%",
|
|
384
|
+
background: "#9ca3af",
|
|
385
|
+
animation: `kanha-bounce 1.2s ease-in-out ${delay}ms infinite`
|
|
386
|
+
});
|
|
387
|
+
return /* @__PURE__ */ jsxs("span", { style: { display: "flex", gap: 3, alignItems: "center" }, children: [
|
|
388
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle(0) }),
|
|
389
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle(150) }),
|
|
390
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle(300) }),
|
|
391
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#9ca3af", marginLeft: 6 }, children: "Thinking" })
|
|
392
|
+
] });
|
|
393
|
+
}
|
|
394
|
+
function KanhaBot({
|
|
395
|
+
modelUrl,
|
|
396
|
+
modelLib,
|
|
397
|
+
modelSize,
|
|
398
|
+
systemPrompt,
|
|
399
|
+
temperature,
|
|
400
|
+
maxTokens,
|
|
401
|
+
contextWindowSize,
|
|
402
|
+
botName = "AI Assistant",
|
|
403
|
+
welcomeMessage = "Ask me anything!",
|
|
404
|
+
suggestions = [],
|
|
405
|
+
theme = {}
|
|
406
|
+
}) {
|
|
407
|
+
const primaryColor = theme.primaryColor ?? "#0d9488";
|
|
408
|
+
const position = theme.position ?? "bottom-right";
|
|
409
|
+
const {
|
|
410
|
+
messages,
|
|
411
|
+
input,
|
|
412
|
+
setInput,
|
|
413
|
+
isLoading,
|
|
414
|
+
isThinking,
|
|
415
|
+
mode,
|
|
416
|
+
loadProgress,
|
|
417
|
+
error,
|
|
418
|
+
send,
|
|
419
|
+
clear
|
|
420
|
+
} = useKanhaChat({
|
|
421
|
+
modelUrl,
|
|
422
|
+
modelLib,
|
|
423
|
+
modelSize,
|
|
424
|
+
systemPrompt,
|
|
425
|
+
temperature,
|
|
426
|
+
maxTokens,
|
|
427
|
+
contextWindowSize
|
|
428
|
+
});
|
|
429
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
430
|
+
const messagesEndRef = useRef(null);
|
|
431
|
+
const textareaRef = useRef(null);
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
434
|
+
}, [messages]);
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
if (textareaRef.current) {
|
|
437
|
+
textareaRef.current.style.height = "auto";
|
|
438
|
+
textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 120) + "px";
|
|
439
|
+
}
|
|
440
|
+
}, [input]);
|
|
441
|
+
const handleKeyPress = (e) => {
|
|
442
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
443
|
+
e.preventDefault();
|
|
444
|
+
send();
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const isReady = mode === "ready";
|
|
448
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
449
|
+
/* @__PURE__ */ jsxs("style", { children: [
|
|
450
|
+
spinKeyframes,
|
|
451
|
+
bounceKeyframes
|
|
452
|
+
] }),
|
|
453
|
+
isOpen && /* @__PURE__ */ jsxs("div", { style: panelStyle(position), children: [
|
|
454
|
+
/* @__PURE__ */ jsxs("div", { style: { ...headerStyle, background: primaryColor }, children: [
|
|
455
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
456
|
+
/* @__PURE__ */ jsx("p", { style: headerTitleStyle, children: botName }),
|
|
457
|
+
/* @__PURE__ */ jsx("p", { style: headerSubtitleStyle, children: isReady ? welcomeMessage : mode === "loading" ? `Loading AI model (${loadProgress}%)` : mode === "error" ? "Failed to load" : "Detecting capabilities..." }),
|
|
458
|
+
mode === "loading" && /* @__PURE__ */ jsx("div", { style: progressBarContainer, children: /* @__PURE__ */ jsx("div", { style: progressBarFill(loadProgress, "#fff") }) })
|
|
459
|
+
] }),
|
|
460
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4 }, children: [
|
|
461
|
+
messages.length > 0 && /* @__PURE__ */ jsx("button", { onClick: clear, style: iconButtonStyle, "aria-label": "Clear chat", children: /* @__PURE__ */ jsx(TrashIcon, {}) }),
|
|
462
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setIsOpen(false), style: iconButtonStyle, "aria-label": "Close", children: /* @__PURE__ */ jsx(CloseIcon, {}) })
|
|
463
|
+
] })
|
|
464
|
+
] }),
|
|
465
|
+
error && /* @__PURE__ */ jsx("div", { style: { padding: "10px 16px", background: "#fef2f2", borderBottom: "1px solid #fecaca", fontSize: 13, color: "#b91c1c" }, children: error }),
|
|
466
|
+
/* @__PURE__ */ jsx("div", { style: messagesContainerStyle, children: messages.length === 0 ? /* @__PURE__ */ jsx("div", { style: emptyStateStyle, children: isReady ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
467
|
+
/* @__PURE__ */ jsx(ChatIcon, {}),
|
|
468
|
+
/* @__PURE__ */ jsx("p", { style: { marginTop: 12, fontWeight: 500, color: "#374151" }, children: welcomeMessage }),
|
|
469
|
+
suggestions.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6, width: "100%", marginTop: 16 }, children: suggestions.map((text) => /* @__PURE__ */ jsx(
|
|
470
|
+
"button",
|
|
471
|
+
{
|
|
472
|
+
onClick: () => {
|
|
473
|
+
setInput(text);
|
|
474
|
+
setTimeout(send, 0);
|
|
475
|
+
},
|
|
476
|
+
style: suggestionButtonStyle(),
|
|
477
|
+
onMouseEnter: (e) => {
|
|
478
|
+
e.target.style.borderColor = primaryColor;
|
|
479
|
+
},
|
|
480
|
+
onMouseLeave: (e) => {
|
|
481
|
+
e.target.style.borderColor = "#e5e7eb";
|
|
482
|
+
},
|
|
483
|
+
children: text
|
|
484
|
+
},
|
|
485
|
+
text
|
|
486
|
+
)) })
|
|
487
|
+
] }) : mode === "loading" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
488
|
+
/* @__PURE__ */ jsx(Spinner, { size: 32 }),
|
|
489
|
+
/* @__PURE__ */ jsxs("p", { style: { marginTop: 8 }, children: [
|
|
490
|
+
"Loading AI model (",
|
|
491
|
+
loadProgress,
|
|
492
|
+
"%)"
|
|
493
|
+
] }),
|
|
494
|
+
/* @__PURE__ */ jsx("p", { style: { fontSize: 12, marginTop: 4 }, children: "First load takes about a minute" })
|
|
495
|
+
] }) : mode === "error" ? /* @__PURE__ */ jsx("p", { children: error }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
496
|
+
/* @__PURE__ */ jsx(Spinner, { size: 32 }),
|
|
497
|
+
/* @__PURE__ */ jsx("p", { style: { marginTop: 8 }, children: "Detecting browser capabilities..." })
|
|
498
|
+
] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
499
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx(
|
|
500
|
+
"div",
|
|
501
|
+
{
|
|
502
|
+
style: {
|
|
503
|
+
display: "flex",
|
|
504
|
+
justifyContent: msg.role === "user" ? "flex-end" : "flex-start"
|
|
505
|
+
},
|
|
506
|
+
children: /* @__PURE__ */ jsx("div", { style: msg.role === "user" ? userBubbleStyle(primaryColor) : assistantBubbleStyle, children: msg.role === "assistant" ? /* @__PURE__ */ jsx(ReactMarkdown, { children: msg.content }) : /* @__PURE__ */ jsx("span", { style: { whiteSpace: "pre-wrap" }, children: msg.content }) })
|
|
507
|
+
},
|
|
508
|
+
i
|
|
509
|
+
)),
|
|
510
|
+
isLoading && (isThinking || messages[messages.length - 1]?.role !== "assistant") && /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ jsx("div", { style: assistantBubbleStyle, children: isThinking ? /* @__PURE__ */ jsx(ThinkingDots, {}) : /* @__PURE__ */ jsx(Spinner, { size: 16 }) }) }),
|
|
511
|
+
/* @__PURE__ */ jsx("div", { ref: messagesEndRef })
|
|
512
|
+
] }) }),
|
|
513
|
+
/* @__PURE__ */ jsxs("div", { style: inputBarStyle, children: [
|
|
514
|
+
/* @__PURE__ */ jsx(
|
|
515
|
+
"textarea",
|
|
516
|
+
{
|
|
517
|
+
ref: textareaRef,
|
|
518
|
+
value: input,
|
|
519
|
+
onChange: (e) => setInput(e.target.value),
|
|
520
|
+
onKeyDown: handleKeyPress,
|
|
521
|
+
placeholder: isReady ? "Type your message..." : "Waiting for model...",
|
|
522
|
+
disabled: !isReady || isLoading,
|
|
523
|
+
rows: 1,
|
|
524
|
+
style: {
|
|
525
|
+
...textareaStyle,
|
|
526
|
+
opacity: !isReady ? 0.5 : 1
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
),
|
|
530
|
+
/* @__PURE__ */ jsx(
|
|
531
|
+
"button",
|
|
532
|
+
{
|
|
533
|
+
onClick: send,
|
|
534
|
+
disabled: !isReady || isLoading || !input.trim(),
|
|
535
|
+
style: {
|
|
536
|
+
...sendButtonStyle(primaryColor),
|
|
537
|
+
opacity: !isReady || isLoading || !input.trim() ? 0.5 : 1,
|
|
538
|
+
cursor: !isReady || isLoading || !input.trim() ? "not-allowed" : "pointer"
|
|
539
|
+
},
|
|
540
|
+
"aria-label": "Send message",
|
|
541
|
+
children: isLoading ? /* @__PURE__ */ jsx(Spinner, { size: 18 }) : /* @__PURE__ */ jsx(SendIcon, {})
|
|
542
|
+
}
|
|
543
|
+
)
|
|
544
|
+
] })
|
|
545
|
+
] }),
|
|
546
|
+
/* @__PURE__ */ jsx(
|
|
547
|
+
"button",
|
|
548
|
+
{
|
|
549
|
+
onClick: () => setIsOpen(!isOpen),
|
|
550
|
+
style: fabStyle(primaryColor, position),
|
|
551
|
+
"aria-label": "Toggle chat",
|
|
552
|
+
onMouseEnter: (e) => {
|
|
553
|
+
e.target.style.transform = "scale(1.05)";
|
|
554
|
+
},
|
|
555
|
+
onMouseLeave: (e) => {
|
|
556
|
+
e.target.style.transform = "scale(1)";
|
|
557
|
+
},
|
|
558
|
+
children: isOpen ? /* @__PURE__ */ jsx(CloseIcon, {}) : /* @__PURE__ */ jsx(ChatIcon, {})
|
|
559
|
+
}
|
|
560
|
+
)
|
|
561
|
+
] });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export { KanhaBot, useKanhaChat };
|
|
565
|
+
//# sourceMappingURL=index.js.map
|
|
566
|
+
//# sourceMappingURL=index.js.map
|