@wallavi/widget 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +247 -169
- package/dist/index.mjs +248 -170
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -9,13 +9,25 @@ type ToolPart = {
|
|
|
9
9
|
output?: unknown;
|
|
10
10
|
errorText?: string;
|
|
11
11
|
};
|
|
12
|
+
type PickerPart = {
|
|
13
|
+
type: "picker";
|
|
14
|
+
pickerId: string;
|
|
15
|
+
paramName: string;
|
|
16
|
+
toolName: string;
|
|
17
|
+
label: string;
|
|
18
|
+
options: Array<{
|
|
19
|
+
value: string;
|
|
20
|
+
label: string;
|
|
21
|
+
}>;
|
|
22
|
+
selectedValue?: string;
|
|
23
|
+
};
|
|
12
24
|
type MessagePart = {
|
|
13
25
|
type: "text";
|
|
14
26
|
text: string;
|
|
15
27
|
} | {
|
|
16
28
|
type: "reasoning";
|
|
17
29
|
text: string;
|
|
18
|
-
} | ToolPart;
|
|
30
|
+
} | ToolPart | PickerPart;
|
|
19
31
|
type Message = {
|
|
20
32
|
id: string;
|
|
21
33
|
role: "user" | "assistant";
|
|
@@ -170,6 +182,7 @@ interface UseChatReturn {
|
|
|
170
182
|
send: (text?: string) => Promise<void>;
|
|
171
183
|
regenerate: () => Promise<void>;
|
|
172
184
|
reset: () => void;
|
|
185
|
+
selectPickerOption: (pickerId: string, paramName: string, value: string, label: string) => Promise<void>;
|
|
173
186
|
}
|
|
174
187
|
declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
|
|
175
188
|
|
package/dist/index.d.ts
CHANGED
|
@@ -9,13 +9,25 @@ type ToolPart = {
|
|
|
9
9
|
output?: unknown;
|
|
10
10
|
errorText?: string;
|
|
11
11
|
};
|
|
12
|
+
type PickerPart = {
|
|
13
|
+
type: "picker";
|
|
14
|
+
pickerId: string;
|
|
15
|
+
paramName: string;
|
|
16
|
+
toolName: string;
|
|
17
|
+
label: string;
|
|
18
|
+
options: Array<{
|
|
19
|
+
value: string;
|
|
20
|
+
label: string;
|
|
21
|
+
}>;
|
|
22
|
+
selectedValue?: string;
|
|
23
|
+
};
|
|
12
24
|
type MessagePart = {
|
|
13
25
|
type: "text";
|
|
14
26
|
text: string;
|
|
15
27
|
} | {
|
|
16
28
|
type: "reasoning";
|
|
17
29
|
text: string;
|
|
18
|
-
} | ToolPart;
|
|
30
|
+
} | ToolPart | PickerPart;
|
|
19
31
|
type Message = {
|
|
20
32
|
id: string;
|
|
21
33
|
role: "user" | "assistant";
|
|
@@ -170,6 +182,7 @@ interface UseChatReturn {
|
|
|
170
182
|
send: (text?: string) => Promise<void>;
|
|
171
183
|
regenerate: () => Promise<void>;
|
|
172
184
|
reset: () => void;
|
|
185
|
+
selectPickerOption: (pickerId: string, paramName: string, value: string, label: string) => Promise<void>;
|
|
173
186
|
}
|
|
174
187
|
declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
|
|
175
188
|
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ var ReactMarkdownLib__default = /*#__PURE__*/_interopDefault(ReactMarkdownLib);
|
|
|
34
34
|
var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
|
|
35
35
|
|
|
36
36
|
// src/bubble-widget.tsx
|
|
37
|
+
var cn = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
37
38
|
|
|
38
39
|
// src/types.ts
|
|
39
40
|
function getContrastColor(hex) {
|
|
@@ -83,6 +84,10 @@ function useChat({
|
|
|
83
84
|
playgroundOverrides
|
|
84
85
|
}) {
|
|
85
86
|
const persistKey = persist ? `wallavi_${agentId}` : null;
|
|
87
|
+
const onNavigateRef = react.useRef(onNavigate);
|
|
88
|
+
react.useEffect(() => {
|
|
89
|
+
onNavigateRef.current = onNavigate;
|
|
90
|
+
});
|
|
86
91
|
const [messages, setMessages] = react.useState(() => {
|
|
87
92
|
if (!persistKey || typeof window === "undefined") return [];
|
|
88
93
|
try {
|
|
@@ -130,6 +135,118 @@ function useChat({
|
|
|
130
135
|
}
|
|
131
136
|
}
|
|
132
137
|
}, [persistKey]);
|
|
138
|
+
const applyStreamEvent = react.useCallback((proto, msgId) => {
|
|
139
|
+
if (proto.type === "navigate") {
|
|
140
|
+
if (proto.path.startsWith("/")) onNavigateRef.current?.(proto.path);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
setMessages((prev) => {
|
|
144
|
+
const idx = prev.findIndex((m) => m.id === msgId);
|
|
145
|
+
if (idx === -1) return prev;
|
|
146
|
+
const existing = prev[idx];
|
|
147
|
+
const msg = { id: existing.id, role: existing.role, parts: [...existing.parts] };
|
|
148
|
+
switch (proto.type) {
|
|
149
|
+
case "text-delta": {
|
|
150
|
+
const textIdx = msg.parts.findIndex((p) => p.type === "text");
|
|
151
|
+
if (textIdx === -1) {
|
|
152
|
+
msg.parts.push({ type: "text", text: proto.delta });
|
|
153
|
+
} else {
|
|
154
|
+
const p = msg.parts[textIdx];
|
|
155
|
+
msg.parts[textIdx] = { type: "text", text: p.text + proto.delta };
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "reasoning-delta": {
|
|
160
|
+
const rIdx = msg.parts.findIndex((p) => p.type === "reasoning");
|
|
161
|
+
if (rIdx === -1) {
|
|
162
|
+
msg.parts.unshift({ type: "reasoning", text: proto.delta });
|
|
163
|
+
} else {
|
|
164
|
+
const p = msg.parts[rIdx];
|
|
165
|
+
msg.parts[rIdx] = { type: "reasoning", text: p.text + proto.delta };
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "tool-input-available": {
|
|
170
|
+
msg.parts.push({
|
|
171
|
+
type: "tool",
|
|
172
|
+
toolCallId: proto.toolCallId,
|
|
173
|
+
toolName: proto.toolName,
|
|
174
|
+
input: proto.input ?? {},
|
|
175
|
+
status: "running"
|
|
176
|
+
});
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "tool-output-available": {
|
|
180
|
+
const tIdx = msg.parts.findIndex(
|
|
181
|
+
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
182
|
+
);
|
|
183
|
+
if (tIdx !== -1) {
|
|
184
|
+
msg.parts[tIdx] = { ...msg.parts[tIdx], status: "done", output: proto.output };
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "tool-output-error": {
|
|
189
|
+
const tIdx = msg.parts.findIndex(
|
|
190
|
+
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
191
|
+
);
|
|
192
|
+
if (tIdx !== -1) {
|
|
193
|
+
msg.parts[tIdx] = { ...msg.parts[tIdx], status: "error", errorText: proto.errorText };
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case "picker": {
|
|
198
|
+
msg.parts.push({
|
|
199
|
+
type: "picker",
|
|
200
|
+
pickerId: proto.pickerId,
|
|
201
|
+
paramName: proto.paramName,
|
|
202
|
+
toolName: proto.toolName,
|
|
203
|
+
label: proto.label,
|
|
204
|
+
options: proto.options
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const copy = [...prev];
|
|
210
|
+
copy[idx] = msg;
|
|
211
|
+
return copy;
|
|
212
|
+
});
|
|
213
|
+
}, []);
|
|
214
|
+
const fetchAndStream = react.useCallback(async (opts) => {
|
|
215
|
+
const { input: userInput, msgId, extraMetadata } = opts;
|
|
216
|
+
const isPrivate = Boolean(workspaceId);
|
|
217
|
+
const token = isPrivate && typeof window !== "undefined" ? await window.Clerk?.session?.getToken() : null;
|
|
218
|
+
const url = isPrivate ? `${API_URL}/api/threads/${threadId}/stream` : `${API_URL}/api/chat/stream`;
|
|
219
|
+
const res = await fetch(url, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers: {
|
|
222
|
+
"Content-Type": "application/json",
|
|
223
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
224
|
+
},
|
|
225
|
+
body: JSON.stringify({
|
|
226
|
+
input: userInput,
|
|
227
|
+
agentId,
|
|
228
|
+
...isPrivate ? { workspaceId, ...playgroundOverrides ? { playgroundOverrides } : {} } : { threadId },
|
|
229
|
+
source,
|
|
230
|
+
...userContext?.userName ? { userName: userContext.userName } : {},
|
|
231
|
+
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
232
|
+
userMetadata: {
|
|
233
|
+
...userContext?.metadata ?? {},
|
|
234
|
+
...userContext?.pageContext ? { pageContext: userContext.pageContext } : {},
|
|
235
|
+
headers: {
|
|
236
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
237
|
+
...userContext?.headers ?? {}
|
|
238
|
+
},
|
|
239
|
+
...extraMetadata ?? {}
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
});
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
const errText = await res.text().catch(() => "");
|
|
245
|
+
throw new Error(errText || `API error ${res.status}`);
|
|
246
|
+
}
|
|
247
|
+
if (!res.body) throw new Error("No stream body");
|
|
248
|
+
await consumeStream(res.body, (proto) => applyStreamEvent(proto, msgId));
|
|
249
|
+
}, [agentId, workspaceId, source, threadId, userContext, playgroundOverrides, applyStreamEvent]);
|
|
133
250
|
const send = react.useCallback(
|
|
134
251
|
async (text) => {
|
|
135
252
|
const userInput = (text ?? input).trim();
|
|
@@ -143,144 +260,59 @@ function useChat({
|
|
|
143
260
|
setStreaming(true);
|
|
144
261
|
const assistantMsgId = newId();
|
|
145
262
|
streamingMsgIdRef.current = assistantMsgId;
|
|
146
|
-
setMessages((prev) => [
|
|
147
|
-
...prev,
|
|
148
|
-
{ id: assistantMsgId, role: "assistant", parts: [] }
|
|
149
|
-
]);
|
|
263
|
+
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
150
264
|
try {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
265
|
+
await fetchAndStream({ input: userInput, msgId: assistantMsgId });
|
|
266
|
+
} catch {
|
|
267
|
+
setMessages((prev) => {
|
|
268
|
+
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
269
|
+
if (idx === -1) return prev;
|
|
270
|
+
const copy = [...prev];
|
|
271
|
+
const err = copy[idx];
|
|
272
|
+
copy[idx] = { id: err.id, role: err.role, parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }] };
|
|
273
|
+
return copy;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
setStreaming(false);
|
|
277
|
+
streamingMsgIdRef.current = null;
|
|
278
|
+
},
|
|
279
|
+
[input, streaming, fetchAndStream]
|
|
280
|
+
);
|
|
281
|
+
const selectPickerOption = react.useCallback(
|
|
282
|
+
async (pickerId, paramName, value, label) => {
|
|
283
|
+
if (streaming) return;
|
|
284
|
+
setMessages(
|
|
285
|
+
(prev) => prev.map((msg) => ({
|
|
286
|
+
...msg,
|
|
287
|
+
parts: msg.parts.map(
|
|
288
|
+
(part) => part.type === "picker" && part.pickerId === pickerId ? { ...part, selectedValue: value } : part
|
|
289
|
+
)
|
|
290
|
+
}))
|
|
291
|
+
);
|
|
292
|
+
setStreaming(true);
|
|
293
|
+
const assistantMsgId = newId();
|
|
294
|
+
streamingMsgIdRef.current = assistantMsgId;
|
|
295
|
+
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
296
|
+
try {
|
|
297
|
+
await fetchAndStream({
|
|
298
|
+
input: label,
|
|
299
|
+
msgId: assistantMsgId,
|
|
300
|
+
extraMetadata: { __pickerSelection: { pickerId, paramName, value, label } }
|
|
180
301
|
});
|
|
181
|
-
if (!res.ok) {
|
|
182
|
-
const errText = await res.text().catch(() => "");
|
|
183
|
-
throw new Error(errText || `API error ${res.status}`);
|
|
184
|
-
}
|
|
185
|
-
if (!res.body) throw new Error("No stream body");
|
|
186
|
-
const applyEvent = (proto) => {
|
|
187
|
-
if (proto.type === "navigate") {
|
|
188
|
-
if (proto.path.startsWith("/")) onNavigate?.(proto.path);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
setMessages((prev) => {
|
|
192
|
-
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
193
|
-
if (idx === -1) return prev;
|
|
194
|
-
const existing = prev[idx];
|
|
195
|
-
const msg = {
|
|
196
|
-
id: existing.id,
|
|
197
|
-
role: existing.role,
|
|
198
|
-
parts: [...existing.parts]
|
|
199
|
-
};
|
|
200
|
-
switch (proto.type) {
|
|
201
|
-
case "text-delta": {
|
|
202
|
-
const textIdx = msg.parts.findIndex((p) => p.type === "text");
|
|
203
|
-
if (textIdx === -1) {
|
|
204
|
-
msg.parts.push({ type: "text", text: proto.delta });
|
|
205
|
-
} else {
|
|
206
|
-
const p = msg.parts[textIdx];
|
|
207
|
-
msg.parts[textIdx] = { type: "text", text: p.text + proto.delta };
|
|
208
|
-
}
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
case "reasoning-delta": {
|
|
212
|
-
const rIdx = msg.parts.findIndex((p) => p.type === "reasoning");
|
|
213
|
-
if (rIdx === -1) {
|
|
214
|
-
msg.parts.unshift({ type: "reasoning", text: proto.delta });
|
|
215
|
-
} else {
|
|
216
|
-
const p = msg.parts[rIdx];
|
|
217
|
-
msg.parts[rIdx] = { type: "reasoning", text: p.text + proto.delta };
|
|
218
|
-
}
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
case "tool-input-available": {
|
|
222
|
-
msg.parts.push({
|
|
223
|
-
type: "tool",
|
|
224
|
-
toolCallId: proto.toolCallId,
|
|
225
|
-
toolName: proto.toolName,
|
|
226
|
-
input: proto.input ?? {},
|
|
227
|
-
status: "running"
|
|
228
|
-
});
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
case "tool-output-available": {
|
|
232
|
-
const tIdx = msg.parts.findIndex(
|
|
233
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
234
|
-
);
|
|
235
|
-
if (tIdx !== -1) {
|
|
236
|
-
msg.parts[tIdx] = {
|
|
237
|
-
...msg.parts[tIdx],
|
|
238
|
-
status: "done",
|
|
239
|
-
output: proto.output
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
case "tool-output-error": {
|
|
245
|
-
const tIdx = msg.parts.findIndex(
|
|
246
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
247
|
-
);
|
|
248
|
-
if (tIdx !== -1) {
|
|
249
|
-
msg.parts[tIdx] = {
|
|
250
|
-
...msg.parts[tIdx],
|
|
251
|
-
status: "error",
|
|
252
|
-
errorText: proto.errorText
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
default:
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
const copy = [...prev];
|
|
261
|
-
copy[idx] = msg;
|
|
262
|
-
return copy;
|
|
263
|
-
});
|
|
264
|
-
};
|
|
265
|
-
await consumeStream(res.body, applyEvent);
|
|
266
302
|
} catch {
|
|
267
303
|
setMessages((prev) => {
|
|
268
304
|
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
269
305
|
if (idx === -1) return prev;
|
|
270
306
|
const copy = [...prev];
|
|
271
307
|
const err = copy[idx];
|
|
272
|
-
copy[idx] = {
|
|
273
|
-
id: err.id,
|
|
274
|
-
role: err.role,
|
|
275
|
-
parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }]
|
|
276
|
-
};
|
|
308
|
+
copy[idx] = { id: err.id, role: err.role, parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }] };
|
|
277
309
|
return copy;
|
|
278
310
|
});
|
|
279
311
|
}
|
|
280
312
|
setStreaming(false);
|
|
281
313
|
streamingMsgIdRef.current = null;
|
|
282
314
|
},
|
|
283
|
-
[
|
|
315
|
+
[streaming, fetchAndStream]
|
|
284
316
|
);
|
|
285
317
|
const regenerate = react.useCallback(async () => {
|
|
286
318
|
if (streaming) return;
|
|
@@ -295,7 +327,7 @@ function useChat({
|
|
|
295
327
|
});
|
|
296
328
|
await send(lastText);
|
|
297
329
|
}, [streaming, messages, send]);
|
|
298
|
-
return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
|
|
330
|
+
return { messages, input, setInput, streaming, threadId, send, regenerate, reset, selectPickerOption };
|
|
299
331
|
}
|
|
300
332
|
function DefaultIcon() {
|
|
301
333
|
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
|
|
@@ -401,7 +433,6 @@ function ChatHeader({
|
|
|
401
433
|
var Avatar2 = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
|
|
402
434
|
var AvatarImage2 = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
|
|
403
435
|
var AvatarFallback2 = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Fallback, { style: { display: "flex", width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: "9999px", ...style }, ...p });
|
|
404
|
-
var cn = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
405
436
|
var ReactMarkdown = ReactMarkdownLib__default.default;
|
|
406
437
|
function ThinkingDots() {
|
|
407
438
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -470,18 +501,71 @@ function ReasoningBlock({ text }) {
|
|
|
470
501
|
open && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground/80 whitespace-pre-wrap border-l-2 border-muted pl-2 leading-relaxed", children: text })
|
|
471
502
|
] });
|
|
472
503
|
}
|
|
504
|
+
function PickerSelector({
|
|
505
|
+
part,
|
|
506
|
+
disabled,
|
|
507
|
+
onSelect
|
|
508
|
+
}) {
|
|
509
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
510
|
+
"div",
|
|
511
|
+
{
|
|
512
|
+
style: {
|
|
513
|
+
display: "flex",
|
|
514
|
+
flexDirection: "column",
|
|
515
|
+
gap: 8,
|
|
516
|
+
padding: "12px 16px",
|
|
517
|
+
borderRadius: "0 16px 16px 16px",
|
|
518
|
+
backgroundColor: "var(--muted, #f4f4f5)"
|
|
519
|
+
},
|
|
520
|
+
children: [
|
|
521
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, fontSize: 13, color: "var(--muted-foreground, #71717a)", fontWeight: 500 }, children: part.label }),
|
|
522
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: part.options.map((opt) => {
|
|
523
|
+
const isSelected = part.selectedValue === opt.value;
|
|
524
|
+
const isConsumed = !!part.selectedValue && !isSelected;
|
|
525
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
526
|
+
"button",
|
|
527
|
+
{
|
|
528
|
+
onClick: () => {
|
|
529
|
+
if (!part.selectedValue && !disabled) {
|
|
530
|
+
onSelect(part.pickerId, part.paramName, opt.value, opt.label);
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
disabled: disabled || !!part.selectedValue,
|
|
534
|
+
style: {
|
|
535
|
+
fontSize: 13,
|
|
536
|
+
borderRadius: 20,
|
|
537
|
+
padding: "6px 14px",
|
|
538
|
+
border: isSelected ? "1.5px solid var(--primary, #19191c)" : "1.5px solid var(--border, #e4e4e7)",
|
|
539
|
+
backgroundColor: isSelected ? "var(--primary, #19191c)" : "transparent",
|
|
540
|
+
color: isSelected ? "var(--primary-foreground, #fff)" : "var(--foreground, #09090b)",
|
|
541
|
+
cursor: disabled || !!part.selectedValue ? "default" : "pointer",
|
|
542
|
+
opacity: isConsumed ? 0.35 : 1,
|
|
543
|
+
transition: "all 0.15s ease",
|
|
544
|
+
fontWeight: isSelected ? 500 : 400
|
|
545
|
+
},
|
|
546
|
+
children: opt.label
|
|
547
|
+
},
|
|
548
|
+
opt.value
|
|
549
|
+
);
|
|
550
|
+
}) })
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
);
|
|
554
|
+
}
|
|
473
555
|
function MessageBubble({
|
|
474
556
|
message,
|
|
475
557
|
userColor,
|
|
476
558
|
agentName,
|
|
477
559
|
profilePicture,
|
|
478
560
|
isStreaming,
|
|
479
|
-
showThinking = true
|
|
561
|
+
showThinking = true,
|
|
562
|
+
onPickerSelect
|
|
480
563
|
}) {
|
|
481
564
|
const isUser = message.role === "user";
|
|
482
565
|
const textPart = message.parts.find((p) => p.type === "text");
|
|
483
566
|
const reasoningPart = message.parts.find((p) => p.type === "reasoning");
|
|
484
567
|
const toolParts = message.parts.filter((p) => p.type === "tool");
|
|
568
|
+
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
485
569
|
const contrastColor = getContrastColor(userColor);
|
|
486
570
|
if (isUser) {
|
|
487
571
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -494,12 +578,22 @@ function MessageBubble({
|
|
|
494
578
|
) });
|
|
495
579
|
}
|
|
496
580
|
const visibleToolParts = showThinking ? toolParts : [];
|
|
497
|
-
const isEmpty = !textPart?.text && visibleToolParts.length === 0;
|
|
581
|
+
const isEmpty = !textPart?.text && visibleToolParts.length === 0 && pickerParts.length === 0;
|
|
498
582
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
499
583
|
/* @__PURE__ */ jsxRuntime.jsx(Avatar2, { style: { width: 28, height: 28, marginTop: 2, border: "1px solid rgba(0,0,0,0.08)" }, children: profilePicture ? /* @__PURE__ */ jsxRuntime.jsx(AvatarImage2, { src: profilePicture, alt: agentName }) : /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback2, { style: { fontSize: 10, fontWeight: 600, backgroundColor: "var(--primary, #19191c)", color: "var(--primary-foreground, #fff)" }, children: agentName.slice(0, 2).toUpperCase() }) }),
|
|
500
584
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5 min-w-0 max-w-[82%]", children: [
|
|
501
585
|
showThinking && reasoningPart && /* @__PURE__ */ jsxRuntime.jsx(ReasoningBlock, { text: reasoningPart.text }),
|
|
502
586
|
visibleToolParts.map((t) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallBadge, { part: t }, t.toolCallId)),
|
|
587
|
+
pickerParts.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
588
|
+
PickerSelector,
|
|
589
|
+
{
|
|
590
|
+
part: p,
|
|
591
|
+
disabled: isStreaming ?? false,
|
|
592
|
+
onSelect: onPickerSelect ?? (() => {
|
|
593
|
+
})
|
|
594
|
+
},
|
|
595
|
+
p.pickerId
|
|
596
|
+
)),
|
|
503
597
|
isEmpty && isStreaming ? /* @__PURE__ */ jsxRuntime.jsx(ThinkingDots, {}) : textPart?.text ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-sm bg-muted px-4 py-2.5 text-sm leading-relaxed overflow-hidden prose prose-sm max-w-none prose-p:my-1 prose-headings:font-semibold prose-headings:my-1.5 prose-ul:my-1 prose-li:my-0.5 prose-strong:font-semibold", children: [
|
|
504
598
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
505
599
|
ReactMarkdown,
|
|
@@ -544,7 +638,8 @@ function ChatMessages({
|
|
|
544
638
|
initialMessages = [],
|
|
545
639
|
suggestedMessages = [],
|
|
546
640
|
showThinking = true,
|
|
547
|
-
onSuggest
|
|
641
|
+
onSuggest,
|
|
642
|
+
onPickerSelect
|
|
548
643
|
}) {
|
|
549
644
|
const bottomRef = react.useRef(null);
|
|
550
645
|
const showGreeting = messages.length === 0;
|
|
@@ -565,7 +660,8 @@ function ChatMessages({
|
|
|
565
660
|
agentName,
|
|
566
661
|
profilePicture,
|
|
567
662
|
isStreaming: streaming && i === messages.length - 1 && msg.role === "assistant",
|
|
568
|
-
showThinking
|
|
663
|
+
showThinking,
|
|
664
|
+
onPickerSelect
|
|
569
665
|
},
|
|
570
666
|
msg.id
|
|
571
667
|
)) }),
|
|
@@ -599,6 +695,11 @@ function ChatInput({
|
|
|
599
695
|
textareaRef.current.style.height = "auto";
|
|
600
696
|
}
|
|
601
697
|
}, [input]);
|
|
698
|
+
react.useEffect(() => {
|
|
699
|
+
if (!streaming) {
|
|
700
|
+
textareaRef.current?.focus();
|
|
701
|
+
}
|
|
702
|
+
}, [streaming]);
|
|
602
703
|
const handleKeyDown = (e) => {
|
|
603
704
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
604
705
|
e.preventDefault();
|
|
@@ -655,7 +756,6 @@ function ChatInput({
|
|
|
655
756
|
] })
|
|
656
757
|
] });
|
|
657
758
|
}
|
|
658
|
-
var cn3 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
659
759
|
function ChatWidget({
|
|
660
760
|
agentId,
|
|
661
761
|
workspaceId,
|
|
@@ -696,7 +796,7 @@ function ChatWidget({
|
|
|
696
796
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
697
797
|
"div",
|
|
698
798
|
{
|
|
699
|
-
className:
|
|
799
|
+
className: cn(
|
|
700
800
|
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full",
|
|
701
801
|
className
|
|
702
802
|
),
|
|
@@ -726,7 +826,8 @@ function ChatWidget({
|
|
|
726
826
|
initialMessages,
|
|
727
827
|
suggestedMessages,
|
|
728
828
|
showThinking,
|
|
729
|
-
onSuggest: (msg) => chat.send(msg)
|
|
829
|
+
onSuggest: (msg) => chat.send(msg),
|
|
830
|
+
onPickerSelect: chat.selectPickerOption
|
|
730
831
|
}
|
|
731
832
|
),
|
|
732
833
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -825,7 +926,6 @@ function useAutoConfig(agentId, enabled) {
|
|
|
825
926
|
}, [agentId, enabled]);
|
|
826
927
|
return result;
|
|
827
928
|
}
|
|
828
|
-
var cn4 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
829
929
|
var KEY_EXPANDED = "wallavi_bubble_expanded";
|
|
830
930
|
var KEY_DISMISSED = "wallavi_bubble_dismissed";
|
|
831
931
|
function BubbleWidget({
|
|
@@ -873,35 +973,13 @@ function BubbleWidget({
|
|
|
873
973
|
if (localStorage.getItem(KEY_EXPANDED) === "true") setExpanded(true);
|
|
874
974
|
}, []);
|
|
875
975
|
const remote = useAutoConfig(chatProps.agentId, autoConfig);
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
const
|
|
879
|
-
const
|
|
880
|
-
const
|
|
881
|
-
const
|
|
882
|
-
const
|
|
883
|
-
react.useEffect(() => {
|
|
884
|
-
if (remote.loading) return;
|
|
885
|
-
if (!bubbleIconUrlProp) setResolvedBubbleIcon(remote.bubbleIconUrl);
|
|
886
|
-
if (!autoOpenProp) setResolvedAutoOpen(remote.autoOpen);
|
|
887
|
-
if (!keyboardShortcutProp) setResolvedKeyboardShortcut(remote.keyboardShortcut);
|
|
888
|
-
if (!positionProp) setResolvedPosition(remote.position);
|
|
889
|
-
if (bubbleSizeProp == null) setResolvedBubbleSize(remote.bubbleSize);
|
|
890
|
-
if (widthProp == null) setResolvedWidth(remote.panelWidth);
|
|
891
|
-
if (heightProp == null) setResolvedHeight(remote.panelHeight);
|
|
892
|
-
}, [remote.loading]);
|
|
893
|
-
react.useEffect(() => {
|
|
894
|
-
if (autoOpenProp) setResolvedAutoOpen(true);
|
|
895
|
-
}, [autoOpenProp]);
|
|
896
|
-
react.useEffect(() => {
|
|
897
|
-
if (keyboardShortcutProp) setResolvedKeyboardShortcut(true);
|
|
898
|
-
}, [keyboardShortcutProp]);
|
|
899
|
-
react.useEffect(() => {
|
|
900
|
-
if (bubbleIconUrlProp) setResolvedBubbleIcon(bubbleIconUrlProp);
|
|
901
|
-
}, [bubbleIconUrlProp]);
|
|
902
|
-
react.useEffect(() => {
|
|
903
|
-
if (positionProp) setResolvedPosition(positionProp);
|
|
904
|
-
}, [positionProp]);
|
|
976
|
+
const resolvedPosition = positionProp ?? remote.position;
|
|
977
|
+
const resolvedBubbleIcon = bubbleIconUrlProp ?? remote.bubbleIconUrl;
|
|
978
|
+
const resolvedAutoOpen = autoOpenProp || remote.autoOpen;
|
|
979
|
+
const resolvedKeyboardShortcut = keyboardShortcutProp || remote.keyboardShortcut;
|
|
980
|
+
const resolvedBubbleSize = bubbleSizeProp ?? remote.bubbleSize;
|
|
981
|
+
const resolvedWidth = widthProp ?? remote.panelWidth;
|
|
982
|
+
const resolvedHeight = heightProp ?? remote.panelHeight;
|
|
905
983
|
const definedChatProps = Object.fromEntries(
|
|
906
984
|
Object.entries(chatProps).filter(([, v]) => v !== void 0)
|
|
907
985
|
);
|
|
@@ -911,18 +989,18 @@ function BubbleWidget({
|
|
|
911
989
|
agentId: chatProps.agentId,
|
|
912
990
|
agentName: chatProps.agentName
|
|
913
991
|
};
|
|
992
|
+
const setOpenRef = react.useRef(setOpen);
|
|
993
|
+
react.useEffect(() => {
|
|
994
|
+
setOpenRef.current = setOpen;
|
|
995
|
+
});
|
|
914
996
|
react.useEffect(() => {
|
|
915
997
|
if (!resolvedAutoOpen || autoOpenedRef.current) return;
|
|
916
998
|
const dismissedUntil = Number(localStorage.getItem(KEY_DISMISSED) ?? 0);
|
|
917
999
|
if (dismissedUntil < Date.now()) {
|
|
918
1000
|
autoOpenedRef.current = true;
|
|
919
|
-
|
|
1001
|
+
setOpenRef.current(true);
|
|
920
1002
|
}
|
|
921
1003
|
}, [resolvedAutoOpen]);
|
|
922
|
-
const setOpenRef = react.useRef(setOpen);
|
|
923
|
-
react.useEffect(() => {
|
|
924
|
-
setOpenRef.current = setOpen;
|
|
925
|
-
});
|
|
926
1004
|
react.useEffect(() => {
|
|
927
1005
|
if (!resolvedKeyboardShortcut) return;
|
|
928
1006
|
const onKey = (e) => {
|
|
@@ -988,7 +1066,7 @@ function BubbleWidget({
|
|
|
988
1066
|
onClose: handleClose,
|
|
989
1067
|
onExpand: toggleExpanded,
|
|
990
1068
|
expanded,
|
|
991
|
-
className:
|
|
1069
|
+
className: cn("shadow-2xl h-full", panelClassName)
|
|
992
1070
|
}
|
|
993
1071
|
)
|
|
994
1072
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
2
2
|
import { X, RotateCcw, Loader2, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -8,6 +8,7 @@ import ReactMarkdownLib from 'react-markdown';
|
|
|
8
8
|
import remarkGfm from 'remark-gfm';
|
|
9
9
|
|
|
10
10
|
// src/bubble-widget.tsx
|
|
11
|
+
var cn = (...inputs) => twMerge(clsx(inputs));
|
|
11
12
|
|
|
12
13
|
// src/types.ts
|
|
13
14
|
function getContrastColor(hex) {
|
|
@@ -57,6 +58,10 @@ function useChat({
|
|
|
57
58
|
playgroundOverrides
|
|
58
59
|
}) {
|
|
59
60
|
const persistKey = persist ? `wallavi_${agentId}` : null;
|
|
61
|
+
const onNavigateRef = useRef(onNavigate);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
onNavigateRef.current = onNavigate;
|
|
64
|
+
});
|
|
60
65
|
const [messages, setMessages] = useState(() => {
|
|
61
66
|
if (!persistKey || typeof window === "undefined") return [];
|
|
62
67
|
try {
|
|
@@ -104,6 +109,118 @@ function useChat({
|
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
111
|
}, [persistKey]);
|
|
112
|
+
const applyStreamEvent = useCallback((proto, msgId) => {
|
|
113
|
+
if (proto.type === "navigate") {
|
|
114
|
+
if (proto.path.startsWith("/")) onNavigateRef.current?.(proto.path);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
setMessages((prev) => {
|
|
118
|
+
const idx = prev.findIndex((m) => m.id === msgId);
|
|
119
|
+
if (idx === -1) return prev;
|
|
120
|
+
const existing = prev[idx];
|
|
121
|
+
const msg = { id: existing.id, role: existing.role, parts: [...existing.parts] };
|
|
122
|
+
switch (proto.type) {
|
|
123
|
+
case "text-delta": {
|
|
124
|
+
const textIdx = msg.parts.findIndex((p) => p.type === "text");
|
|
125
|
+
if (textIdx === -1) {
|
|
126
|
+
msg.parts.push({ type: "text", text: proto.delta });
|
|
127
|
+
} else {
|
|
128
|
+
const p = msg.parts[textIdx];
|
|
129
|
+
msg.parts[textIdx] = { type: "text", text: p.text + proto.delta };
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "reasoning-delta": {
|
|
134
|
+
const rIdx = msg.parts.findIndex((p) => p.type === "reasoning");
|
|
135
|
+
if (rIdx === -1) {
|
|
136
|
+
msg.parts.unshift({ type: "reasoning", text: proto.delta });
|
|
137
|
+
} else {
|
|
138
|
+
const p = msg.parts[rIdx];
|
|
139
|
+
msg.parts[rIdx] = { type: "reasoning", text: p.text + proto.delta };
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "tool-input-available": {
|
|
144
|
+
msg.parts.push({
|
|
145
|
+
type: "tool",
|
|
146
|
+
toolCallId: proto.toolCallId,
|
|
147
|
+
toolName: proto.toolName,
|
|
148
|
+
input: proto.input ?? {},
|
|
149
|
+
status: "running"
|
|
150
|
+
});
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "tool-output-available": {
|
|
154
|
+
const tIdx = msg.parts.findIndex(
|
|
155
|
+
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
156
|
+
);
|
|
157
|
+
if (tIdx !== -1) {
|
|
158
|
+
msg.parts[tIdx] = { ...msg.parts[tIdx], status: "done", output: proto.output };
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "tool-output-error": {
|
|
163
|
+
const tIdx = msg.parts.findIndex(
|
|
164
|
+
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
165
|
+
);
|
|
166
|
+
if (tIdx !== -1) {
|
|
167
|
+
msg.parts[tIdx] = { ...msg.parts[tIdx], status: "error", errorText: proto.errorText };
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "picker": {
|
|
172
|
+
msg.parts.push({
|
|
173
|
+
type: "picker",
|
|
174
|
+
pickerId: proto.pickerId,
|
|
175
|
+
paramName: proto.paramName,
|
|
176
|
+
toolName: proto.toolName,
|
|
177
|
+
label: proto.label,
|
|
178
|
+
options: proto.options
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const copy = [...prev];
|
|
184
|
+
copy[idx] = msg;
|
|
185
|
+
return copy;
|
|
186
|
+
});
|
|
187
|
+
}, []);
|
|
188
|
+
const fetchAndStream = useCallback(async (opts) => {
|
|
189
|
+
const { input: userInput, msgId, extraMetadata } = opts;
|
|
190
|
+
const isPrivate = Boolean(workspaceId);
|
|
191
|
+
const token = isPrivate && typeof window !== "undefined" ? await window.Clerk?.session?.getToken() : null;
|
|
192
|
+
const url = isPrivate ? `${API_URL}/api/threads/${threadId}/stream` : `${API_URL}/api/chat/stream`;
|
|
193
|
+
const res = await fetch(url, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: {
|
|
196
|
+
"Content-Type": "application/json",
|
|
197
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
input: userInput,
|
|
201
|
+
agentId,
|
|
202
|
+
...isPrivate ? { workspaceId, ...playgroundOverrides ? { playgroundOverrides } : {} } : { threadId },
|
|
203
|
+
source,
|
|
204
|
+
...userContext?.userName ? { userName: userContext.userName } : {},
|
|
205
|
+
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
206
|
+
userMetadata: {
|
|
207
|
+
...userContext?.metadata ?? {},
|
|
208
|
+
...userContext?.pageContext ? { pageContext: userContext.pageContext } : {},
|
|
209
|
+
headers: {
|
|
210
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
211
|
+
...userContext?.headers ?? {}
|
|
212
|
+
},
|
|
213
|
+
...extraMetadata ?? {}
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
});
|
|
217
|
+
if (!res.ok) {
|
|
218
|
+
const errText = await res.text().catch(() => "");
|
|
219
|
+
throw new Error(errText || `API error ${res.status}`);
|
|
220
|
+
}
|
|
221
|
+
if (!res.body) throw new Error("No stream body");
|
|
222
|
+
await consumeStream(res.body, (proto) => applyStreamEvent(proto, msgId));
|
|
223
|
+
}, [agentId, workspaceId, source, threadId, userContext, playgroundOverrides, applyStreamEvent]);
|
|
107
224
|
const send = useCallback(
|
|
108
225
|
async (text) => {
|
|
109
226
|
const userInput = (text ?? input).trim();
|
|
@@ -117,144 +234,59 @@ function useChat({
|
|
|
117
234
|
setStreaming(true);
|
|
118
235
|
const assistantMsgId = newId();
|
|
119
236
|
streamingMsgIdRef.current = assistantMsgId;
|
|
120
|
-
setMessages((prev) => [
|
|
121
|
-
...prev,
|
|
122
|
-
{ id: assistantMsgId, role: "assistant", parts: [] }
|
|
123
|
-
]);
|
|
237
|
+
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
124
238
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
239
|
+
await fetchAndStream({ input: userInput, msgId: assistantMsgId });
|
|
240
|
+
} catch {
|
|
241
|
+
setMessages((prev) => {
|
|
242
|
+
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
243
|
+
if (idx === -1) return prev;
|
|
244
|
+
const copy = [...prev];
|
|
245
|
+
const err = copy[idx];
|
|
246
|
+
copy[idx] = { id: err.id, role: err.role, parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }] };
|
|
247
|
+
return copy;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
setStreaming(false);
|
|
251
|
+
streamingMsgIdRef.current = null;
|
|
252
|
+
},
|
|
253
|
+
[input, streaming, fetchAndStream]
|
|
254
|
+
);
|
|
255
|
+
const selectPickerOption = useCallback(
|
|
256
|
+
async (pickerId, paramName, value, label) => {
|
|
257
|
+
if (streaming) return;
|
|
258
|
+
setMessages(
|
|
259
|
+
(prev) => prev.map((msg) => ({
|
|
260
|
+
...msg,
|
|
261
|
+
parts: msg.parts.map(
|
|
262
|
+
(part) => part.type === "picker" && part.pickerId === pickerId ? { ...part, selectedValue: value } : part
|
|
263
|
+
)
|
|
264
|
+
}))
|
|
265
|
+
);
|
|
266
|
+
setStreaming(true);
|
|
267
|
+
const assistantMsgId = newId();
|
|
268
|
+
streamingMsgIdRef.current = assistantMsgId;
|
|
269
|
+
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
270
|
+
try {
|
|
271
|
+
await fetchAndStream({
|
|
272
|
+
input: label,
|
|
273
|
+
msgId: assistantMsgId,
|
|
274
|
+
extraMetadata: { __pickerSelection: { pickerId, paramName, value, label } }
|
|
154
275
|
});
|
|
155
|
-
if (!res.ok) {
|
|
156
|
-
const errText = await res.text().catch(() => "");
|
|
157
|
-
throw new Error(errText || `API error ${res.status}`);
|
|
158
|
-
}
|
|
159
|
-
if (!res.body) throw new Error("No stream body");
|
|
160
|
-
const applyEvent = (proto) => {
|
|
161
|
-
if (proto.type === "navigate") {
|
|
162
|
-
if (proto.path.startsWith("/")) onNavigate?.(proto.path);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
setMessages((prev) => {
|
|
166
|
-
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
167
|
-
if (idx === -1) return prev;
|
|
168
|
-
const existing = prev[idx];
|
|
169
|
-
const msg = {
|
|
170
|
-
id: existing.id,
|
|
171
|
-
role: existing.role,
|
|
172
|
-
parts: [...existing.parts]
|
|
173
|
-
};
|
|
174
|
-
switch (proto.type) {
|
|
175
|
-
case "text-delta": {
|
|
176
|
-
const textIdx = msg.parts.findIndex((p) => p.type === "text");
|
|
177
|
-
if (textIdx === -1) {
|
|
178
|
-
msg.parts.push({ type: "text", text: proto.delta });
|
|
179
|
-
} else {
|
|
180
|
-
const p = msg.parts[textIdx];
|
|
181
|
-
msg.parts[textIdx] = { type: "text", text: p.text + proto.delta };
|
|
182
|
-
}
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
case "reasoning-delta": {
|
|
186
|
-
const rIdx = msg.parts.findIndex((p) => p.type === "reasoning");
|
|
187
|
-
if (rIdx === -1) {
|
|
188
|
-
msg.parts.unshift({ type: "reasoning", text: proto.delta });
|
|
189
|
-
} else {
|
|
190
|
-
const p = msg.parts[rIdx];
|
|
191
|
-
msg.parts[rIdx] = { type: "reasoning", text: p.text + proto.delta };
|
|
192
|
-
}
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
case "tool-input-available": {
|
|
196
|
-
msg.parts.push({
|
|
197
|
-
type: "tool",
|
|
198
|
-
toolCallId: proto.toolCallId,
|
|
199
|
-
toolName: proto.toolName,
|
|
200
|
-
input: proto.input ?? {},
|
|
201
|
-
status: "running"
|
|
202
|
-
});
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
case "tool-output-available": {
|
|
206
|
-
const tIdx = msg.parts.findIndex(
|
|
207
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
208
|
-
);
|
|
209
|
-
if (tIdx !== -1) {
|
|
210
|
-
msg.parts[tIdx] = {
|
|
211
|
-
...msg.parts[tIdx],
|
|
212
|
-
status: "done",
|
|
213
|
-
output: proto.output
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
case "tool-output-error": {
|
|
219
|
-
const tIdx = msg.parts.findIndex(
|
|
220
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
221
|
-
);
|
|
222
|
-
if (tIdx !== -1) {
|
|
223
|
-
msg.parts[tIdx] = {
|
|
224
|
-
...msg.parts[tIdx],
|
|
225
|
-
status: "error",
|
|
226
|
-
errorText: proto.errorText
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
default:
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
const copy = [...prev];
|
|
235
|
-
copy[idx] = msg;
|
|
236
|
-
return copy;
|
|
237
|
-
});
|
|
238
|
-
};
|
|
239
|
-
await consumeStream(res.body, applyEvent);
|
|
240
276
|
} catch {
|
|
241
277
|
setMessages((prev) => {
|
|
242
278
|
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
243
279
|
if (idx === -1) return prev;
|
|
244
280
|
const copy = [...prev];
|
|
245
281
|
const err = copy[idx];
|
|
246
|
-
copy[idx] = {
|
|
247
|
-
id: err.id,
|
|
248
|
-
role: err.role,
|
|
249
|
-
parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }]
|
|
250
|
-
};
|
|
282
|
+
copy[idx] = { id: err.id, role: err.role, parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }] };
|
|
251
283
|
return copy;
|
|
252
284
|
});
|
|
253
285
|
}
|
|
254
286
|
setStreaming(false);
|
|
255
287
|
streamingMsgIdRef.current = null;
|
|
256
288
|
},
|
|
257
|
-
[
|
|
289
|
+
[streaming, fetchAndStream]
|
|
258
290
|
);
|
|
259
291
|
const regenerate = useCallback(async () => {
|
|
260
292
|
if (streaming) return;
|
|
@@ -269,7 +301,7 @@ function useChat({
|
|
|
269
301
|
});
|
|
270
302
|
await send(lastText);
|
|
271
303
|
}, [streaming, messages, send]);
|
|
272
|
-
return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
|
|
304
|
+
return { messages, input, setInput, streaming, threadId, send, regenerate, reset, selectPickerOption };
|
|
273
305
|
}
|
|
274
306
|
function DefaultIcon() {
|
|
275
307
|
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
|
|
@@ -375,7 +407,6 @@ function ChatHeader({
|
|
|
375
407
|
var Avatar2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
|
|
376
408
|
var AvatarImage2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
|
|
377
409
|
var AvatarFallback2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { style: { display: "flex", width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: "9999px", ...style }, ...p });
|
|
378
|
-
var cn = (...inputs) => twMerge(clsx(inputs));
|
|
379
410
|
var ReactMarkdown = ReactMarkdownLib;
|
|
380
411
|
function ThinkingDots() {
|
|
381
412
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -444,18 +475,71 @@ function ReasoningBlock({ text }) {
|
|
|
444
475
|
open && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground/80 whitespace-pre-wrap border-l-2 border-muted pl-2 leading-relaxed", children: text })
|
|
445
476
|
] });
|
|
446
477
|
}
|
|
478
|
+
function PickerSelector({
|
|
479
|
+
part,
|
|
480
|
+
disabled,
|
|
481
|
+
onSelect
|
|
482
|
+
}) {
|
|
483
|
+
return /* @__PURE__ */ jsxs(
|
|
484
|
+
"div",
|
|
485
|
+
{
|
|
486
|
+
style: {
|
|
487
|
+
display: "flex",
|
|
488
|
+
flexDirection: "column",
|
|
489
|
+
gap: 8,
|
|
490
|
+
padding: "12px 16px",
|
|
491
|
+
borderRadius: "0 16px 16px 16px",
|
|
492
|
+
backgroundColor: "var(--muted, #f4f4f5)"
|
|
493
|
+
},
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 13, color: "var(--muted-foreground, #71717a)", fontWeight: 500 }, children: part.label }),
|
|
496
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: part.options.map((opt) => {
|
|
497
|
+
const isSelected = part.selectedValue === opt.value;
|
|
498
|
+
const isConsumed = !!part.selectedValue && !isSelected;
|
|
499
|
+
return /* @__PURE__ */ jsx(
|
|
500
|
+
"button",
|
|
501
|
+
{
|
|
502
|
+
onClick: () => {
|
|
503
|
+
if (!part.selectedValue && !disabled) {
|
|
504
|
+
onSelect(part.pickerId, part.paramName, opt.value, opt.label);
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
disabled: disabled || !!part.selectedValue,
|
|
508
|
+
style: {
|
|
509
|
+
fontSize: 13,
|
|
510
|
+
borderRadius: 20,
|
|
511
|
+
padding: "6px 14px",
|
|
512
|
+
border: isSelected ? "1.5px solid var(--primary, #19191c)" : "1.5px solid var(--border, #e4e4e7)",
|
|
513
|
+
backgroundColor: isSelected ? "var(--primary, #19191c)" : "transparent",
|
|
514
|
+
color: isSelected ? "var(--primary-foreground, #fff)" : "var(--foreground, #09090b)",
|
|
515
|
+
cursor: disabled || !!part.selectedValue ? "default" : "pointer",
|
|
516
|
+
opacity: isConsumed ? 0.35 : 1,
|
|
517
|
+
transition: "all 0.15s ease",
|
|
518
|
+
fontWeight: isSelected ? 500 : 400
|
|
519
|
+
},
|
|
520
|
+
children: opt.label
|
|
521
|
+
},
|
|
522
|
+
opt.value
|
|
523
|
+
);
|
|
524
|
+
}) })
|
|
525
|
+
]
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
}
|
|
447
529
|
function MessageBubble({
|
|
448
530
|
message,
|
|
449
531
|
userColor,
|
|
450
532
|
agentName,
|
|
451
533
|
profilePicture,
|
|
452
534
|
isStreaming,
|
|
453
|
-
showThinking = true
|
|
535
|
+
showThinking = true,
|
|
536
|
+
onPickerSelect
|
|
454
537
|
}) {
|
|
455
538
|
const isUser = message.role === "user";
|
|
456
539
|
const textPart = message.parts.find((p) => p.type === "text");
|
|
457
540
|
const reasoningPart = message.parts.find((p) => p.type === "reasoning");
|
|
458
541
|
const toolParts = message.parts.filter((p) => p.type === "tool");
|
|
542
|
+
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
459
543
|
const contrastColor = getContrastColor(userColor);
|
|
460
544
|
if (isUser) {
|
|
461
545
|
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
@@ -468,12 +552,22 @@ function MessageBubble({
|
|
|
468
552
|
) });
|
|
469
553
|
}
|
|
470
554
|
const visibleToolParts = showThinking ? toolParts : [];
|
|
471
|
-
const isEmpty = !textPart?.text && visibleToolParts.length === 0;
|
|
555
|
+
const isEmpty = !textPart?.text && visibleToolParts.length === 0 && pickerParts.length === 0;
|
|
472
556
|
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
473
557
|
/* @__PURE__ */ jsx(Avatar2, { style: { width: 28, height: 28, marginTop: 2, border: "1px solid rgba(0,0,0,0.08)" }, children: profilePicture ? /* @__PURE__ */ jsx(AvatarImage2, { src: profilePicture, alt: agentName }) : /* @__PURE__ */ jsx(AvatarFallback2, { style: { fontSize: 10, fontWeight: 600, backgroundColor: "var(--primary, #19191c)", color: "var(--primary-foreground, #fff)" }, children: agentName.slice(0, 2).toUpperCase() }) }),
|
|
474
558
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 min-w-0 max-w-[82%]", children: [
|
|
475
559
|
showThinking && reasoningPart && /* @__PURE__ */ jsx(ReasoningBlock, { text: reasoningPart.text }),
|
|
476
560
|
visibleToolParts.map((t) => /* @__PURE__ */ jsx(ToolCallBadge, { part: t }, t.toolCallId)),
|
|
561
|
+
pickerParts.map((p) => /* @__PURE__ */ jsx(
|
|
562
|
+
PickerSelector,
|
|
563
|
+
{
|
|
564
|
+
part: p,
|
|
565
|
+
disabled: isStreaming ?? false,
|
|
566
|
+
onSelect: onPickerSelect ?? (() => {
|
|
567
|
+
})
|
|
568
|
+
},
|
|
569
|
+
p.pickerId
|
|
570
|
+
)),
|
|
477
571
|
isEmpty && isStreaming ? /* @__PURE__ */ jsx(ThinkingDots, {}) : textPart?.text ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-sm bg-muted px-4 py-2.5 text-sm leading-relaxed overflow-hidden prose prose-sm max-w-none prose-p:my-1 prose-headings:font-semibold prose-headings:my-1.5 prose-ul:my-1 prose-li:my-0.5 prose-strong:font-semibold", children: [
|
|
478
572
|
/* @__PURE__ */ jsx(
|
|
479
573
|
ReactMarkdown,
|
|
@@ -518,7 +612,8 @@ function ChatMessages({
|
|
|
518
612
|
initialMessages = [],
|
|
519
613
|
suggestedMessages = [],
|
|
520
614
|
showThinking = true,
|
|
521
|
-
onSuggest
|
|
615
|
+
onSuggest,
|
|
616
|
+
onPickerSelect
|
|
522
617
|
}) {
|
|
523
618
|
const bottomRef = useRef(null);
|
|
524
619
|
const showGreeting = messages.length === 0;
|
|
@@ -539,7 +634,8 @@ function ChatMessages({
|
|
|
539
634
|
agentName,
|
|
540
635
|
profilePicture,
|
|
541
636
|
isStreaming: streaming && i === messages.length - 1 && msg.role === "assistant",
|
|
542
|
-
showThinking
|
|
637
|
+
showThinking,
|
|
638
|
+
onPickerSelect
|
|
543
639
|
},
|
|
544
640
|
msg.id
|
|
545
641
|
)) }),
|
|
@@ -573,6 +669,11 @@ function ChatInput({
|
|
|
573
669
|
textareaRef.current.style.height = "auto";
|
|
574
670
|
}
|
|
575
671
|
}, [input]);
|
|
672
|
+
useEffect(() => {
|
|
673
|
+
if (!streaming) {
|
|
674
|
+
textareaRef.current?.focus();
|
|
675
|
+
}
|
|
676
|
+
}, [streaming]);
|
|
576
677
|
const handleKeyDown = (e) => {
|
|
577
678
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
578
679
|
e.preventDefault();
|
|
@@ -629,7 +730,6 @@ function ChatInput({
|
|
|
629
730
|
] })
|
|
630
731
|
] });
|
|
631
732
|
}
|
|
632
|
-
var cn3 = (...inputs) => twMerge(clsx(inputs));
|
|
633
733
|
function ChatWidget({
|
|
634
734
|
agentId,
|
|
635
735
|
workspaceId,
|
|
@@ -670,7 +770,7 @@ function ChatWidget({
|
|
|
670
770
|
return /* @__PURE__ */ jsxs(
|
|
671
771
|
"div",
|
|
672
772
|
{
|
|
673
|
-
className:
|
|
773
|
+
className: cn(
|
|
674
774
|
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full",
|
|
675
775
|
className
|
|
676
776
|
),
|
|
@@ -700,7 +800,8 @@ function ChatWidget({
|
|
|
700
800
|
initialMessages,
|
|
701
801
|
suggestedMessages,
|
|
702
802
|
showThinking,
|
|
703
|
-
onSuggest: (msg) => chat.send(msg)
|
|
803
|
+
onSuggest: (msg) => chat.send(msg),
|
|
804
|
+
onPickerSelect: chat.selectPickerOption
|
|
704
805
|
}
|
|
705
806
|
),
|
|
706
807
|
/* @__PURE__ */ jsx(
|
|
@@ -799,7 +900,6 @@ function useAutoConfig(agentId, enabled) {
|
|
|
799
900
|
}, [agentId, enabled]);
|
|
800
901
|
return result;
|
|
801
902
|
}
|
|
802
|
-
var cn4 = (...inputs) => twMerge(clsx(inputs));
|
|
803
903
|
var KEY_EXPANDED = "wallavi_bubble_expanded";
|
|
804
904
|
var KEY_DISMISSED = "wallavi_bubble_dismissed";
|
|
805
905
|
function BubbleWidget({
|
|
@@ -847,35 +947,13 @@ function BubbleWidget({
|
|
|
847
947
|
if (localStorage.getItem(KEY_EXPANDED) === "true") setExpanded(true);
|
|
848
948
|
}, []);
|
|
849
949
|
const remote = useAutoConfig(chatProps.agentId, autoConfig);
|
|
850
|
-
const
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
const
|
|
854
|
-
const
|
|
855
|
-
const
|
|
856
|
-
const
|
|
857
|
-
useEffect(() => {
|
|
858
|
-
if (remote.loading) return;
|
|
859
|
-
if (!bubbleIconUrlProp) setResolvedBubbleIcon(remote.bubbleIconUrl);
|
|
860
|
-
if (!autoOpenProp) setResolvedAutoOpen(remote.autoOpen);
|
|
861
|
-
if (!keyboardShortcutProp) setResolvedKeyboardShortcut(remote.keyboardShortcut);
|
|
862
|
-
if (!positionProp) setResolvedPosition(remote.position);
|
|
863
|
-
if (bubbleSizeProp == null) setResolvedBubbleSize(remote.bubbleSize);
|
|
864
|
-
if (widthProp == null) setResolvedWidth(remote.panelWidth);
|
|
865
|
-
if (heightProp == null) setResolvedHeight(remote.panelHeight);
|
|
866
|
-
}, [remote.loading]);
|
|
867
|
-
useEffect(() => {
|
|
868
|
-
if (autoOpenProp) setResolvedAutoOpen(true);
|
|
869
|
-
}, [autoOpenProp]);
|
|
870
|
-
useEffect(() => {
|
|
871
|
-
if (keyboardShortcutProp) setResolvedKeyboardShortcut(true);
|
|
872
|
-
}, [keyboardShortcutProp]);
|
|
873
|
-
useEffect(() => {
|
|
874
|
-
if (bubbleIconUrlProp) setResolvedBubbleIcon(bubbleIconUrlProp);
|
|
875
|
-
}, [bubbleIconUrlProp]);
|
|
876
|
-
useEffect(() => {
|
|
877
|
-
if (positionProp) setResolvedPosition(positionProp);
|
|
878
|
-
}, [positionProp]);
|
|
950
|
+
const resolvedPosition = positionProp ?? remote.position;
|
|
951
|
+
const resolvedBubbleIcon = bubbleIconUrlProp ?? remote.bubbleIconUrl;
|
|
952
|
+
const resolvedAutoOpen = autoOpenProp || remote.autoOpen;
|
|
953
|
+
const resolvedKeyboardShortcut = keyboardShortcutProp || remote.keyboardShortcut;
|
|
954
|
+
const resolvedBubbleSize = bubbleSizeProp ?? remote.bubbleSize;
|
|
955
|
+
const resolvedWidth = widthProp ?? remote.panelWidth;
|
|
956
|
+
const resolvedHeight = heightProp ?? remote.panelHeight;
|
|
879
957
|
const definedChatProps = Object.fromEntries(
|
|
880
958
|
Object.entries(chatProps).filter(([, v]) => v !== void 0)
|
|
881
959
|
);
|
|
@@ -885,18 +963,18 @@ function BubbleWidget({
|
|
|
885
963
|
agentId: chatProps.agentId,
|
|
886
964
|
agentName: chatProps.agentName
|
|
887
965
|
};
|
|
966
|
+
const setOpenRef = useRef(setOpen);
|
|
967
|
+
useEffect(() => {
|
|
968
|
+
setOpenRef.current = setOpen;
|
|
969
|
+
});
|
|
888
970
|
useEffect(() => {
|
|
889
971
|
if (!resolvedAutoOpen || autoOpenedRef.current) return;
|
|
890
972
|
const dismissedUntil = Number(localStorage.getItem(KEY_DISMISSED) ?? 0);
|
|
891
973
|
if (dismissedUntil < Date.now()) {
|
|
892
974
|
autoOpenedRef.current = true;
|
|
893
|
-
|
|
975
|
+
setOpenRef.current(true);
|
|
894
976
|
}
|
|
895
977
|
}, [resolvedAutoOpen]);
|
|
896
|
-
const setOpenRef = useRef(setOpen);
|
|
897
|
-
useEffect(() => {
|
|
898
|
-
setOpenRef.current = setOpen;
|
|
899
|
-
});
|
|
900
978
|
useEffect(() => {
|
|
901
979
|
if (!resolvedKeyboardShortcut) return;
|
|
902
980
|
const onKey = (e) => {
|
|
@@ -962,7 +1040,7 @@ function BubbleWidget({
|
|
|
962
1040
|
onClose: handleClose,
|
|
963
1041
|
onExpand: toggleExpanded,
|
|
964
1042
|
expanded,
|
|
965
|
-
className:
|
|
1043
|
+
className: cn("shadow-2xl h-full", panelClassName)
|
|
966
1044
|
}
|
|
967
1045
|
)
|
|
968
1046
|
}
|