@wallavi/widget 1.4.5 → 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 +228 -130
- package/dist/index.mjs +228 -130
- 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
|
@@ -135,6 +135,118 @@ function useChat({
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
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]);
|
|
138
250
|
const send = react.useCallback(
|
|
139
251
|
async (text) => {
|
|
140
252
|
const userInput = (text ?? input).trim();
|
|
@@ -148,144 +260,59 @@ function useChat({
|
|
|
148
260
|
setStreaming(true);
|
|
149
261
|
const assistantMsgId = newId();
|
|
150
262
|
streamingMsgIdRef.current = assistantMsgId;
|
|
151
|
-
setMessages((prev) => [
|
|
152
|
-
...prev,
|
|
153
|
-
{ id: assistantMsgId, role: "assistant", parts: [] }
|
|
154
|
-
]);
|
|
263
|
+
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
155
264
|
try {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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 } }
|
|
185
301
|
});
|
|
186
|
-
if (!res.ok) {
|
|
187
|
-
const errText = await res.text().catch(() => "");
|
|
188
|
-
throw new Error(errText || `API error ${res.status}`);
|
|
189
|
-
}
|
|
190
|
-
if (!res.body) throw new Error("No stream body");
|
|
191
|
-
const applyEvent = (proto) => {
|
|
192
|
-
if (proto.type === "navigate") {
|
|
193
|
-
if (proto.path.startsWith("/")) onNavigateRef.current?.(proto.path);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
setMessages((prev) => {
|
|
197
|
-
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
198
|
-
if (idx === -1) return prev;
|
|
199
|
-
const existing = prev[idx];
|
|
200
|
-
const msg = {
|
|
201
|
-
id: existing.id,
|
|
202
|
-
role: existing.role,
|
|
203
|
-
parts: [...existing.parts]
|
|
204
|
-
};
|
|
205
|
-
switch (proto.type) {
|
|
206
|
-
case "text-delta": {
|
|
207
|
-
const textIdx = msg.parts.findIndex((p) => p.type === "text");
|
|
208
|
-
if (textIdx === -1) {
|
|
209
|
-
msg.parts.push({ type: "text", text: proto.delta });
|
|
210
|
-
} else {
|
|
211
|
-
const p = msg.parts[textIdx];
|
|
212
|
-
msg.parts[textIdx] = { type: "text", text: p.text + proto.delta };
|
|
213
|
-
}
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
case "reasoning-delta": {
|
|
217
|
-
const rIdx = msg.parts.findIndex((p) => p.type === "reasoning");
|
|
218
|
-
if (rIdx === -1) {
|
|
219
|
-
msg.parts.unshift({ type: "reasoning", text: proto.delta });
|
|
220
|
-
} else {
|
|
221
|
-
const p = msg.parts[rIdx];
|
|
222
|
-
msg.parts[rIdx] = { type: "reasoning", text: p.text + proto.delta };
|
|
223
|
-
}
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
case "tool-input-available": {
|
|
227
|
-
msg.parts.push({
|
|
228
|
-
type: "tool",
|
|
229
|
-
toolCallId: proto.toolCallId,
|
|
230
|
-
toolName: proto.toolName,
|
|
231
|
-
input: proto.input ?? {},
|
|
232
|
-
status: "running"
|
|
233
|
-
});
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
case "tool-output-available": {
|
|
237
|
-
const tIdx = msg.parts.findIndex(
|
|
238
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
239
|
-
);
|
|
240
|
-
if (tIdx !== -1) {
|
|
241
|
-
msg.parts[tIdx] = {
|
|
242
|
-
...msg.parts[tIdx],
|
|
243
|
-
status: "done",
|
|
244
|
-
output: proto.output
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
case "tool-output-error": {
|
|
250
|
-
const tIdx = msg.parts.findIndex(
|
|
251
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
252
|
-
);
|
|
253
|
-
if (tIdx !== -1) {
|
|
254
|
-
msg.parts[tIdx] = {
|
|
255
|
-
...msg.parts[tIdx],
|
|
256
|
-
status: "error",
|
|
257
|
-
errorText: proto.errorText
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
default:
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
const copy = [...prev];
|
|
266
|
-
copy[idx] = msg;
|
|
267
|
-
return copy;
|
|
268
|
-
});
|
|
269
|
-
};
|
|
270
|
-
await consumeStream(res.body, applyEvent);
|
|
271
302
|
} catch {
|
|
272
303
|
setMessages((prev) => {
|
|
273
304
|
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
274
305
|
if (idx === -1) return prev;
|
|
275
306
|
const copy = [...prev];
|
|
276
307
|
const err = copy[idx];
|
|
277
|
-
copy[idx] = {
|
|
278
|
-
id: err.id,
|
|
279
|
-
role: err.role,
|
|
280
|
-
parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }]
|
|
281
|
-
};
|
|
308
|
+
copy[idx] = { id: err.id, role: err.role, parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }] };
|
|
282
309
|
return copy;
|
|
283
310
|
});
|
|
284
311
|
}
|
|
285
312
|
setStreaming(false);
|
|
286
313
|
streamingMsgIdRef.current = null;
|
|
287
314
|
},
|
|
288
|
-
[
|
|
315
|
+
[streaming, fetchAndStream]
|
|
289
316
|
);
|
|
290
317
|
const regenerate = react.useCallback(async () => {
|
|
291
318
|
if (streaming) return;
|
|
@@ -300,7 +327,7 @@ function useChat({
|
|
|
300
327
|
});
|
|
301
328
|
await send(lastText);
|
|
302
329
|
}, [streaming, messages, send]);
|
|
303
|
-
return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
|
|
330
|
+
return { messages, input, setInput, streaming, threadId, send, regenerate, reset, selectPickerOption };
|
|
304
331
|
}
|
|
305
332
|
function DefaultIcon() {
|
|
306
333
|
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
|
|
@@ -474,18 +501,71 @@ function ReasoningBlock({ text }) {
|
|
|
474
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 })
|
|
475
502
|
] });
|
|
476
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
|
+
}
|
|
477
555
|
function MessageBubble({
|
|
478
556
|
message,
|
|
479
557
|
userColor,
|
|
480
558
|
agentName,
|
|
481
559
|
profilePicture,
|
|
482
560
|
isStreaming,
|
|
483
|
-
showThinking = true
|
|
561
|
+
showThinking = true,
|
|
562
|
+
onPickerSelect
|
|
484
563
|
}) {
|
|
485
564
|
const isUser = message.role === "user";
|
|
486
565
|
const textPart = message.parts.find((p) => p.type === "text");
|
|
487
566
|
const reasoningPart = message.parts.find((p) => p.type === "reasoning");
|
|
488
567
|
const toolParts = message.parts.filter((p) => p.type === "tool");
|
|
568
|
+
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
489
569
|
const contrastColor = getContrastColor(userColor);
|
|
490
570
|
if (isUser) {
|
|
491
571
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -498,12 +578,22 @@ function MessageBubble({
|
|
|
498
578
|
) });
|
|
499
579
|
}
|
|
500
580
|
const visibleToolParts = showThinking ? toolParts : [];
|
|
501
|
-
const isEmpty = !textPart?.text && visibleToolParts.length === 0;
|
|
581
|
+
const isEmpty = !textPart?.text && visibleToolParts.length === 0 && pickerParts.length === 0;
|
|
502
582
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
503
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() }) }),
|
|
504
584
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5 min-w-0 max-w-[82%]", children: [
|
|
505
585
|
showThinking && reasoningPart && /* @__PURE__ */ jsxRuntime.jsx(ReasoningBlock, { text: reasoningPart.text }),
|
|
506
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
|
+
)),
|
|
507
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: [
|
|
508
598
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
509
599
|
ReactMarkdown,
|
|
@@ -548,7 +638,8 @@ function ChatMessages({
|
|
|
548
638
|
initialMessages = [],
|
|
549
639
|
suggestedMessages = [],
|
|
550
640
|
showThinking = true,
|
|
551
|
-
onSuggest
|
|
641
|
+
onSuggest,
|
|
642
|
+
onPickerSelect
|
|
552
643
|
}) {
|
|
553
644
|
const bottomRef = react.useRef(null);
|
|
554
645
|
const showGreeting = messages.length === 0;
|
|
@@ -569,7 +660,8 @@ function ChatMessages({
|
|
|
569
660
|
agentName,
|
|
570
661
|
profilePicture,
|
|
571
662
|
isStreaming: streaming && i === messages.length - 1 && msg.role === "assistant",
|
|
572
|
-
showThinking
|
|
663
|
+
showThinking,
|
|
664
|
+
onPickerSelect
|
|
573
665
|
},
|
|
574
666
|
msg.id
|
|
575
667
|
)) }),
|
|
@@ -603,6 +695,11 @@ function ChatInput({
|
|
|
603
695
|
textareaRef.current.style.height = "auto";
|
|
604
696
|
}
|
|
605
697
|
}, [input]);
|
|
698
|
+
react.useEffect(() => {
|
|
699
|
+
if (!streaming) {
|
|
700
|
+
textareaRef.current?.focus();
|
|
701
|
+
}
|
|
702
|
+
}, [streaming]);
|
|
606
703
|
const handleKeyDown = (e) => {
|
|
607
704
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
608
705
|
e.preventDefault();
|
|
@@ -729,7 +826,8 @@ function ChatWidget({
|
|
|
729
826
|
initialMessages,
|
|
730
827
|
suggestedMessages,
|
|
731
828
|
showThinking,
|
|
732
|
-
onSuggest: (msg) => chat.send(msg)
|
|
829
|
+
onSuggest: (msg) => chat.send(msg),
|
|
830
|
+
onPickerSelect: chat.selectPickerOption
|
|
733
831
|
}
|
|
734
832
|
),
|
|
735
833
|
/* @__PURE__ */ jsxRuntime.jsx(
|
package/dist/index.mjs
CHANGED
|
@@ -109,6 +109,118 @@ function useChat({
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
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]);
|
|
112
224
|
const send = useCallback(
|
|
113
225
|
async (text) => {
|
|
114
226
|
const userInput = (text ?? input).trim();
|
|
@@ -122,144 +234,59 @@ function useChat({
|
|
|
122
234
|
setStreaming(true);
|
|
123
235
|
const assistantMsgId = newId();
|
|
124
236
|
streamingMsgIdRef.current = assistantMsgId;
|
|
125
|
-
setMessages((prev) => [
|
|
126
|
-
...prev,
|
|
127
|
-
{ id: assistantMsgId, role: "assistant", parts: [] }
|
|
128
|
-
]);
|
|
237
|
+
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
129
238
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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 } }
|
|
159
275
|
});
|
|
160
|
-
if (!res.ok) {
|
|
161
|
-
const errText = await res.text().catch(() => "");
|
|
162
|
-
throw new Error(errText || `API error ${res.status}`);
|
|
163
|
-
}
|
|
164
|
-
if (!res.body) throw new Error("No stream body");
|
|
165
|
-
const applyEvent = (proto) => {
|
|
166
|
-
if (proto.type === "navigate") {
|
|
167
|
-
if (proto.path.startsWith("/")) onNavigateRef.current?.(proto.path);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
setMessages((prev) => {
|
|
171
|
-
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
172
|
-
if (idx === -1) return prev;
|
|
173
|
-
const existing = prev[idx];
|
|
174
|
-
const msg = {
|
|
175
|
-
id: existing.id,
|
|
176
|
-
role: existing.role,
|
|
177
|
-
parts: [...existing.parts]
|
|
178
|
-
};
|
|
179
|
-
switch (proto.type) {
|
|
180
|
-
case "text-delta": {
|
|
181
|
-
const textIdx = msg.parts.findIndex((p) => p.type === "text");
|
|
182
|
-
if (textIdx === -1) {
|
|
183
|
-
msg.parts.push({ type: "text", text: proto.delta });
|
|
184
|
-
} else {
|
|
185
|
-
const p = msg.parts[textIdx];
|
|
186
|
-
msg.parts[textIdx] = { type: "text", text: p.text + proto.delta };
|
|
187
|
-
}
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
case "reasoning-delta": {
|
|
191
|
-
const rIdx = msg.parts.findIndex((p) => p.type === "reasoning");
|
|
192
|
-
if (rIdx === -1) {
|
|
193
|
-
msg.parts.unshift({ type: "reasoning", text: proto.delta });
|
|
194
|
-
} else {
|
|
195
|
-
const p = msg.parts[rIdx];
|
|
196
|
-
msg.parts[rIdx] = { type: "reasoning", text: p.text + proto.delta };
|
|
197
|
-
}
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
case "tool-input-available": {
|
|
201
|
-
msg.parts.push({
|
|
202
|
-
type: "tool",
|
|
203
|
-
toolCallId: proto.toolCallId,
|
|
204
|
-
toolName: proto.toolName,
|
|
205
|
-
input: proto.input ?? {},
|
|
206
|
-
status: "running"
|
|
207
|
-
});
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
case "tool-output-available": {
|
|
211
|
-
const tIdx = msg.parts.findIndex(
|
|
212
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
213
|
-
);
|
|
214
|
-
if (tIdx !== -1) {
|
|
215
|
-
msg.parts[tIdx] = {
|
|
216
|
-
...msg.parts[tIdx],
|
|
217
|
-
status: "done",
|
|
218
|
-
output: proto.output
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
case "tool-output-error": {
|
|
224
|
-
const tIdx = msg.parts.findIndex(
|
|
225
|
-
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
226
|
-
);
|
|
227
|
-
if (tIdx !== -1) {
|
|
228
|
-
msg.parts[tIdx] = {
|
|
229
|
-
...msg.parts[tIdx],
|
|
230
|
-
status: "error",
|
|
231
|
-
errorText: proto.errorText
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
default:
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
const copy = [...prev];
|
|
240
|
-
copy[idx] = msg;
|
|
241
|
-
return copy;
|
|
242
|
-
});
|
|
243
|
-
};
|
|
244
|
-
await consumeStream(res.body, applyEvent);
|
|
245
276
|
} catch {
|
|
246
277
|
setMessages((prev) => {
|
|
247
278
|
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
248
279
|
if (idx === -1) return prev;
|
|
249
280
|
const copy = [...prev];
|
|
250
281
|
const err = copy[idx];
|
|
251
|
-
copy[idx] = {
|
|
252
|
-
id: err.id,
|
|
253
|
-
role: err.role,
|
|
254
|
-
parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }]
|
|
255
|
-
};
|
|
282
|
+
copy[idx] = { id: err.id, role: err.role, parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }] };
|
|
256
283
|
return copy;
|
|
257
284
|
});
|
|
258
285
|
}
|
|
259
286
|
setStreaming(false);
|
|
260
287
|
streamingMsgIdRef.current = null;
|
|
261
288
|
},
|
|
262
|
-
[
|
|
289
|
+
[streaming, fetchAndStream]
|
|
263
290
|
);
|
|
264
291
|
const regenerate = useCallback(async () => {
|
|
265
292
|
if (streaming) return;
|
|
@@ -274,7 +301,7 @@ function useChat({
|
|
|
274
301
|
});
|
|
275
302
|
await send(lastText);
|
|
276
303
|
}, [streaming, messages, send]);
|
|
277
|
-
return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
|
|
304
|
+
return { messages, input, setInput, streaming, threadId, send, regenerate, reset, selectPickerOption };
|
|
278
305
|
}
|
|
279
306
|
function DefaultIcon() {
|
|
280
307
|
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
|
|
@@ -448,18 +475,71 @@ function ReasoningBlock({ text }) {
|
|
|
448
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 })
|
|
449
476
|
] });
|
|
450
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
|
+
}
|
|
451
529
|
function MessageBubble({
|
|
452
530
|
message,
|
|
453
531
|
userColor,
|
|
454
532
|
agentName,
|
|
455
533
|
profilePicture,
|
|
456
534
|
isStreaming,
|
|
457
|
-
showThinking = true
|
|
535
|
+
showThinking = true,
|
|
536
|
+
onPickerSelect
|
|
458
537
|
}) {
|
|
459
538
|
const isUser = message.role === "user";
|
|
460
539
|
const textPart = message.parts.find((p) => p.type === "text");
|
|
461
540
|
const reasoningPart = message.parts.find((p) => p.type === "reasoning");
|
|
462
541
|
const toolParts = message.parts.filter((p) => p.type === "tool");
|
|
542
|
+
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
463
543
|
const contrastColor = getContrastColor(userColor);
|
|
464
544
|
if (isUser) {
|
|
465
545
|
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
@@ -472,12 +552,22 @@ function MessageBubble({
|
|
|
472
552
|
) });
|
|
473
553
|
}
|
|
474
554
|
const visibleToolParts = showThinking ? toolParts : [];
|
|
475
|
-
const isEmpty = !textPart?.text && visibleToolParts.length === 0;
|
|
555
|
+
const isEmpty = !textPart?.text && visibleToolParts.length === 0 && pickerParts.length === 0;
|
|
476
556
|
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
477
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() }) }),
|
|
478
558
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 min-w-0 max-w-[82%]", children: [
|
|
479
559
|
showThinking && reasoningPart && /* @__PURE__ */ jsx(ReasoningBlock, { text: reasoningPart.text }),
|
|
480
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
|
+
)),
|
|
481
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: [
|
|
482
572
|
/* @__PURE__ */ jsx(
|
|
483
573
|
ReactMarkdown,
|
|
@@ -522,7 +612,8 @@ function ChatMessages({
|
|
|
522
612
|
initialMessages = [],
|
|
523
613
|
suggestedMessages = [],
|
|
524
614
|
showThinking = true,
|
|
525
|
-
onSuggest
|
|
615
|
+
onSuggest,
|
|
616
|
+
onPickerSelect
|
|
526
617
|
}) {
|
|
527
618
|
const bottomRef = useRef(null);
|
|
528
619
|
const showGreeting = messages.length === 0;
|
|
@@ -543,7 +634,8 @@ function ChatMessages({
|
|
|
543
634
|
agentName,
|
|
544
635
|
profilePicture,
|
|
545
636
|
isStreaming: streaming && i === messages.length - 1 && msg.role === "assistant",
|
|
546
|
-
showThinking
|
|
637
|
+
showThinking,
|
|
638
|
+
onPickerSelect
|
|
547
639
|
},
|
|
548
640
|
msg.id
|
|
549
641
|
)) }),
|
|
@@ -577,6 +669,11 @@ function ChatInput({
|
|
|
577
669
|
textareaRef.current.style.height = "auto";
|
|
578
670
|
}
|
|
579
671
|
}, [input]);
|
|
672
|
+
useEffect(() => {
|
|
673
|
+
if (!streaming) {
|
|
674
|
+
textareaRef.current?.focus();
|
|
675
|
+
}
|
|
676
|
+
}, [streaming]);
|
|
580
677
|
const handleKeyDown = (e) => {
|
|
581
678
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
582
679
|
e.preventDefault();
|
|
@@ -703,7 +800,8 @@ function ChatWidget({
|
|
|
703
800
|
initialMessages,
|
|
704
801
|
suggestedMessages,
|
|
705
802
|
showThinking,
|
|
706
|
-
onSuggest: (msg) => chat.send(msg)
|
|
803
|
+
onSuggest: (msg) => chat.send(msg),
|
|
804
|
+
onPickerSelect: chat.selectPickerOption
|
|
707
805
|
}
|
|
708
806
|
),
|
|
709
807
|
/* @__PURE__ */ jsx(
|