@yak-io/react 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/LICENSE +36 -0
- package/dist/YakProvider.d.ts +112 -0
- package/dist/YakProvider.d.ts.map +1 -0
- package/dist/YakProvider.js +171 -0
- package/dist/YakWidget.d.ts +16 -0
- package/dist/YakWidget.d.ts.map +1 -0
- package/dist/YakWidget.js +385 -0
- package/dist/context.d.ts +57 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +30 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/internal/logger.d.ts +12 -0
- package/dist/internal/logger.d.ts.map +1 -0
- package/dist/internal/logger.js +44 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Yak Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yak. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary property of Yak and are protected by copyright law.
|
|
7
|
+
|
|
8
|
+
GRANT OF LICENSE:
|
|
9
|
+
Subject to the terms of this license and your valid subscription or agreement
|
|
10
|
+
with Yak, you are granted a limited, non-exclusive, non-transferable license
|
|
11
|
+
to use the Software solely for integrating the Yak chatbot widget into your
|
|
12
|
+
applications as intended and documented.
|
|
13
|
+
|
|
14
|
+
RESTRICTIONS:
|
|
15
|
+
You may NOT:
|
|
16
|
+
- Modify, adapt, alter, translate, or create derivative works of the Software
|
|
17
|
+
- Reverse engineer, disassemble, decompile, or otherwise attempt to derive
|
|
18
|
+
the source code of the Software
|
|
19
|
+
- Redistribute, sublicense, lease, rent, or lend the Software to third parties
|
|
20
|
+
- Remove or alter any proprietary notices, labels, or marks on the Software
|
|
21
|
+
- Use the Software for any purpose other than as expressly permitted herein
|
|
22
|
+
|
|
23
|
+
NO WARRANTY:
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL YAK
|
|
27
|
+
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
28
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
29
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
30
|
+
|
|
31
|
+
TERMINATION:
|
|
32
|
+
This license is effective until terminated. Your rights under this license
|
|
33
|
+
will terminate automatically without notice if you fail to comply with any
|
|
34
|
+
of its terms.
|
|
35
|
+
|
|
36
|
+
For licensing inquiries, contact: support@yak.io
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type Theme, type ChatConfigProvider, type ToolCallHandler, type GraphQLSchemaHandler, type RESTSchemaHandler } from "@yak-io/javascript";
|
|
3
|
+
/**
|
|
4
|
+
* Props for YakProvider
|
|
5
|
+
*/
|
|
6
|
+
export type YakProviderProps = {
|
|
7
|
+
/** App identifier in the yak SaaS */
|
|
8
|
+
appId: string;
|
|
9
|
+
/**
|
|
10
|
+
* Provider function for chat configuration (routes + tools).
|
|
11
|
+
* The consuming platform decides how to get the config (static, fetch, etc.)
|
|
12
|
+
* Called when the widget is opened.
|
|
13
|
+
*
|
|
14
|
+
* @example Static config
|
|
15
|
+
* ```tsx
|
|
16
|
+
* getConfig={() => ({
|
|
17
|
+
* routes: { routes: [...], generated_at: "..." },
|
|
18
|
+
* tools: { tools: [...], generated_at: "..." },
|
|
19
|
+
* })}
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example Fetch from server
|
|
23
|
+
* ```tsx
|
|
24
|
+
* getConfig={async () => {
|
|
25
|
+
* const res = await fetch("/api/yak/config");
|
|
26
|
+
* return res.json();
|
|
27
|
+
* }}
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
getConfig?: ChatConfigProvider;
|
|
31
|
+
/**
|
|
32
|
+
* Handler for tool calls from the chat widget.
|
|
33
|
+
* The consuming platform decides how to execute (browser, server fetch, etc.)
|
|
34
|
+
*
|
|
35
|
+
* @example Browser-only execution
|
|
36
|
+
* ```tsx
|
|
37
|
+
* onToolCall={async (name, args) => {
|
|
38
|
+
* if (name === "ui.scrollTo") {
|
|
39
|
+
* document.getElementById((args as {id: string}).id)?.scrollIntoView();
|
|
40
|
+
* return { success: true };
|
|
41
|
+
* }
|
|
42
|
+
* throw new Error(`Unknown tool: ${name}`);
|
|
43
|
+
* }}
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Server delegation
|
|
47
|
+
* ```tsx
|
|
48
|
+
* onToolCall={async (name, args) => {
|
|
49
|
+
* const res = await fetch("/api/yak/tools", {
|
|
50
|
+
* method: "POST",
|
|
51
|
+
* headers: { "Content-Type": "application/json" },
|
|
52
|
+
* body: JSON.stringify({ name, args }),
|
|
53
|
+
* });
|
|
54
|
+
* const data = await res.json();
|
|
55
|
+
* if (!data.ok) throw new Error(data.error);
|
|
56
|
+
* return data.result;
|
|
57
|
+
* }}
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
onToolCall?: ToolCallHandler;
|
|
61
|
+
/**
|
|
62
|
+
* Handler for GraphQL schema tool calls.
|
|
63
|
+
* Called when the LLM generates a GraphQL operation based on provided schema context.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* onGraphQLSchemaCall={async (schemaName, request) => {
|
|
68
|
+
* const response = await fetch("/graphql", {
|
|
69
|
+
* method: "POST",
|
|
70
|
+
* headers: { "Content-Type": "application/json" },
|
|
71
|
+
* body: JSON.stringify(request),
|
|
72
|
+
* });
|
|
73
|
+
* return response.json();
|
|
74
|
+
* }}
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
onGraphQLSchemaCall?: GraphQLSchemaHandler;
|
|
78
|
+
/**
|
|
79
|
+
* Handler for REST/OpenAPI schema tool calls.
|
|
80
|
+
* Called when the LLM generates a REST request based on provided OpenAPI schema context.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```tsx
|
|
84
|
+
* onRESTSchemaCall={async (schemaName, request) => {
|
|
85
|
+
* const url = new URL(request.path, "https://api.example.com");
|
|
86
|
+
* if (request.query) {
|
|
87
|
+
* Object.entries(request.query).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
88
|
+
* }
|
|
89
|
+
* const response = await fetch(url, {
|
|
90
|
+
* method: request.method,
|
|
91
|
+
* headers: request.body ? { "Content-Type": "application/json" } : undefined,
|
|
92
|
+
* body: request.body ? JSON.stringify(request.body) : undefined,
|
|
93
|
+
* });
|
|
94
|
+
* return response.json();
|
|
95
|
+
* }}
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
onRESTSchemaCall?: RESTSchemaHandler;
|
|
99
|
+
/** Optional theme configuration */
|
|
100
|
+
theme?: Theme;
|
|
101
|
+
/** Optional redirect handler (defaults to window.location.assign) */
|
|
102
|
+
onRedirect?: (path: string) => void;
|
|
103
|
+
/** Disable the restart session button in the header */
|
|
104
|
+
disableRestartButton?: boolean;
|
|
105
|
+
/** Children components */
|
|
106
|
+
children: React.ReactNode;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* YakProvider sets up the context and message handling for the yak widget.
|
|
110
|
+
*/
|
|
111
|
+
export declare function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, children, }: YakProviderProps): React.JSX.Element;
|
|
112
|
+
//# sourceMappingURL=YakProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YakProvider.d.ts","sourceRoot":"","sources":["../src/YakProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,EAEL,KAAK,KAAK,EAGV,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,mCAAmC;IACnC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0BAA0B;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,EACL,UAAU,EACV,oBAAoB,EACpB,QAAQ,GACT,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAgMtC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
4
|
+
import { YakContext } from "./context.js";
|
|
5
|
+
import { YakClient, } from "@yak-io/javascript";
|
|
6
|
+
import { logger } from "./internal/logger.js";
|
|
7
|
+
/**
|
|
8
|
+
* YakProvider sets up the context and message handling for the yak widget.
|
|
9
|
+
*/
|
|
10
|
+
export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, children, }) {
|
|
11
|
+
const [iframeWindow, setIframeWindow] = useState(null);
|
|
12
|
+
const [chatConfig, setChatConfig] = useState(null);
|
|
13
|
+
const [isWidgetOpen, setIsWidgetOpen] = useState(false);
|
|
14
|
+
const [pendingPrompt, setPendingPrompt] = useState(null);
|
|
15
|
+
const [isIframeReady, setIsIframeReady] = useState(false);
|
|
16
|
+
// Initialize YakClient
|
|
17
|
+
const clientRef = useRef(null);
|
|
18
|
+
if (!clientRef.current) {
|
|
19
|
+
clientRef.current = new YakClient({
|
|
20
|
+
appId,
|
|
21
|
+
onToolCall,
|
|
22
|
+
onGraphQLSchemaCall,
|
|
23
|
+
onRESTSchemaCall,
|
|
24
|
+
theme,
|
|
25
|
+
onRedirect,
|
|
26
|
+
options: { disableRestartButton },
|
|
27
|
+
onClose: () => setIsWidgetOpen(false),
|
|
28
|
+
onReady: () => setIsIframeReady(true),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const client = clientRef.current;
|
|
32
|
+
// Get the iframe origin from the client (computed based on environment)
|
|
33
|
+
const iframeOrigin = client.getIframeOrigin();
|
|
34
|
+
// Update client config when props change
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
client.updateConfig({
|
|
37
|
+
appId,
|
|
38
|
+
onToolCall,
|
|
39
|
+
onGraphQLSchemaCall,
|
|
40
|
+
onRESTSchemaCall,
|
|
41
|
+
theme,
|
|
42
|
+
onRedirect,
|
|
43
|
+
options: { disableRestartButton },
|
|
44
|
+
chatConfig: chatConfig ?? undefined,
|
|
45
|
+
});
|
|
46
|
+
}, [appId, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, chatConfig, client]);
|
|
47
|
+
// Update client iframe window
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
client.setIframeWindow(iframeWindow);
|
|
50
|
+
}, [iframeWindow, client]);
|
|
51
|
+
// Update client widget open state
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
client.setWidgetOpen(isWidgetOpen);
|
|
54
|
+
}, [isWidgetOpen, client]);
|
|
55
|
+
// Mount/Unmount client listeners
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
client.mount();
|
|
58
|
+
return () => client.unmount();
|
|
59
|
+
}, [client]);
|
|
60
|
+
// Get chat config when widget is opened
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (typeof window === "undefined" || !isWidgetOpen || !getConfig)
|
|
63
|
+
return;
|
|
64
|
+
logger.debug("Getting chat config");
|
|
65
|
+
let cancelled = false;
|
|
66
|
+
(async () => {
|
|
67
|
+
try {
|
|
68
|
+
const config = await getConfig();
|
|
69
|
+
if (!cancelled) {
|
|
70
|
+
logger.debug(`Chat config loaded with ${config.tools?.tools.length ?? 0} tools and ${config.routes?.routes.length ?? 0} routes`);
|
|
71
|
+
setChatConfig(config);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger.warn("Error getting chat config:", err);
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
};
|
|
81
|
+
}, [getConfig, isWidgetOpen]);
|
|
82
|
+
const resolvedRedirect = useMemo(() => {
|
|
83
|
+
if (onRedirect)
|
|
84
|
+
return onRedirect;
|
|
85
|
+
if (typeof window === "undefined")
|
|
86
|
+
return undefined;
|
|
87
|
+
return (path) => {
|
|
88
|
+
window.location.assign(path);
|
|
89
|
+
};
|
|
90
|
+
}, [onRedirect]);
|
|
91
|
+
const config = useMemo(() => ({
|
|
92
|
+
appId,
|
|
93
|
+
theme,
|
|
94
|
+
chatConfig,
|
|
95
|
+
onRedirect: resolvedRedirect,
|
|
96
|
+
}), [appId, theme, chatConfig, resolvedRedirect]);
|
|
97
|
+
// Methods to get URLs from the client
|
|
98
|
+
const getIframeOrigin = useCallback(() => client.getIframeOrigin(), [client]);
|
|
99
|
+
const getEmbedUrl = useCallback(() => client.getEmbedUrl(), [client]);
|
|
100
|
+
const registerIframeWindow = useCallback((win) => {
|
|
101
|
+
logger.debug("Iframe window registered");
|
|
102
|
+
setIframeWindow(win);
|
|
103
|
+
}, []);
|
|
104
|
+
const unregisterIframeWindow = useCallback(() => {
|
|
105
|
+
logger.debug("Iframe window unregistered");
|
|
106
|
+
setIframeWindow(null);
|
|
107
|
+
}, []);
|
|
108
|
+
const sendMessage = useCallback((message) => {
|
|
109
|
+
iframeWindow?.postMessage(message, iframeOrigin);
|
|
110
|
+
}, [iframeWindow, iframeOrigin]);
|
|
111
|
+
const open = useCallback(() => {
|
|
112
|
+
setIsWidgetOpen(true);
|
|
113
|
+
}, []);
|
|
114
|
+
const close = useCallback(() => {
|
|
115
|
+
setIsWidgetOpen(false);
|
|
116
|
+
}, []);
|
|
117
|
+
const openWithPrompt = useCallback((prompt) => {
|
|
118
|
+
logger.debug("Opening widget with prompt:", prompt);
|
|
119
|
+
setPendingPrompt(prompt);
|
|
120
|
+
setIsWidgetOpen(true);
|
|
121
|
+
}, []);
|
|
122
|
+
// Effect to send pending prompt when iframe becomes ready
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (isIframeReady && pendingPrompt && iframeWindow) {
|
|
125
|
+
logger.debug("Sending pending prompt to iframe:", pendingPrompt);
|
|
126
|
+
const promptMessage = {
|
|
127
|
+
type: "yak:prompt",
|
|
128
|
+
payload: { prompt: pendingPrompt },
|
|
129
|
+
};
|
|
130
|
+
iframeWindow.postMessage(promptMessage, iframeOrigin);
|
|
131
|
+
setPendingPrompt(null);
|
|
132
|
+
}
|
|
133
|
+
}, [isIframeReady, pendingPrompt, iframeWindow, iframeOrigin]);
|
|
134
|
+
// Effect to send focus message when widget is opened
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (isWidgetOpen && iframeWindow && isIframeReady) {
|
|
137
|
+
logger.debug("Sending focus request to iframe");
|
|
138
|
+
const focusMessage = {
|
|
139
|
+
type: "yak:focus",
|
|
140
|
+
};
|
|
141
|
+
iframeWindow.postMessage(focusMessage, iframeOrigin);
|
|
142
|
+
}
|
|
143
|
+
}, [isWidgetOpen, iframeWindow, isIframeReady, iframeOrigin]);
|
|
144
|
+
const contextValue = useMemo(() => ({
|
|
145
|
+
config,
|
|
146
|
+
getIframeOrigin,
|
|
147
|
+
getEmbedUrl,
|
|
148
|
+
registerIframeWindow,
|
|
149
|
+
unregisterIframeWindow,
|
|
150
|
+
sendMessage,
|
|
151
|
+
isOpen: isWidgetOpen,
|
|
152
|
+
isIframeReady,
|
|
153
|
+
open,
|
|
154
|
+
close,
|
|
155
|
+
openWithPrompt,
|
|
156
|
+
setIsIframeReady,
|
|
157
|
+
}), [
|
|
158
|
+
config,
|
|
159
|
+
getIframeOrigin,
|
|
160
|
+
getEmbedUrl,
|
|
161
|
+
registerIframeWindow,
|
|
162
|
+
unregisterIframeWindow,
|
|
163
|
+
sendMessage,
|
|
164
|
+
isWidgetOpen,
|
|
165
|
+
isIframeReady,
|
|
166
|
+
open,
|
|
167
|
+
close,
|
|
168
|
+
openWithPrompt,
|
|
169
|
+
]);
|
|
170
|
+
return _jsx(YakContext.Provider, { value: contextValue, children: children });
|
|
171
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Props for YakWidget
|
|
4
|
+
*/
|
|
5
|
+
export type YakWidgetProps = {
|
|
6
|
+
/** Optional CSS class name for the iframe */
|
|
7
|
+
iframeClassName?: string;
|
|
8
|
+
/** Text to display next to the logo */
|
|
9
|
+
triggerLabel?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* YakWidget renders a fixed-position launcher button and iframe panel.
|
|
13
|
+
* The iframe loads immediately and shows its own skeleton while waiting for config.
|
|
14
|
+
*/
|
|
15
|
+
export declare function YakWidget({ iframeClassName, triggerLabel, }?: YakWidgetProps): React.JSX.Element;
|
|
16
|
+
//# sourceMappingURL=YakWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YakWidget.d.ts","sourceRoot":"","sources":["../src/YakWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAqU3D;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,eAAe,EACf,YAA4B,GAC7B,GAAE,cAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAwJzC"}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useEffect, useState } from "react";
|
|
4
|
+
import { useYak } from "./context.js";
|
|
5
|
+
/**
|
|
6
|
+
* All widget styles consolidated in one place
|
|
7
|
+
*/
|
|
8
|
+
function getWidgetStyles() {
|
|
9
|
+
return `
|
|
10
|
+
/* ===========================================
|
|
11
|
+
YAK WIDGET STYLES
|
|
12
|
+
=========================================== */
|
|
13
|
+
|
|
14
|
+
/* Trigger Button Base */
|
|
15
|
+
.yak-widget-trigger {
|
|
16
|
+
position: fixed;
|
|
17
|
+
z-index: 9997;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 12px;
|
|
21
|
+
border: none;
|
|
22
|
+
border-radius: 30px;
|
|
23
|
+
padding: 0 5px 0 20px;
|
|
24
|
+
height: 45px;
|
|
25
|
+
min-width: 45px;
|
|
26
|
+
width: auto;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
background-color: #000;
|
|
31
|
+
color: #fff;
|
|
32
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Trigger position variants */
|
|
36
|
+
.yak-widget-trigger[data-position="left"] {
|
|
37
|
+
bottom: 28px;
|
|
38
|
+
left: 28px;
|
|
39
|
+
flex-direction: row-reverse;
|
|
40
|
+
}
|
|
41
|
+
.yak-widget-trigger[data-position="right"] {
|
|
42
|
+
bottom: 28px;
|
|
43
|
+
right: 28px;
|
|
44
|
+
flex-direction: row;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.yak-widget-trigger-label {
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
white-space: nowrap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.yak-widget-icon-bg {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
width: 36px;
|
|
58
|
+
height: 36px;
|
|
59
|
+
border-radius: 50%;
|
|
60
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.yak-widget-icon {
|
|
64
|
+
width: 20px;
|
|
65
|
+
height: 20px;
|
|
66
|
+
color: currentColor;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Spinner animation for loading state */
|
|
70
|
+
.yak-widget-spinner {
|
|
71
|
+
width: 20px;
|
|
72
|
+
height: 20px;
|
|
73
|
+
border: 2px solid currentColor;
|
|
74
|
+
border-top-color: transparent;
|
|
75
|
+
border-radius: 50%;
|
|
76
|
+
animation: yak-widget-spin 0.8s linear infinite;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@keyframes yak-widget-spin {
|
|
80
|
+
to {
|
|
81
|
+
transform: rotate(360deg);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Loading/disabled state for trigger button */
|
|
86
|
+
.yak-widget-trigger:disabled {
|
|
87
|
+
cursor: wait;
|
|
88
|
+
opacity: 0.8;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Custom button styles for forced light mode */
|
|
92
|
+
.yak-widget-trigger.yak-widget-custom-light {
|
|
93
|
+
background-color: var(--yak-btn-light-bg, #fff);
|
|
94
|
+
color: var(--yak-btn-light-color, #000);
|
|
95
|
+
border: 1px solid var(--yak-btn-light-border, transparent);
|
|
96
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Custom button styles for forced dark mode */
|
|
100
|
+
.yak-widget-trigger.yak-widget-custom-dark {
|
|
101
|
+
background-color: var(--yak-btn-dark-bg, #000);
|
|
102
|
+
color: var(--yak-btn-dark-color, #fff);
|
|
103
|
+
border: 1px solid var(--yak-btn-dark-border, transparent);
|
|
104
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* System mode with custom light colors */
|
|
108
|
+
@media (prefers-color-scheme: light) {
|
|
109
|
+
.yak-widget-trigger[data-has-light-custom]:not(.yak-widget-dark) {
|
|
110
|
+
background-color: var(--yak-btn-light-bg, #fff);
|
|
111
|
+
color: var(--yak-btn-light-color, #000);
|
|
112
|
+
border: 1px solid var(--yak-btn-light-border, transparent);
|
|
113
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* System mode with custom dark colors */
|
|
118
|
+
@media (prefers-color-scheme: dark) {
|
|
119
|
+
.yak-widget-trigger[data-has-dark-custom]:not(.yak-widget-light) {
|
|
120
|
+
background-color: var(--yak-btn-dark-bg, #000);
|
|
121
|
+
color: var(--yak-btn-dark-color, #fff);
|
|
122
|
+
border: 1px solid var(--yak-btn-dark-border, transparent);
|
|
123
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Light mode via system preference (default styling without custom) */
|
|
128
|
+
@media (prefers-color-scheme: light) {
|
|
129
|
+
.yak-widget-trigger:not(.yak-widget-dark):not([data-has-light-custom]) {
|
|
130
|
+
background-color: #fff;
|
|
131
|
+
color: #000;
|
|
132
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
133
|
+
border: 1px solid #e5e5e5;
|
|
134
|
+
}
|
|
135
|
+
.yak-widget-trigger:not(.yak-widget-dark):not([data-has-light-custom]) .yak-widget-icon-bg {
|
|
136
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Dark mode via system preference (default styling without custom) */
|
|
141
|
+
@media (prefers-color-scheme: dark) {
|
|
142
|
+
.yak-widget-trigger:not(.yak-widget-light):not([data-has-dark-custom]) {
|
|
143
|
+
background-color: #000;
|
|
144
|
+
color: #fff;
|
|
145
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
146
|
+
border: none;
|
|
147
|
+
}
|
|
148
|
+
.yak-widget-trigger:not(.yak-widget-light):not([data-has-dark-custom]) .yak-widget-icon-bg {
|
|
149
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Forced light mode (default styling) */
|
|
154
|
+
.yak-widget-trigger.yak-widget-light:not(.yak-widget-custom-light) {
|
|
155
|
+
background-color: #fff;
|
|
156
|
+
color: #000;
|
|
157
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
158
|
+
border: 1px solid #e5e5e5;
|
|
159
|
+
}
|
|
160
|
+
.yak-widget-trigger.yak-widget-light:not(.yak-widget-custom-light) .yak-widget-icon-bg {
|
|
161
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Forced dark mode (default styling) */
|
|
165
|
+
.yak-widget-trigger.yak-widget-dark:not(.yak-widget-custom-dark) {
|
|
166
|
+
background-color: #000;
|
|
167
|
+
color: #fff;
|
|
168
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
169
|
+
border: none;
|
|
170
|
+
}
|
|
171
|
+
.yak-widget-trigger.yak-widget-dark:not(.yak-widget-custom-dark) .yak-widget-icon-bg {
|
|
172
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ===========================================
|
|
176
|
+
BACKDROP
|
|
177
|
+
=========================================== */
|
|
178
|
+
.yak-widget-backdrop {
|
|
179
|
+
position: fixed;
|
|
180
|
+
top: 0;
|
|
181
|
+
left: 0;
|
|
182
|
+
right: 0;
|
|
183
|
+
bottom: 0;
|
|
184
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
185
|
+
z-index: 9997;
|
|
186
|
+
opacity: 0;
|
|
187
|
+
visibility: hidden;
|
|
188
|
+
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
|
|
189
|
+
}
|
|
190
|
+
.yak-widget-backdrop[data-open="true"] {
|
|
191
|
+
opacity: 1;
|
|
192
|
+
visibility: visible;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* ===========================================
|
|
196
|
+
CONTAINER
|
|
197
|
+
=========================================== */
|
|
198
|
+
.yak-widget-container {
|
|
199
|
+
position: fixed;
|
|
200
|
+
width: 500px;
|
|
201
|
+
height: 600px;
|
|
202
|
+
max-width: calc(100vw - 40px);
|
|
203
|
+
max-height: calc(100vh - 120px);
|
|
204
|
+
border-radius: 15px;
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
z-index: 9998;
|
|
207
|
+
background-color: transparent;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Container position variants (chatbox mode) */
|
|
211
|
+
.yak-widget-container[data-position="left"]:not(.yak-widget-drawer) {
|
|
212
|
+
bottom: 16px;
|
|
213
|
+
left: 16px;
|
|
214
|
+
}
|
|
215
|
+
.yak-widget-container[data-position="right"]:not(.yak-widget-drawer) {
|
|
216
|
+
bottom: 16px;
|
|
217
|
+
right: 16px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* Container visibility (chatbox mode) */
|
|
221
|
+
.yak-widget-container:not(.yak-widget-drawer) {
|
|
222
|
+
display: none;
|
|
223
|
+
}
|
|
224
|
+
.yak-widget-container:not(.yak-widget-drawer)[data-open="true"] {
|
|
225
|
+
display: block;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* ===========================================
|
|
229
|
+
DRAWER MODE
|
|
230
|
+
=========================================== */
|
|
231
|
+
.yak-widget-container.yak-widget-drawer {
|
|
232
|
+
height: calc(100% - 32px);
|
|
233
|
+
max-width: 100vw;
|
|
234
|
+
max-height: none;
|
|
235
|
+
border-radius: 15px;
|
|
236
|
+
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Drawer position */
|
|
240
|
+
.yak-widget-container.yak-widget-drawer[data-position="left"] {
|
|
241
|
+
top: 16px;
|
|
242
|
+
left: 16px;
|
|
243
|
+
bottom: 16px;
|
|
244
|
+
transform: translateX(calc(-100% - 16px));
|
|
245
|
+
}
|
|
246
|
+
.yak-widget-container.yak-widget-drawer[data-position="right"] {
|
|
247
|
+
top: 16px;
|
|
248
|
+
right: 16px;
|
|
249
|
+
bottom: 16px;
|
|
250
|
+
transform: translateX(calc(100% + 16px));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Drawer open state */
|
|
254
|
+
.yak-widget-container.yak-widget-drawer[data-open="true"] {
|
|
255
|
+
transform: translateX(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ===========================================
|
|
259
|
+
IFRAME
|
|
260
|
+
=========================================== */
|
|
261
|
+
.yak-widget-iframe {
|
|
262
|
+
position: absolute;
|
|
263
|
+
inset: 0;
|
|
264
|
+
width: 100%;
|
|
265
|
+
height: 100%;
|
|
266
|
+
border: none;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* ===========================================
|
|
270
|
+
MOBILE RESPONSIVE
|
|
271
|
+
=========================================== */
|
|
272
|
+
@media (max-width: 640px) {
|
|
273
|
+
.yak-widget-container:not(.yak-widget-drawer) {
|
|
274
|
+
width: 100% !important;
|
|
275
|
+
height: 100% !important;
|
|
276
|
+
height: 100dvh !important;
|
|
277
|
+
max-width: none !important;
|
|
278
|
+
max-height: none !important;
|
|
279
|
+
top: 0 !important;
|
|
280
|
+
left: 0 !important;
|
|
281
|
+
right: 0 !important;
|
|
282
|
+
bottom: 0 !important;
|
|
283
|
+
border-radius: 0 !important;
|
|
284
|
+
}
|
|
285
|
+
.yak-widget-container.yak-widget-drawer {
|
|
286
|
+
width: 100% !important;
|
|
287
|
+
max-width: none !important;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Inline SVG for brain/circuit fallback icon
|
|
294
|
+
*/
|
|
295
|
+
function BrainCircuitIcon({ size = 20, className }) {
|
|
296
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: [_jsx("path", { d: "M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" }), _jsx("path", { d: "M9 13a4.5 4.5 0 0 0 3-4" }), _jsx("path", { d: "M6.003 5.125A3 3 0 0 0 6.401 6.5" }), _jsx("path", { d: "M3.477 10.896a4 4 0 0 1 .585-.396" }), _jsx("path", { d: "M6 18a4 4 0 0 1-1.967-.516" }), _jsx("path", { d: "M12 13h4" }), _jsx("path", { d: "M12 18h6a2 2 0 0 1 2 2v1" }), _jsx("path", { d: "M12 8h8" }), _jsx("path", { d: "M16 8V5a2 2 0 0 1 2-2" }), _jsx("circle", { cx: "16", cy: "13", r: ".5" }), _jsx("circle", { cx: "18", cy: "3", r: ".5" }), _jsx("circle", { cx: "20", cy: "21", r: ".5" }), _jsx("circle", { cx: "20", cy: "8", r: ".5" })] }));
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* YakWidget renders a fixed-position launcher button and iframe panel.
|
|
300
|
+
* The iframe loads immediately and shows its own skeleton while waiting for config.
|
|
301
|
+
*/
|
|
302
|
+
export function YakWidget({ iframeClassName, triggerLabel = "Ask with AI", } = {}) {
|
|
303
|
+
const { config, getEmbedUrl, registerIframeWindow, unregisterIframeWindow, isOpen, isIframeReady, open, close, } = useYak();
|
|
304
|
+
const iframeRef = useRef(null);
|
|
305
|
+
const [hasBeenOpened, setHasBeenOpened] = useState(false);
|
|
306
|
+
// Track if we're in a loading state (open but iframe not ready)
|
|
307
|
+
const isLoading = isOpen && hasBeenOpened && !isIframeReady;
|
|
308
|
+
// Get URLs from the client (computed based on environment)
|
|
309
|
+
const iframeSrc = getEmbedUrl();
|
|
310
|
+
// Track when widget is first opened
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
if (isOpen && !hasBeenOpened) {
|
|
313
|
+
setHasBeenOpened(true);
|
|
314
|
+
}
|
|
315
|
+
}, [isOpen, hasBeenOpened]);
|
|
316
|
+
// Register iframe window when loaded
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
if (!hasBeenOpened)
|
|
319
|
+
return;
|
|
320
|
+
const iframe = iframeRef.current;
|
|
321
|
+
if (!iframe)
|
|
322
|
+
return;
|
|
323
|
+
const handleLoad = () => {
|
|
324
|
+
if (iframe.contentWindow) {
|
|
325
|
+
registerIframeWindow(iframe.contentWindow);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
iframe.addEventListener("load", handleLoad);
|
|
329
|
+
return () => {
|
|
330
|
+
iframe.removeEventListener("load", handleLoad);
|
|
331
|
+
unregisterIframeWindow();
|
|
332
|
+
};
|
|
333
|
+
}, [registerIframeWindow, unregisterIframeWindow, hasBeenOpened]);
|
|
334
|
+
// Determine position styles based on theme
|
|
335
|
+
const position = config.theme?.position ?? "right";
|
|
336
|
+
const colorMode = config.theme?.colorMode;
|
|
337
|
+
const displayMode = config.theme?.displayMode ?? "chatbox";
|
|
338
|
+
const isDrawer = displayMode === "drawer";
|
|
339
|
+
// Determine color mode class for the widget
|
|
340
|
+
const colorModeClass = colorMode === "light" ? "yak-widget-light" : colorMode === "dark" ? "yak-widget-dark" : "";
|
|
341
|
+
// Check if custom button theme is provided (now nested in light/dark)
|
|
342
|
+
const lightButton = config.theme?.light?.button;
|
|
343
|
+
const darkButton = config.theme?.dark?.button;
|
|
344
|
+
const hasLightCustom = lightButton?.background || lightButton?.color || lightButton?.border;
|
|
345
|
+
const hasDarkCustom = darkButton?.background || darkButton?.color || darkButton?.border;
|
|
346
|
+
// Determine which custom class to apply based on color mode
|
|
347
|
+
let customButtonClass = "";
|
|
348
|
+
if (colorMode === "light" && hasLightCustom) {
|
|
349
|
+
customButtonClass = "yak-widget-custom-light";
|
|
350
|
+
}
|
|
351
|
+
else if (colorMode === "dark" && hasDarkCustom) {
|
|
352
|
+
customButtonClass = "yak-widget-custom-dark";
|
|
353
|
+
}
|
|
354
|
+
else if (colorMode === "system" || colorMode === undefined) {
|
|
355
|
+
// For system mode, we need to handle both via media queries
|
|
356
|
+
// We'll use a data attribute approach instead
|
|
357
|
+
}
|
|
358
|
+
// Build inline style for button CSS variables
|
|
359
|
+
const buttonStyle = {};
|
|
360
|
+
if (lightButton?.background)
|
|
361
|
+
buttonStyle["--yak-btn-light-bg"] = lightButton.background;
|
|
362
|
+
if (lightButton?.color)
|
|
363
|
+
buttonStyle["--yak-btn-light-color"] = lightButton.color;
|
|
364
|
+
if (lightButton?.border)
|
|
365
|
+
buttonStyle["--yak-btn-light-border"] = lightButton.border;
|
|
366
|
+
if (darkButton?.background)
|
|
367
|
+
buttonStyle["--yak-btn-dark-bg"] = darkButton.background;
|
|
368
|
+
if (darkButton?.color)
|
|
369
|
+
buttonStyle["--yak-btn-dark-color"] = darkButton.color;
|
|
370
|
+
if (darkButton?.border)
|
|
371
|
+
buttonStyle["--yak-btn-dark-border"] = darkButton.border;
|
|
372
|
+
// Build container class names
|
|
373
|
+
const containerClasses = [
|
|
374
|
+
"yak-widget-container",
|
|
375
|
+
isDrawer && "yak-widget-drawer",
|
|
376
|
+
colorModeClass,
|
|
377
|
+
].filter(Boolean).join(" ");
|
|
378
|
+
// Build button class names
|
|
379
|
+
const buttonClasses = [
|
|
380
|
+
"yak-widget-trigger",
|
|
381
|
+
colorModeClass,
|
|
382
|
+
customButtonClass,
|
|
383
|
+
].filter(Boolean).join(" ");
|
|
384
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { children: getWidgetStyles() }), _jsxs("button", { onClick: open, className: buttonClasses, style: Object.keys(buttonStyle).length > 0 ? buttonStyle : undefined, "data-position": position, "data-has-light-custom": hasLightCustom || undefined, "data-has-dark-custom": hasDarkCustom || undefined, "aria-label": isLoading ? "Loading chat" : "Open chat", disabled: isLoading, children: [_jsx("span", { className: "yak-widget-trigger-label", children: triggerLabel }), _jsx("div", { className: "yak-widget-icon-bg", children: isLoading ? (_jsx("div", { className: "yak-widget-spinner", "aria-hidden": "true" })) : (_jsx(BrainCircuitIcon, { size: 20, className: "yak-widget-icon" })) })] }), isDrawer && hasBeenOpened && (_jsx("div", { className: "yak-widget-backdrop", "data-open": isOpen && isIframeReady, onClick: close, "aria-hidden": "true" })), hasBeenOpened && (_jsx("div", { className: containerClasses, "data-position": position, "data-open": isOpen && isIframeReady, children: _jsx("iframe", { ref: iframeRef, src: iframeSrc, className: `yak-widget-iframe ${iframeClassName ?? ""}`.trim(), title: "yak-chat-host" }) }))] }));
|
|
385
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Theme, IframeMessageFromHost, ChatConfig } from "@yak-io/javascript";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for the yak provider
|
|
4
|
+
*/
|
|
5
|
+
export type YakConfig = {
|
|
6
|
+
appId: string;
|
|
7
|
+
theme?: Theme;
|
|
8
|
+
chatConfig?: ChatConfig | null;
|
|
9
|
+
onRedirect?: (path: string) => void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Context value with config and iframe management
|
|
13
|
+
*/
|
|
14
|
+
export type YakContextValue = {
|
|
15
|
+
config: YakConfig;
|
|
16
|
+
/** Get the iframe origin URL (determined by environment) */
|
|
17
|
+
getIframeOrigin: () => string;
|
|
18
|
+
/** Get the full iframe embed URL */
|
|
19
|
+
getEmbedUrl: () => string;
|
|
20
|
+
registerIframeWindow: (win: Window) => void;
|
|
21
|
+
unregisterIframeWindow: () => void;
|
|
22
|
+
sendMessage: (message: IframeMessageFromHost) => void;
|
|
23
|
+
/** Whether the chat widget is currently open */
|
|
24
|
+
isOpen: boolean;
|
|
25
|
+
/** Whether the iframe is ready to receive messages */
|
|
26
|
+
isIframeReady: boolean;
|
|
27
|
+
/** Open the chat widget */
|
|
28
|
+
open: () => void;
|
|
29
|
+
/** Close the chat widget */
|
|
30
|
+
close: () => void;
|
|
31
|
+
/** Open the chat widget and trigger a specific prompt */
|
|
32
|
+
openWithPrompt: (prompt: string) => void;
|
|
33
|
+
setIsIframeReady: (ready: boolean) => void;
|
|
34
|
+
};
|
|
35
|
+
export declare const YakContext: import("react").Context<YakContextValue | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Hook to access the Yak chat widget API.
|
|
38
|
+
* Provides methods for opening, closing, and triggering prompts.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* function MyComponent() {
|
|
43
|
+
* const { open, close, openWithPrompt, isOpen } = useYak();
|
|
44
|
+
*
|
|
45
|
+
* return (
|
|
46
|
+
* <div>
|
|
47
|
+
* <button onClick={() => open()}>Open Chat</button>
|
|
48
|
+
* <button onClick={() => openWithPrompt("Help me!")}>Get Help</button>
|
|
49
|
+
* </div>
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @throws {Error} if used outside YakProvider
|
|
55
|
+
*/
|
|
56
|
+
export declare function useYak(): YakContextValue;
|
|
57
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEnF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,oCAAoC;IACpC,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,oBAAoB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,sBAAsB,EAAE,MAAM,IAAI,CAAC;IACnC,WAAW,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACtD,gDAAgD;IAChD,MAAM,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,aAAa,EAAE,OAAO,CAAC;IACvB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,yDAAyD;IACzD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,eAAO,MAAM,UAAU,iDAA8C,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,MAAM,IAAI,eAAe,CAMxC"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
export const YakContext = createContext(null);
|
|
4
|
+
/**
|
|
5
|
+
* Hook to access the Yak chat widget API.
|
|
6
|
+
* Provides methods for opening, closing, and triggering prompts.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function MyComponent() {
|
|
11
|
+
* const { open, close, openWithPrompt, isOpen } = useYak();
|
|
12
|
+
*
|
|
13
|
+
* return (
|
|
14
|
+
* <div>
|
|
15
|
+
* <button onClick={() => open()}>Open Chat</button>
|
|
16
|
+
* <button onClick={() => openWithPrompt("Help me!")}>Get Help</button>
|
|
17
|
+
* </div>
|
|
18
|
+
* );
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @throws {Error} if used outside YakProvider
|
|
23
|
+
*/
|
|
24
|
+
export function useYak() {
|
|
25
|
+
const context = useContext(YakContext);
|
|
26
|
+
if (!context) {
|
|
27
|
+
throw new Error("useYak must be used within YakProvider");
|
|
28
|
+
}
|
|
29
|
+
return context;
|
|
30
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { useYak } from "./context.js";
|
|
2
|
+
export type { YakConfig, YakContextValue } from "./context.js";
|
|
3
|
+
export { YakProvider } from "./YakProvider.js";
|
|
4
|
+
export type { YakProviderProps } from "./YakProvider.js";
|
|
5
|
+
export { YakWidget } from "./YakWidget.js";
|
|
6
|
+
export type { YakWidgetProps } from "./YakWidget.js";
|
|
7
|
+
export { type GraphQLSchemaHandler, type RESTSchemaHandler, type GraphQLRequest, type RESTRequest, type ToolCallHandler, type SchemaSource, type GraphQLSchemaSource, type OpenAPISchemaSource, type Theme, type ThemeColors, type ButtonColors, } from "@yak-io/javascript";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logger utility for the yak React SDK.
|
|
3
|
+
* Debug/info logs are only emitted in development mode.
|
|
4
|
+
* Warnings and errors are always logged.
|
|
5
|
+
*/
|
|
6
|
+
export declare const logger: {
|
|
7
|
+
debug: (message: string, data?: unknown) => void;
|
|
8
|
+
info: (message: string, data?: unknown) => void;
|
|
9
|
+
warn: (message: string, data?: unknown) => void;
|
|
10
|
+
error: (message: string, data?: unknown) => void;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/internal/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,eAAO,MAAM,MAAM;qBACA,MAAM,SAAS,OAAO,KAAG,IAAI;oBAU9B,MAAM,SAAS,OAAO,KAAG,IAAI;oBAU7B,MAAM,SAAS,OAAO,KAAG,IAAI;qBAQ5B,MAAM,SAAS,OAAO,KAAG,IAAI;CAO/C,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logger utility for the yak React SDK.
|
|
3
|
+
* Debug/info logs are only emitted in development mode.
|
|
4
|
+
* Warnings and errors are always logged.
|
|
5
|
+
*/
|
|
6
|
+
const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
|
|
7
|
+
export const logger = {
|
|
8
|
+
debug: (message, data) => {
|
|
9
|
+
if (isDev) {
|
|
10
|
+
if (data !== undefined) {
|
|
11
|
+
console.log(`[yak-chat-host] ${message}`, data);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
console.log(`[yak-chat-host] ${message}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
info: (message, data) => {
|
|
19
|
+
if (isDev) {
|
|
20
|
+
if (data !== undefined) {
|
|
21
|
+
console.info(`[yak-chat-host] ${message}`, data);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.info(`[yak-chat-host] ${message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
warn: (message, data) => {
|
|
29
|
+
if (data !== undefined) {
|
|
30
|
+
console.warn(`[yak-chat-host] ${message}`, data);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.warn(`[yak-chat-host] ${message}`);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
error: (message, data) => {
|
|
37
|
+
if (data !== undefined) {
|
|
38
|
+
console.error(`[yak-chat-host] ${message}`, data);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.error(`[yak-chat-host] ${message}`);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yak-io/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React SDK for embedding yak chatbot",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"author": "Yak <support@yak.io>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/9f-au/yak.git",
|
|
11
|
+
"directory": "packages/react"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"provenance": false
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"yak",
|
|
19
|
+
"chatbot",
|
|
20
|
+
"ai",
|
|
21
|
+
"widget",
|
|
22
|
+
"chat",
|
|
23
|
+
"react"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@yak-io/javascript": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
45
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^24.10.2",
|
|
49
|
+
"@types/react": "^19.2.7",
|
|
50
|
+
"@types/react-dom": "^19.2.0",
|
|
51
|
+
"typescript": "^5.3.0",
|
|
52
|
+
"@repo/typescript-config": "0.0.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc",
|
|
56
|
+
"check-types": "tsc --noEmit"
|
|
57
|
+
}
|
|
58
|
+
}
|