@yak-io/react 0.3.0 → 0.4.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/YakProvider.d.ts +3 -73
- package/dist/YakProvider.d.ts.map +1 -1
- package/dist/YakProvider.js +284 -16
- package/dist/YakWidget.d.ts +21 -6
- package/dist/YakWidget.d.ts.map +1 -1
- package/dist/YakWidget.js +77 -203
- package/dist/context.d.ts +24 -12
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +19 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +8 -6
package/dist/YakProvider.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import type React from "react";
|
|
2
2
|
import { type Theme, type ChatConfigProvider, type ToolCallHandler, type GraphQLSchemaHandler, type RESTSchemaHandler } from "@yak-io/javascript";
|
|
3
3
|
/**
|
|
4
4
|
* Props for YakProvider
|
|
@@ -10,90 +10,19 @@ export type YakProviderProps = {
|
|
|
10
10
|
* Provider function for chat configuration (routes + tools).
|
|
11
11
|
* The consuming platform decides how to get the config (static, fetch, etc.)
|
|
12
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
13
|
*/
|
|
30
14
|
getConfig?: ChatConfigProvider;
|
|
31
15
|
/**
|
|
32
16
|
* Handler for tool calls from the chat widget.
|
|
33
17
|
* 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
18
|
*/
|
|
60
19
|
onToolCall?: ToolCallHandler;
|
|
61
20
|
/**
|
|
62
21
|
* 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
22
|
*/
|
|
77
23
|
onGraphQLSchemaCall?: GraphQLSchemaHandler;
|
|
78
24
|
/**
|
|
79
25
|
* 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
26
|
*/
|
|
98
27
|
onRESTSchemaCall?: RESTSchemaHandler;
|
|
99
28
|
/** Optional theme configuration */
|
|
@@ -106,7 +35,8 @@ export type YakProviderProps = {
|
|
|
106
35
|
children: React.ReactNode;
|
|
107
36
|
};
|
|
108
37
|
/**
|
|
109
|
-
* YakProvider sets up the context
|
|
38
|
+
* YakProvider sets up the context, message handling, and renders the chat iframe.
|
|
39
|
+
* The iframe is rendered as a fixed-position panel that can be opened/closed.
|
|
110
40
|
*/
|
|
111
41
|
export declare function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, children, }: YakProviderProps): React.JSX.Element;
|
|
112
42
|
//# sourceMappingURL=YakProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YakProvider.d.ts","sourceRoot":"","sources":["../src/YakProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"YakProvider.d.ts","sourceRoot":"","sources":["../src/YakProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAEL,KAAK,KAAK,EAGV,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;OAEG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C;;OAEG;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;AAmMF;;;GAGG;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,CAqVtC"}
|
package/dist/YakProvider.js
CHANGED
|
@@ -1,11 +1,204 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
4
4
|
import { YakContext } from "./context.js";
|
|
5
5
|
import { YakClient, } from "@yak-io/javascript";
|
|
6
6
|
import { logger } from "./internal/logger.js";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* All iframe/panel styles consolidated in one place
|
|
9
|
+
*/
|
|
10
|
+
function getIframeStyles() {
|
|
11
|
+
return `
|
|
12
|
+
/* ===========================================
|
|
13
|
+
YAK IFRAME STYLES (rendered by provider)
|
|
14
|
+
=========================================== */
|
|
15
|
+
|
|
16
|
+
/* ===========================================
|
|
17
|
+
ROOT LAYER (full viewport, pointer-events pass-through)
|
|
18
|
+
=========================================== */
|
|
19
|
+
.yak-panel-root {
|
|
20
|
+
position: fixed;
|
|
21
|
+
top: 0;
|
|
22
|
+
left: 0;
|
|
23
|
+
right: 0;
|
|
24
|
+
bottom: 0;
|
|
25
|
+
width: 100vw;
|
|
26
|
+
height: 100vh;
|
|
27
|
+
pointer-events: none;
|
|
28
|
+
z-index: 9998;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ===========================================
|
|
32
|
+
CONTAINER
|
|
33
|
+
=========================================== */
|
|
34
|
+
.yak-panel-container {
|
|
35
|
+
position: absolute;
|
|
36
|
+
width: 500px;
|
|
37
|
+
height: 600px;
|
|
38
|
+
max-width: calc(100vw - 40px);
|
|
39
|
+
max-height: calc(100vh - 120px);
|
|
40
|
+
border-radius: 15px;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
background-color: transparent;
|
|
43
|
+
pointer-events: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Container position variants (chatbox mode) - all 9 positions */
|
|
47
|
+
.yak-panel-container[data-position="top-left"]:not(.yak-panel-drawer) {
|
|
48
|
+
top: 16px;
|
|
49
|
+
left: 16px;
|
|
50
|
+
}
|
|
51
|
+
.yak-panel-container[data-position="top-center"]:not(.yak-panel-drawer) {
|
|
52
|
+
top: 16px;
|
|
53
|
+
left: 50%;
|
|
54
|
+
transform: translateX(-50%);
|
|
55
|
+
}
|
|
56
|
+
.yak-panel-container[data-position="top-right"]:not(.yak-panel-drawer) {
|
|
57
|
+
top: 16px;
|
|
58
|
+
right: 16px;
|
|
59
|
+
}
|
|
60
|
+
.yak-panel-container[data-position="left-center"]:not(.yak-panel-drawer) {
|
|
61
|
+
top: 50%;
|
|
62
|
+
left: 16px;
|
|
63
|
+
transform: translateY(-50%);
|
|
64
|
+
}
|
|
65
|
+
.yak-panel-container[data-position="right-center"]:not(.yak-panel-drawer) {
|
|
66
|
+
top: 50%;
|
|
67
|
+
right: 16px;
|
|
68
|
+
transform: translateY(-50%);
|
|
69
|
+
}
|
|
70
|
+
.yak-panel-container[data-position="bottom-left"]:not(.yak-panel-drawer) {
|
|
71
|
+
bottom: 16px;
|
|
72
|
+
left: 16px;
|
|
73
|
+
}
|
|
74
|
+
.yak-panel-container[data-position="bottom-center"]:not(.yak-panel-drawer) {
|
|
75
|
+
bottom: 16px;
|
|
76
|
+
left: 50%;
|
|
77
|
+
transform: translateX(-50%);
|
|
78
|
+
}
|
|
79
|
+
.yak-panel-container[data-position="bottom-right"]:not(.yak-panel-drawer) {
|
|
80
|
+
bottom: 16px;
|
|
81
|
+
right: 16px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Container visibility (chatbox mode) */
|
|
85
|
+
.yak-panel-container:not(.yak-panel-drawer) {
|
|
86
|
+
display: none;
|
|
87
|
+
}
|
|
88
|
+
.yak-panel-container:not(.yak-panel-drawer)[data-open="true"] {
|
|
89
|
+
display: block;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ===========================================
|
|
93
|
+
EXPANDED MODE (full viewport for data table)
|
|
94
|
+
=========================================== */
|
|
95
|
+
.yak-panel-container[data-expanded="true"] {
|
|
96
|
+
width: calc(100vw - 32px) !important;
|
|
97
|
+
height: calc(100vh - 32px) !important;
|
|
98
|
+
max-width: none !important;
|
|
99
|
+
max-height: none !important;
|
|
100
|
+
top: 16px !important;
|
|
101
|
+
left: 16px !important;
|
|
102
|
+
right: 16px !important;
|
|
103
|
+
bottom: 16px !important;
|
|
104
|
+
border-radius: 15px !important;
|
|
105
|
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
|
106
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15) !important;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@media (prefers-color-scheme: dark) {
|
|
110
|
+
.yak-panel-container[data-expanded="true"]:not(.yak-panel-light) {
|
|
111
|
+
border-color: rgba(255, 255, 255, 0.1) !important;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.yak-panel-container.yak-panel-dark[data-expanded="true"] {
|
|
116
|
+
border-color: rgba(255, 255, 255, 0.1) !important;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.yak-panel-container.yak-panel-light[data-expanded="true"] {
|
|
120
|
+
border-color: rgba(0, 0, 0, 0.1) !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ===========================================
|
|
124
|
+
DRAWER MODE
|
|
125
|
+
=========================================== */
|
|
126
|
+
.yak-panel-container.yak-panel-drawer {
|
|
127
|
+
height: calc(100% - 32px);
|
|
128
|
+
max-width: 100vw;
|
|
129
|
+
max-height: none;
|
|
130
|
+
border-radius: 15px;
|
|
131
|
+
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Drawer position - only left-center and right-center are relevant for drawer mode */
|
|
135
|
+
.yak-panel-container.yak-panel-drawer[data-position="left-center"],
|
|
136
|
+
.yak-panel-container.yak-panel-drawer[data-position="top-left"],
|
|
137
|
+
.yak-panel-container.yak-panel-drawer[data-position="bottom-left"] {
|
|
138
|
+
top: 16px;
|
|
139
|
+
left: 16px;
|
|
140
|
+
bottom: 16px;
|
|
141
|
+
transform: translateX(calc(-100% - 16px));
|
|
142
|
+
}
|
|
143
|
+
.yak-panel-container.yak-panel-drawer[data-position="right-center"],
|
|
144
|
+
.yak-panel-container.yak-panel-drawer[data-position="top-right"],
|
|
145
|
+
.yak-panel-container.yak-panel-drawer[data-position="bottom-right"] {
|
|
146
|
+
top: 16px;
|
|
147
|
+
right: 16px;
|
|
148
|
+
bottom: 16px;
|
|
149
|
+
transform: translateX(calc(100% + 16px));
|
|
150
|
+
}
|
|
151
|
+
/* Center positions default to right side for drawer */
|
|
152
|
+
.yak-panel-container.yak-panel-drawer[data-position="top-center"],
|
|
153
|
+
.yak-panel-container.yak-panel-drawer[data-position="bottom-center"] {
|
|
154
|
+
top: 16px;
|
|
155
|
+
right: 16px;
|
|
156
|
+
bottom: 16px;
|
|
157
|
+
transform: translateX(calc(100% + 16px));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Drawer open state */
|
|
161
|
+
.yak-panel-container.yak-panel-drawer[data-open="true"] {
|
|
162
|
+
transform: translateX(0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ===========================================
|
|
166
|
+
IFRAME
|
|
167
|
+
=========================================== */
|
|
168
|
+
.yak-panel-iframe {
|
|
169
|
+
position: absolute;
|
|
170
|
+
inset: 0;
|
|
171
|
+
width: 100%;
|
|
172
|
+
height: 100%;
|
|
173
|
+
border: none;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* ===========================================
|
|
177
|
+
MOBILE RESPONSIVE
|
|
178
|
+
=========================================== */
|
|
179
|
+
@media (max-width: 640px) {
|
|
180
|
+
.yak-panel-container:not(.yak-panel-drawer) {
|
|
181
|
+
width: 100% !important;
|
|
182
|
+
height: 100% !important;
|
|
183
|
+
height: 100dvh !important;
|
|
184
|
+
max-width: none !important;
|
|
185
|
+
max-height: none !important;
|
|
186
|
+
top: 0 !important;
|
|
187
|
+
left: 0 !important;
|
|
188
|
+
right: 0 !important;
|
|
189
|
+
bottom: 0 !important;
|
|
190
|
+
border-radius: 0 !important;
|
|
191
|
+
}
|
|
192
|
+
.yak-panel-container.yak-panel-drawer {
|
|
193
|
+
width: 100% !important;
|
|
194
|
+
max-width: none !important;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* YakProvider sets up the context, message handling, and renders the chat iframe.
|
|
201
|
+
* The iframe is rendered as a fixed-position panel that can be opened/closed.
|
|
9
202
|
*/
|
|
10
203
|
export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, children, }) {
|
|
11
204
|
const [iframeWindow, setIframeWindow] = useState(null);
|
|
@@ -13,6 +206,9 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
13
206
|
const [isWidgetOpen, setIsWidgetOpen] = useState(false);
|
|
14
207
|
const [pendingPrompt, setPendingPrompt] = useState(null);
|
|
15
208
|
const [isIframeReady, setIsIframeReady] = useState(false);
|
|
209
|
+
const [hasBeenOpened, setHasBeenOpened] = useState(false);
|
|
210
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
211
|
+
const iframeRef = useRef(null);
|
|
16
212
|
// Store event subscribers for tool call events
|
|
17
213
|
const toolEventSubscribersRef = useRef(new Set());
|
|
18
214
|
// Handler that notifies all subscribers when a tool call completes
|
|
@@ -50,6 +246,7 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
50
246
|
const client = clientRef.current;
|
|
51
247
|
// Get the iframe origin from the client (computed based on environment)
|
|
52
248
|
const iframeOrigin = client.getIframeOrigin();
|
|
249
|
+
const iframeSrc = client.getEmbedUrl();
|
|
53
250
|
// Update client config when props change
|
|
54
251
|
useEffect(() => {
|
|
55
252
|
client.updateConfig({
|
|
@@ -63,7 +260,18 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
63
260
|
chatConfig: chatConfig ?? undefined,
|
|
64
261
|
onToolCallComplete: handleToolCallComplete,
|
|
65
262
|
});
|
|
66
|
-
}, [
|
|
263
|
+
}, [
|
|
264
|
+
appId,
|
|
265
|
+
onToolCall,
|
|
266
|
+
onGraphQLSchemaCall,
|
|
267
|
+
onRESTSchemaCall,
|
|
268
|
+
theme,
|
|
269
|
+
onRedirect,
|
|
270
|
+
disableRestartButton,
|
|
271
|
+
chatConfig,
|
|
272
|
+
client,
|
|
273
|
+
handleToolCallComplete,
|
|
274
|
+
]);
|
|
67
275
|
// Update client iframe window
|
|
68
276
|
useEffect(() => {
|
|
69
277
|
client.setIframeWindow(iframeWindow);
|
|
@@ -77,6 +285,67 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
77
285
|
client.mount();
|
|
78
286
|
return () => client.unmount();
|
|
79
287
|
}, [client]);
|
|
288
|
+
// Track when widget is first opened
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (isWidgetOpen && !hasBeenOpened) {
|
|
291
|
+
setHasBeenOpened(true);
|
|
292
|
+
}
|
|
293
|
+
}, [isWidgetOpen, hasBeenOpened]);
|
|
294
|
+
// Register iframe window when loaded
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (!hasBeenOpened)
|
|
297
|
+
return;
|
|
298
|
+
const iframe = iframeRef.current;
|
|
299
|
+
if (!iframe)
|
|
300
|
+
return;
|
|
301
|
+
const handleLoad = () => {
|
|
302
|
+
if (iframe.contentWindow) {
|
|
303
|
+
logger.debug("Iframe window registered");
|
|
304
|
+
setIframeWindow(iframe.contentWindow);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
iframe.addEventListener("load", handleLoad);
|
|
308
|
+
return () => {
|
|
309
|
+
iframe.removeEventListener("load", handleLoad);
|
|
310
|
+
logger.debug("Iframe window unregistered");
|
|
311
|
+
setIframeWindow(null);
|
|
312
|
+
};
|
|
313
|
+
}, [hasBeenOpened]);
|
|
314
|
+
// Listen for expansion messages from iframe
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
const handleMessage = (event) => {
|
|
317
|
+
const iframe = iframeRef.current;
|
|
318
|
+
if (!iframe)
|
|
319
|
+
return;
|
|
320
|
+
// Check for expansion control messages
|
|
321
|
+
if (event.data?.type === "YAK_SET_EXPANDED") {
|
|
322
|
+
setIsExpanded(Boolean(event.data.expanded));
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
window.addEventListener("message", handleMessage);
|
|
326
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
327
|
+
}, []);
|
|
328
|
+
// Detect mobile/fullscreen and notify iframe
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
if (!isIframeReady)
|
|
331
|
+
return;
|
|
332
|
+
const mobileQuery = window.matchMedia("(max-width: 640px)");
|
|
333
|
+
const notifyFullscreen = (isFullscreen) => {
|
|
334
|
+
const msg = {
|
|
335
|
+
type: "yak:viewport",
|
|
336
|
+
payload: { fullscreen: isFullscreen },
|
|
337
|
+
};
|
|
338
|
+
iframeWindow?.postMessage(msg, iframeOrigin);
|
|
339
|
+
};
|
|
340
|
+
// Send initial state
|
|
341
|
+
notifyFullscreen(mobileQuery.matches);
|
|
342
|
+
// Listen for changes
|
|
343
|
+
const handleChange = (e) => {
|
|
344
|
+
notifyFullscreen(e.matches);
|
|
345
|
+
};
|
|
346
|
+
mobileQuery.addEventListener("change", handleChange);
|
|
347
|
+
return () => mobileQuery.removeEventListener("change", handleChange);
|
|
348
|
+
}, [isIframeReady, iframeWindow, iframeOrigin]);
|
|
80
349
|
// Get chat config when widget is opened
|
|
81
350
|
useEffect(() => {
|
|
82
351
|
if (typeof window === "undefined" || !isWidgetOpen || !getConfig)
|
|
@@ -117,14 +386,6 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
117
386
|
// Methods to get URLs from the client
|
|
118
387
|
const getIframeOrigin = useCallback(() => client.getIframeOrigin(), [client]);
|
|
119
388
|
const getEmbedUrl = useCallback(() => client.getEmbedUrl(), [client]);
|
|
120
|
-
const registerIframeWindow = useCallback((win) => {
|
|
121
|
-
logger.debug("Iframe window registered");
|
|
122
|
-
setIframeWindow(win);
|
|
123
|
-
}, []);
|
|
124
|
-
const unregisterIframeWindow = useCallback(() => {
|
|
125
|
-
logger.debug("Iframe window unregistered");
|
|
126
|
-
setIframeWindow(null);
|
|
127
|
-
}, []);
|
|
128
389
|
const sendMessage = useCallback((message) => {
|
|
129
390
|
iframeWindow?.postMessage(message, iframeOrigin);
|
|
130
391
|
}, [iframeWindow, iframeOrigin]);
|
|
@@ -175,8 +436,6 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
175
436
|
config,
|
|
176
437
|
getIframeOrigin,
|
|
177
438
|
getEmbedUrl,
|
|
178
|
-
registerIframeWindow,
|
|
179
|
-
unregisterIframeWindow,
|
|
180
439
|
sendMessage,
|
|
181
440
|
isOpen: isWidgetOpen,
|
|
182
441
|
isIframeReady,
|
|
@@ -189,8 +448,6 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
189
448
|
config,
|
|
190
449
|
getIframeOrigin,
|
|
191
450
|
getEmbedUrl,
|
|
192
|
-
registerIframeWindow,
|
|
193
|
-
unregisterIframeWindow,
|
|
194
451
|
sendMessage,
|
|
195
452
|
isWidgetOpen,
|
|
196
453
|
isIframeReady,
|
|
@@ -199,5 +456,16 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
|
|
|
199
456
|
openWithPrompt,
|
|
200
457
|
subscribeToToolEvents,
|
|
201
458
|
]);
|
|
202
|
-
|
|
459
|
+
// Determine position styles based on theme
|
|
460
|
+
const position = theme?.position ?? "bottom-right";
|
|
461
|
+
const colorMode = theme?.colorMode;
|
|
462
|
+
const displayMode = theme?.displayMode ?? "chatbox";
|
|
463
|
+
const isDrawer = displayMode === "drawer";
|
|
464
|
+
// Determine color mode class for the container
|
|
465
|
+
const colorModeClass = colorMode === "light" ? "yak-panel-light" : colorMode === "dark" ? "yak-panel-dark" : "";
|
|
466
|
+
// Build container class names
|
|
467
|
+
const containerClasses = ["yak-panel-container", isDrawer && "yak-panel-drawer", colorModeClass]
|
|
468
|
+
.filter(Boolean)
|
|
469
|
+
.join(" ");
|
|
470
|
+
return (_jsxs(YakContext.Provider, { value: contextValue, children: [children, _jsx("style", { children: getIframeStyles() }), hasBeenOpened && (_jsx("div", { className: "yak-panel-root", "data-expanded": isExpanded, children: _jsx("div", { className: containerClasses, "data-position": position, "data-open": isWidgetOpen && isIframeReady, "data-expanded": isExpanded, children: _jsx("iframe", { ref: iframeRef, src: iframeSrc, className: "yak-panel-iframe", title: "yak-chat-host" }) }) }))] }));
|
|
203
471
|
}
|
package/dist/YakWidget.d.ts
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { WidgetPosition } from "@yak-io/javascript";
|
|
2
3
|
/**
|
|
3
4
|
* Props for YakWidget
|
|
4
5
|
*/
|
|
5
6
|
export type YakWidgetProps = {
|
|
6
|
-
/** Optional CSS class name for the iframe */
|
|
7
|
-
iframeClassName?: string;
|
|
8
7
|
/** Text to display next to the logo */
|
|
9
8
|
triggerLabel?: string;
|
|
9
|
+
/** Position of the button (defaults to theme position or "bottom-right") */
|
|
10
|
+
position?: WidgetPosition;
|
|
11
|
+
/** Color mode override */
|
|
12
|
+
colorMode?: "light" | "dark" | "system";
|
|
13
|
+
/** Custom button colors for light mode */
|
|
14
|
+
lightButton?: {
|
|
15
|
+
background?: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
border?: string;
|
|
18
|
+
};
|
|
19
|
+
/** Custom button colors for dark mode */
|
|
20
|
+
darkButton?: {
|
|
21
|
+
background?: string;
|
|
22
|
+
color?: string;
|
|
23
|
+
border?: string;
|
|
24
|
+
};
|
|
10
25
|
};
|
|
11
26
|
/**
|
|
12
|
-
* YakWidget renders a fixed-position launcher button
|
|
13
|
-
* The iframe
|
|
27
|
+
* YakWidget renders a fixed-position launcher button.
|
|
28
|
+
* The chat iframe is rendered by YakProvider - this is just the trigger button.
|
|
14
29
|
*/
|
|
15
|
-
export declare function YakWidget({
|
|
30
|
+
export declare function YakWidget({ triggerLabel, position, colorMode, lightButton, darkButton, }?: YakWidgetProps): React.JSX.Element;
|
|
16
31
|
//# sourceMappingURL=YakWidget.d.ts.map
|
package/dist/YakWidget.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YakWidget.d.ts","sourceRoot":"","sources":["../src/YakWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"YakWidget.d.ts","sourceRoot":"","sources":["../src/YakWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAwPzD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACxC,0CAA0C;IAC1C,WAAW,CAAC,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,yCAAyC;IACzC,UAAU,CAAC,EAAE;QACX,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAwCF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,YAA4B,EAC5B,QAAyB,EACzB,SAAS,EACT,WAAW,EACX,UAAU,GACX,GAAE,cAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAuCzC"}
|
package/dist/YakWidget.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useEffect, useState } from "react";
|
|
4
3
|
import { useYak } from "./context.js";
|
|
5
4
|
/**
|
|
6
|
-
* All
|
|
5
|
+
* All button styles consolidated in one place
|
|
7
6
|
*/
|
|
8
|
-
function
|
|
7
|
+
function getButtonStyles() {
|
|
9
8
|
return `
|
|
10
9
|
/* ===========================================
|
|
11
|
-
YAK WIDGET STYLES
|
|
10
|
+
YAK WIDGET BUTTON STYLES
|
|
12
11
|
=========================================== */
|
|
13
12
|
|
|
14
13
|
/* Trigger Button Base */
|
|
@@ -32,13 +31,47 @@ function getWidgetStyles() {
|
|
|
32
31
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
/* Trigger position variants */
|
|
36
|
-
.yak-widget-trigger[data-position="left"] {
|
|
34
|
+
/* Trigger position variants - all 9 positions */
|
|
35
|
+
.yak-widget-trigger[data-position="top-left"] {
|
|
36
|
+
top: 28px;
|
|
37
|
+
left: 28px;
|
|
38
|
+
flex-direction: row-reverse;
|
|
39
|
+
}
|
|
40
|
+
.yak-widget-trigger[data-position="top-center"] {
|
|
41
|
+
top: 28px;
|
|
42
|
+
left: 50%;
|
|
43
|
+
transform: translateX(-50%);
|
|
44
|
+
flex-direction: row;
|
|
45
|
+
}
|
|
46
|
+
.yak-widget-trigger[data-position="top-right"] {
|
|
47
|
+
top: 28px;
|
|
48
|
+
right: 28px;
|
|
49
|
+
flex-direction: row;
|
|
50
|
+
}
|
|
51
|
+
.yak-widget-trigger[data-position="left-center"] {
|
|
52
|
+
top: 50%;
|
|
53
|
+
left: 28px;
|
|
54
|
+
transform: translateY(-50%);
|
|
55
|
+
flex-direction: row-reverse;
|
|
56
|
+
}
|
|
57
|
+
.yak-widget-trigger[data-position="right-center"] {
|
|
58
|
+
top: 50%;
|
|
59
|
+
right: 28px;
|
|
60
|
+
transform: translateY(-50%);
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
}
|
|
63
|
+
.yak-widget-trigger[data-position="bottom-left"] {
|
|
37
64
|
bottom: 28px;
|
|
38
65
|
left: 28px;
|
|
39
66
|
flex-direction: row-reverse;
|
|
40
67
|
}
|
|
41
|
-
.yak-widget-trigger[data-position="
|
|
68
|
+
.yak-widget-trigger[data-position="bottom-center"] {
|
|
69
|
+
bottom: 28px;
|
|
70
|
+
left: 50%;
|
|
71
|
+
transform: translateX(-50%);
|
|
72
|
+
flex-direction: row;
|
|
73
|
+
}
|
|
74
|
+
.yak-widget-trigger[data-position="bottom-right"] {
|
|
42
75
|
bottom: 28px;
|
|
43
76
|
right: 28px;
|
|
44
77
|
flex-direction: row;
|
|
@@ -85,7 +118,6 @@ function getWidgetStyles() {
|
|
|
85
118
|
/* Loading/disabled state for trigger button */
|
|
86
119
|
.yak-widget-trigger:disabled {
|
|
87
120
|
cursor: wait;
|
|
88
|
-
opacity: 0.8;
|
|
89
121
|
}
|
|
90
122
|
|
|
91
123
|
/* Custom button styles for forced light mode */
|
|
@@ -171,179 +203,38 @@ function getWidgetStyles() {
|
|
|
171
203
|
.yak-widget-trigger.yak-widget-dark:not(.yak-widget-custom-dark) .yak-widget-icon-bg {
|
|
172
204
|
background-color: rgba(255, 255, 255, 0.1);
|
|
173
205
|
}
|
|
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
206
|
`;
|
|
291
207
|
}
|
|
292
208
|
/**
|
|
293
209
|
* Inline SVG for brain/circuit fallback icon
|
|
294
210
|
*/
|
|
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" })] }));
|
|
211
|
+
function BrainCircuitIcon({ size = 20, className, }) {
|
|
212
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", 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
213
|
}
|
|
298
214
|
/**
|
|
299
|
-
*
|
|
300
|
-
* The iframe loads immediately and shows its own skeleton while waiting for config.
|
|
215
|
+
* Compute button CSS variables from custom button color props
|
|
301
216
|
*/
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
|
217
|
+
function buildButtonStyle(lightButton, darkButton) {
|
|
218
|
+
const style = {};
|
|
219
|
+
if (lightButton?.background)
|
|
220
|
+
style["--yak-btn-light-bg"] = lightButton.background;
|
|
221
|
+
if (lightButton?.color)
|
|
222
|
+
style["--yak-btn-light-color"] = lightButton.color;
|
|
223
|
+
if (lightButton?.border)
|
|
224
|
+
style["--yak-btn-light-border"] = lightButton.border;
|
|
225
|
+
if (darkButton?.background)
|
|
226
|
+
style["--yak-btn-dark-bg"] = darkButton.background;
|
|
227
|
+
if (darkButton?.color)
|
|
228
|
+
style["--yak-btn-dark-color"] = darkButton.color;
|
|
229
|
+
if (darkButton?.border)
|
|
230
|
+
style["--yak-btn-dark-border"] = darkButton.border;
|
|
231
|
+
return style;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Compute button class names from color mode and custom theme flags
|
|
235
|
+
*/
|
|
236
|
+
function buildButtonClasses(colorMode, hasLightCustom, hasDarkCustom) {
|
|
340
237
|
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
238
|
let customButtonClass = "";
|
|
348
239
|
if (colorMode === "light" && hasLightCustom) {
|
|
349
240
|
customButtonClass = "yak-widget-custom-light";
|
|
@@ -351,35 +242,18 @@ export function YakWidget({ iframeClassName, triggerLabel = "Ask with AI", } = {
|
|
|
351
242
|
else if (colorMode === "dark" && hasDarkCustom) {
|
|
352
243
|
customButtonClass = "yak-widget-custom-dark";
|
|
353
244
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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" }) }))] }));
|
|
245
|
+
return ["yak-widget-trigger", colorModeClass, customButtonClass].filter(Boolean).join(" ");
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* YakWidget renders a fixed-position launcher button.
|
|
249
|
+
* The chat iframe is rendered by YakProvider - this is just the trigger button.
|
|
250
|
+
*/
|
|
251
|
+
export function YakWidget({ triggerLabel = "Ask with AI", position = "bottom-right", colorMode, lightButton, darkButton, } = {}) {
|
|
252
|
+
const { open, isOpen, isIframeReady } = useYak();
|
|
253
|
+
const isLoading = isOpen && !isIframeReady;
|
|
254
|
+
const hasLightCustom = lightButton?.background || lightButton?.color || lightButton?.border;
|
|
255
|
+
const hasDarkCustom = darkButton?.background || darkButton?.color || darkButton?.border;
|
|
256
|
+
const buttonStyle = buildButtonStyle(lightButton, darkButton);
|
|
257
|
+
const buttonClasses = buildButtonClasses(colorMode, hasLightCustom, hasDarkCustom);
|
|
258
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { children: getButtonStyles() }), _jsxs("button", { type: "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" })) })] })] }));
|
|
385
259
|
}
|
package/dist/context.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Theme,
|
|
1
|
+
import type { Theme, ChatConfig, ToolCallEvent, IframeMessageFromHost } from "@yak-io/javascript";
|
|
2
2
|
/**
|
|
3
3
|
* Configuration for the yak provider
|
|
4
4
|
*/
|
|
@@ -13,17 +13,10 @@ export type YakConfig = {
|
|
|
13
13
|
*/
|
|
14
14
|
export type ToolCallEventHandler = (event: ToolCallEvent) => void;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Public API for controlling the Yak chat widget.
|
|
17
|
+
* This is what consumers get from useYak().
|
|
17
18
|
*/
|
|
18
19
|
export type YakContextValue = {
|
|
19
|
-
config: YakConfig;
|
|
20
|
-
/** Get the iframe origin URL (determined by environment) */
|
|
21
|
-
getIframeOrigin: () => string;
|
|
22
|
-
/** Get the full iframe embed URL */
|
|
23
|
-
getEmbedUrl: () => string;
|
|
24
|
-
registerIframeWindow: (win: Window) => void;
|
|
25
|
-
unregisterIframeWindow: () => void;
|
|
26
|
-
sendMessage: (message: IframeMessageFromHost) => void;
|
|
27
20
|
/** Whether the chat widget is currently open */
|
|
28
21
|
isOpen: boolean;
|
|
29
22
|
/** Whether the iframe is ready to receive messages */
|
|
@@ -34,11 +27,25 @@ export type YakContextValue = {
|
|
|
34
27
|
close: () => void;
|
|
35
28
|
/** Open the chat widget and trigger a specific prompt */
|
|
36
29
|
openWithPrompt: (prompt: string) => void;
|
|
37
|
-
setIsIframeReady: (ready: boolean) => void;
|
|
38
30
|
/** Subscribe to tool call completion events */
|
|
39
31
|
subscribeToToolEvents: (handler: ToolCallEventHandler) => () => void;
|
|
40
32
|
};
|
|
41
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Internal context with additional methods for iframe management.
|
|
35
|
+
* Not exposed to consumers - only used by provider internals.
|
|
36
|
+
*/
|
|
37
|
+
export type YakInternalContextValue = YakContextValue & {
|
|
38
|
+
config: YakConfig;
|
|
39
|
+
/** Get the iframe origin URL (determined by environment) */
|
|
40
|
+
getIframeOrigin: () => string;
|
|
41
|
+
/** Get the full iframe embed URL */
|
|
42
|
+
getEmbedUrl: () => string;
|
|
43
|
+
/** Send a message to the iframe */
|
|
44
|
+
sendMessage: (message: IframeMessageFromHost) => void;
|
|
45
|
+
/** Set iframe ready state (called by provider) */
|
|
46
|
+
setIsIframeReady: (ready: boolean) => void;
|
|
47
|
+
};
|
|
48
|
+
export declare const YakContext: import("react").Context<YakInternalContextValue | null>;
|
|
42
49
|
/**
|
|
43
50
|
* Hook to access the Yak chat widget API.
|
|
44
51
|
* Provides methods for opening, closing, and triggering prompts.
|
|
@@ -60,6 +67,11 @@ export declare const YakContext: import("react").Context<YakContextValue | null>
|
|
|
60
67
|
* @throws {Error} if used outside YakProvider
|
|
61
68
|
*/
|
|
62
69
|
export declare function useYak(): YakContextValue;
|
|
70
|
+
/**
|
|
71
|
+
* Internal hook for components that need full context access.
|
|
72
|
+
* Not exported publicly.
|
|
73
|
+
*/
|
|
74
|
+
export declare function useYakInternal(): YakInternalContextValue;
|
|
63
75
|
/**
|
|
64
76
|
* Hook to subscribe to tool call completion events.
|
|
65
77
|
* Useful for page-level cache invalidation when chatbot tools modify data.
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAElG;;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,oBAAoB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,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,+CAA+C;IAC/C,qBAAqB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAC;CACtE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,eAAe,GAAG;IACtD,MAAM,EAAE,SAAS,CAAC;IAClB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,oCAAoC;IACpC,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,mCAAmC;IACnC,WAAW,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACtD,kDAAkD;IAClD,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,eAAO,MAAM,UAAU,yDAAsD,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,MAAM,IAAI,eAAe,CAcxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,uBAAuB,CAMxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAkBnE"}
|
package/dist/context.js
CHANGED
|
@@ -26,6 +26,25 @@ export function useYak() {
|
|
|
26
26
|
if (!context) {
|
|
27
27
|
throw new Error("useYak must be used within YakProvider");
|
|
28
28
|
}
|
|
29
|
+
// Return only the public API
|
|
30
|
+
return {
|
|
31
|
+
isOpen: context.isOpen,
|
|
32
|
+
isIframeReady: context.isIframeReady,
|
|
33
|
+
open: context.open,
|
|
34
|
+
close: context.close,
|
|
35
|
+
openWithPrompt: context.openWithPrompt,
|
|
36
|
+
subscribeToToolEvents: context.subscribeToToolEvents,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Internal hook for components that need full context access.
|
|
41
|
+
* Not exported publicly.
|
|
42
|
+
*/
|
|
43
|
+
export function useYakInternal() {
|
|
44
|
+
const context = useContext(YakContext);
|
|
45
|
+
if (!context) {
|
|
46
|
+
throw new Error("useYakInternal must be used within YakProvider");
|
|
47
|
+
}
|
|
29
48
|
return context;
|
|
30
49
|
}
|
|
31
50
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { YakProvider } from "./YakProvider.js";
|
|
|
4
4
|
export type { YakProviderProps } from "./YakProvider.js";
|
|
5
5
|
export { YakWidget } from "./YakWidget.js";
|
|
6
6
|
export type { YakWidgetProps } from "./YakWidget.js";
|
|
7
|
-
export {
|
|
7
|
+
export type { GraphQLSchemaHandler, RESTSchemaHandler, GraphQLRequest, RESTRequest, ToolCallHandler, ToolCallEvent, SchemaSource, GraphQLSchemaSource, OpenAPISchemaSource, Theme, ThemeColors, ButtonColors, WidgetPosition, } from "@yak-io/javascript";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,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,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,eAAe,EACf,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,cAAc,GACf,MAAM,oBAAoB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yak-io/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "React SDK for embedding yak chatbot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"author": "Yak <support@yak.io>",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/388-labs/yak.git",
|
|
11
11
|
"directory": "packages/react"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
@@ -41,21 +41,23 @@
|
|
|
41
41
|
"./package.json": "./package.json"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@yak-io/javascript": "0.
|
|
44
|
+
"@yak-io/javascript": "0.4.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"react": "^18.0.0 || ^19.0.0",
|
|
48
48
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^24.10.
|
|
52
|
-
"@types/react": "^19.2.
|
|
51
|
+
"@types/node": "^24.10.4",
|
|
52
|
+
"@types/react": "^19.2.14",
|
|
53
53
|
"@types/react-dom": "^19.2.0",
|
|
54
54
|
"typescript": "^5.3.0",
|
|
55
55
|
"@repo/typescript-config": "0.0.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsc",
|
|
59
|
-
"check-types": "tsc --noEmit"
|
|
59
|
+
"check-types": "tsc --noEmit",
|
|
60
|
+
"lint": "biome lint ./src --fix",
|
|
61
|
+
"format": "biome format ./src --write"
|
|
60
62
|
}
|
|
61
63
|
}
|