@wallavi/widget 1.4.5 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +255 -130
- package/dist/index.mjs +255 -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,98 @@ 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 PickerOption({
|
|
505
|
+
opt,
|
|
506
|
+
isSelected,
|
|
507
|
+
isConsumed,
|
|
508
|
+
actionDisabled,
|
|
509
|
+
pickerId,
|
|
510
|
+
paramName,
|
|
511
|
+
onSelect
|
|
512
|
+
}) {
|
|
513
|
+
const [hovered, setHovered] = react.useState(false);
|
|
514
|
+
const inactive = actionDisabled || isConsumed;
|
|
515
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
516
|
+
"button",
|
|
517
|
+
{
|
|
518
|
+
disabled: inactive,
|
|
519
|
+
onMouseEnter: () => setHovered(true),
|
|
520
|
+
onMouseLeave: () => setHovered(false),
|
|
521
|
+
onClick: () => {
|
|
522
|
+
if (!inactive) onSelect(pickerId, paramName, opt.value, opt.label);
|
|
523
|
+
},
|
|
524
|
+
style: {
|
|
525
|
+
fontSize: 13,
|
|
526
|
+
lineHeight: "1.4",
|
|
527
|
+
borderRadius: 8,
|
|
528
|
+
padding: "6px 13px",
|
|
529
|
+
border: isSelected ? "1.5px solid var(--primary, #19191c)" : hovered && !inactive ? "1.5px solid rgba(0,0,0,0.28)" : "1.5px solid rgba(0,0,0,0.13)",
|
|
530
|
+
backgroundColor: isSelected ? "var(--primary, #19191c)" : hovered && !inactive ? "rgba(0,0,0,0.04)" : "#fff",
|
|
531
|
+
color: isSelected ? "var(--primary-foreground, #fff)" : "var(--foreground, #09090b)",
|
|
532
|
+
cursor: inactive ? "default" : "pointer",
|
|
533
|
+
opacity: isConsumed ? 0.28 : 1,
|
|
534
|
+
transition: "border-color 0.12s ease, background-color 0.12s ease, opacity 0.12s ease",
|
|
535
|
+
fontWeight: isSelected ? 600 : 400,
|
|
536
|
+
boxShadow: isSelected || inactive ? "none" : "0 1px 2px rgba(0,0,0,0.07)"
|
|
537
|
+
},
|
|
538
|
+
children: opt.label
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
function PickerSelector({
|
|
543
|
+
part,
|
|
544
|
+
disabled,
|
|
545
|
+
onSelect
|
|
546
|
+
}) {
|
|
547
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
548
|
+
"div",
|
|
549
|
+
{
|
|
550
|
+
style: {
|
|
551
|
+
display: "flex",
|
|
552
|
+
flexDirection: "column",
|
|
553
|
+
gap: 10,
|
|
554
|
+
padding: "12px 14px",
|
|
555
|
+
borderRadius: "0 14px 14px 14px",
|
|
556
|
+
backgroundColor: "var(--muted, #f4f4f5)",
|
|
557
|
+
border: "1px solid rgba(0,0,0,0.06)"
|
|
558
|
+
},
|
|
559
|
+
children: [
|
|
560
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, fontSize: 11.5, color: "var(--muted-foreground, #71717a)", fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase" }, children: part.label }),
|
|
561
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 7 }, children: part.options.map((opt) => {
|
|
562
|
+
const isSelected = part.selectedValue === opt.value;
|
|
563
|
+
const isConsumed = !!part.selectedValue && !isSelected;
|
|
564
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
565
|
+
PickerOption,
|
|
566
|
+
{
|
|
567
|
+
opt,
|
|
568
|
+
isSelected,
|
|
569
|
+
isConsumed,
|
|
570
|
+
actionDisabled: disabled,
|
|
571
|
+
pickerId: part.pickerId,
|
|
572
|
+
paramName: part.paramName,
|
|
573
|
+
onSelect
|
|
574
|
+
},
|
|
575
|
+
opt.value
|
|
576
|
+
);
|
|
577
|
+
}) })
|
|
578
|
+
]
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
}
|
|
477
582
|
function MessageBubble({
|
|
478
583
|
message,
|
|
479
584
|
userColor,
|
|
480
585
|
agentName,
|
|
481
586
|
profilePicture,
|
|
482
587
|
isStreaming,
|
|
483
|
-
showThinking = true
|
|
588
|
+
showThinking = true,
|
|
589
|
+
onPickerSelect
|
|
484
590
|
}) {
|
|
485
591
|
const isUser = message.role === "user";
|
|
486
592
|
const textPart = message.parts.find((p) => p.type === "text");
|
|
487
593
|
const reasoningPart = message.parts.find((p) => p.type === "reasoning");
|
|
488
594
|
const toolParts = message.parts.filter((p) => p.type === "tool");
|
|
595
|
+
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
489
596
|
const contrastColor = getContrastColor(userColor);
|
|
490
597
|
if (isUser) {
|
|
491
598
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -498,12 +605,22 @@ function MessageBubble({
|
|
|
498
605
|
) });
|
|
499
606
|
}
|
|
500
607
|
const visibleToolParts = showThinking ? toolParts : [];
|
|
501
|
-
const isEmpty = !textPart?.text && visibleToolParts.length === 0;
|
|
608
|
+
const isEmpty = !textPart?.text && visibleToolParts.length === 0 && pickerParts.length === 0;
|
|
502
609
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
503
610
|
/* @__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
611
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5 min-w-0 max-w-[82%]", children: [
|
|
505
612
|
showThinking && reasoningPart && /* @__PURE__ */ jsxRuntime.jsx(ReasoningBlock, { text: reasoningPart.text }),
|
|
506
613
|
visibleToolParts.map((t) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallBadge, { part: t }, t.toolCallId)),
|
|
614
|
+
pickerParts.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
615
|
+
PickerSelector,
|
|
616
|
+
{
|
|
617
|
+
part: p,
|
|
618
|
+
disabled: isStreaming ?? false,
|
|
619
|
+
onSelect: onPickerSelect ?? (() => {
|
|
620
|
+
})
|
|
621
|
+
},
|
|
622
|
+
p.pickerId
|
|
623
|
+
)),
|
|
507
624
|
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
625
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
509
626
|
ReactMarkdown,
|
|
@@ -548,7 +665,8 @@ function ChatMessages({
|
|
|
548
665
|
initialMessages = [],
|
|
549
666
|
suggestedMessages = [],
|
|
550
667
|
showThinking = true,
|
|
551
|
-
onSuggest
|
|
668
|
+
onSuggest,
|
|
669
|
+
onPickerSelect
|
|
552
670
|
}) {
|
|
553
671
|
const bottomRef = react.useRef(null);
|
|
554
672
|
const showGreeting = messages.length === 0;
|
|
@@ -569,7 +687,8 @@ function ChatMessages({
|
|
|
569
687
|
agentName,
|
|
570
688
|
profilePicture,
|
|
571
689
|
isStreaming: streaming && i === messages.length - 1 && msg.role === "assistant",
|
|
572
|
-
showThinking
|
|
690
|
+
showThinking,
|
|
691
|
+
onPickerSelect
|
|
573
692
|
},
|
|
574
693
|
msg.id
|
|
575
694
|
)) }),
|
|
@@ -603,6 +722,11 @@ function ChatInput({
|
|
|
603
722
|
textareaRef.current.style.height = "auto";
|
|
604
723
|
}
|
|
605
724
|
}, [input]);
|
|
725
|
+
react.useEffect(() => {
|
|
726
|
+
if (!streaming) {
|
|
727
|
+
textareaRef.current?.focus();
|
|
728
|
+
}
|
|
729
|
+
}, [streaming]);
|
|
606
730
|
const handleKeyDown = (e) => {
|
|
607
731
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
608
732
|
e.preventDefault();
|
|
@@ -729,7 +853,8 @@ function ChatWidget({
|
|
|
729
853
|
initialMessages,
|
|
730
854
|
suggestedMessages,
|
|
731
855
|
showThinking,
|
|
732
|
-
onSuggest: (msg) => chat.send(msg)
|
|
856
|
+
onSuggest: (msg) => chat.send(msg),
|
|
857
|
+
onPickerSelect: chat.selectPickerOption
|
|
733
858
|
}
|
|
734
859
|
),
|
|
735
860
|
/* @__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,98 @@ 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 PickerOption({
|
|
479
|
+
opt,
|
|
480
|
+
isSelected,
|
|
481
|
+
isConsumed,
|
|
482
|
+
actionDisabled,
|
|
483
|
+
pickerId,
|
|
484
|
+
paramName,
|
|
485
|
+
onSelect
|
|
486
|
+
}) {
|
|
487
|
+
const [hovered, setHovered] = useState(false);
|
|
488
|
+
const inactive = actionDisabled || isConsumed;
|
|
489
|
+
return /* @__PURE__ */ jsx(
|
|
490
|
+
"button",
|
|
491
|
+
{
|
|
492
|
+
disabled: inactive,
|
|
493
|
+
onMouseEnter: () => setHovered(true),
|
|
494
|
+
onMouseLeave: () => setHovered(false),
|
|
495
|
+
onClick: () => {
|
|
496
|
+
if (!inactive) onSelect(pickerId, paramName, opt.value, opt.label);
|
|
497
|
+
},
|
|
498
|
+
style: {
|
|
499
|
+
fontSize: 13,
|
|
500
|
+
lineHeight: "1.4",
|
|
501
|
+
borderRadius: 8,
|
|
502
|
+
padding: "6px 13px",
|
|
503
|
+
border: isSelected ? "1.5px solid var(--primary, #19191c)" : hovered && !inactive ? "1.5px solid rgba(0,0,0,0.28)" : "1.5px solid rgba(0,0,0,0.13)",
|
|
504
|
+
backgroundColor: isSelected ? "var(--primary, #19191c)" : hovered && !inactive ? "rgba(0,0,0,0.04)" : "#fff",
|
|
505
|
+
color: isSelected ? "var(--primary-foreground, #fff)" : "var(--foreground, #09090b)",
|
|
506
|
+
cursor: inactive ? "default" : "pointer",
|
|
507
|
+
opacity: isConsumed ? 0.28 : 1,
|
|
508
|
+
transition: "border-color 0.12s ease, background-color 0.12s ease, opacity 0.12s ease",
|
|
509
|
+
fontWeight: isSelected ? 600 : 400,
|
|
510
|
+
boxShadow: isSelected || inactive ? "none" : "0 1px 2px rgba(0,0,0,0.07)"
|
|
511
|
+
},
|
|
512
|
+
children: opt.label
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
function PickerSelector({
|
|
517
|
+
part,
|
|
518
|
+
disabled,
|
|
519
|
+
onSelect
|
|
520
|
+
}) {
|
|
521
|
+
return /* @__PURE__ */ jsxs(
|
|
522
|
+
"div",
|
|
523
|
+
{
|
|
524
|
+
style: {
|
|
525
|
+
display: "flex",
|
|
526
|
+
flexDirection: "column",
|
|
527
|
+
gap: 10,
|
|
528
|
+
padding: "12px 14px",
|
|
529
|
+
borderRadius: "0 14px 14px 14px",
|
|
530
|
+
backgroundColor: "var(--muted, #f4f4f5)",
|
|
531
|
+
border: "1px solid rgba(0,0,0,0.06)"
|
|
532
|
+
},
|
|
533
|
+
children: [
|
|
534
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 11.5, color: "var(--muted-foreground, #71717a)", fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase" }, children: part.label }),
|
|
535
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 7 }, children: part.options.map((opt) => {
|
|
536
|
+
const isSelected = part.selectedValue === opt.value;
|
|
537
|
+
const isConsumed = !!part.selectedValue && !isSelected;
|
|
538
|
+
return /* @__PURE__ */ jsx(
|
|
539
|
+
PickerOption,
|
|
540
|
+
{
|
|
541
|
+
opt,
|
|
542
|
+
isSelected,
|
|
543
|
+
isConsumed,
|
|
544
|
+
actionDisabled: disabled,
|
|
545
|
+
pickerId: part.pickerId,
|
|
546
|
+
paramName: part.paramName,
|
|
547
|
+
onSelect
|
|
548
|
+
},
|
|
549
|
+
opt.value
|
|
550
|
+
);
|
|
551
|
+
}) })
|
|
552
|
+
]
|
|
553
|
+
}
|
|
554
|
+
);
|
|
555
|
+
}
|
|
451
556
|
function MessageBubble({
|
|
452
557
|
message,
|
|
453
558
|
userColor,
|
|
454
559
|
agentName,
|
|
455
560
|
profilePicture,
|
|
456
561
|
isStreaming,
|
|
457
|
-
showThinking = true
|
|
562
|
+
showThinking = true,
|
|
563
|
+
onPickerSelect
|
|
458
564
|
}) {
|
|
459
565
|
const isUser = message.role === "user";
|
|
460
566
|
const textPart = message.parts.find((p) => p.type === "text");
|
|
461
567
|
const reasoningPart = message.parts.find((p) => p.type === "reasoning");
|
|
462
568
|
const toolParts = message.parts.filter((p) => p.type === "tool");
|
|
569
|
+
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
463
570
|
const contrastColor = getContrastColor(userColor);
|
|
464
571
|
if (isUser) {
|
|
465
572
|
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
@@ -472,12 +579,22 @@ function MessageBubble({
|
|
|
472
579
|
) });
|
|
473
580
|
}
|
|
474
581
|
const visibleToolParts = showThinking ? toolParts : [];
|
|
475
|
-
const isEmpty = !textPart?.text && visibleToolParts.length === 0;
|
|
582
|
+
const isEmpty = !textPart?.text && visibleToolParts.length === 0 && pickerParts.length === 0;
|
|
476
583
|
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
477
584
|
/* @__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
585
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 min-w-0 max-w-[82%]", children: [
|
|
479
586
|
showThinking && reasoningPart && /* @__PURE__ */ jsx(ReasoningBlock, { text: reasoningPart.text }),
|
|
480
587
|
visibleToolParts.map((t) => /* @__PURE__ */ jsx(ToolCallBadge, { part: t }, t.toolCallId)),
|
|
588
|
+
pickerParts.map((p) => /* @__PURE__ */ jsx(
|
|
589
|
+
PickerSelector,
|
|
590
|
+
{
|
|
591
|
+
part: p,
|
|
592
|
+
disabled: isStreaming ?? false,
|
|
593
|
+
onSelect: onPickerSelect ?? (() => {
|
|
594
|
+
})
|
|
595
|
+
},
|
|
596
|
+
p.pickerId
|
|
597
|
+
)),
|
|
481
598
|
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
599
|
/* @__PURE__ */ jsx(
|
|
483
600
|
ReactMarkdown,
|
|
@@ -522,7 +639,8 @@ function ChatMessages({
|
|
|
522
639
|
initialMessages = [],
|
|
523
640
|
suggestedMessages = [],
|
|
524
641
|
showThinking = true,
|
|
525
|
-
onSuggest
|
|
642
|
+
onSuggest,
|
|
643
|
+
onPickerSelect
|
|
526
644
|
}) {
|
|
527
645
|
const bottomRef = useRef(null);
|
|
528
646
|
const showGreeting = messages.length === 0;
|
|
@@ -543,7 +661,8 @@ function ChatMessages({
|
|
|
543
661
|
agentName,
|
|
544
662
|
profilePicture,
|
|
545
663
|
isStreaming: streaming && i === messages.length - 1 && msg.role === "assistant",
|
|
546
|
-
showThinking
|
|
664
|
+
showThinking,
|
|
665
|
+
onPickerSelect
|
|
547
666
|
},
|
|
548
667
|
msg.id
|
|
549
668
|
)) }),
|
|
@@ -577,6 +696,11 @@ function ChatInput({
|
|
|
577
696
|
textareaRef.current.style.height = "auto";
|
|
578
697
|
}
|
|
579
698
|
}, [input]);
|
|
699
|
+
useEffect(() => {
|
|
700
|
+
if (!streaming) {
|
|
701
|
+
textareaRef.current?.focus();
|
|
702
|
+
}
|
|
703
|
+
}, [streaming]);
|
|
580
704
|
const handleKeyDown = (e) => {
|
|
581
705
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
582
706
|
e.preventDefault();
|
|
@@ -703,7 +827,8 @@ function ChatWidget({
|
|
|
703
827
|
initialMessages,
|
|
704
828
|
suggestedMessages,
|
|
705
829
|
showThinking,
|
|
706
|
-
onSuggest: (msg) => chat.send(msg)
|
|
830
|
+
onSuggest: (msg) => chat.send(msg),
|
|
831
|
+
onPickerSelect: chat.selectPickerOption
|
|
707
832
|
}
|
|
708
833
|
),
|
|
709
834
|
/* @__PURE__ */ jsx(
|