@dataverse-kit/agent-kit 0.2.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/README.md +166 -0
- package/dist/core.cjs +532 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +278 -0
- package/dist/core.d.ts +278 -0
- package/dist/core.mjs +482 -0
- package/dist/core.mjs.map +1 -0
- package/dist/host.cjs +113 -0
- package/dist/host.cjs.map +1 -0
- package/dist/host.d.cts +3 -0
- package/dist/host.d.ts +3 -0
- package/dist/host.mjs +74 -0
- package/dist/host.mjs.map +1 -0
- package/dist/index-DGN6eYnp.d.cts +103 -0
- package/dist/index-DGN6eYnp.d.ts +103 -0
- package/dist/index.cjs +3145 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +863 -0
- package/dist/index.d.ts +863 -0
- package/dist/index.mjs +3068 -0
- package/dist/index.mjs.map +1 -0
- package/dist/protocol-CZJ1v9Hq.d.cts +270 -0
- package/dist/protocol-CZJ1v9Hq.d.ts +270 -0
- package/dist/services.cjs +333 -0
- package/dist/services.cjs.map +1 -0
- package/dist/services.d.cts +118 -0
- package/dist/services.d.ts +118 -0
- package/dist/services.mjs +300 -0
- package/dist/services.mjs.map +1 -0
- package/package.json +89 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3068 @@
|
|
|
1
|
+
// src/conversation/MessageList.tsx
|
|
2
|
+
import * as React10 from "react";
|
|
3
|
+
import { Button as Button3, makeStyles as makeStyles10, tokens as tokens9 } from "@fluentui/react-components";
|
|
4
|
+
import { ArrowDownRegular } from "@fluentui/react-icons";
|
|
5
|
+
|
|
6
|
+
// src/core/runStatus.ts
|
|
7
|
+
var TERMINAL = /* @__PURE__ */ new Set([
|
|
8
|
+
"succeeded",
|
|
9
|
+
"blocked",
|
|
10
|
+
"refused",
|
|
11
|
+
"error",
|
|
12
|
+
"notProvisioned",
|
|
13
|
+
"cancelled"
|
|
14
|
+
]);
|
|
15
|
+
function isTerminalStatus(status) {
|
|
16
|
+
return TERMINAL.has(status);
|
|
17
|
+
}
|
|
18
|
+
function runStatusSeverity(status) {
|
|
19
|
+
switch (status) {
|
|
20
|
+
case "succeeded":
|
|
21
|
+
return "success";
|
|
22
|
+
case "blocked":
|
|
23
|
+
case "refused":
|
|
24
|
+
return "warning";
|
|
25
|
+
case "error":
|
|
26
|
+
return "error";
|
|
27
|
+
case "notProvisioned":
|
|
28
|
+
case "cancelled":
|
|
29
|
+
return "info";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function classifyTransportFailure(input) {
|
|
33
|
+
const { httpStatus, code, retryAfterMs } = input;
|
|
34
|
+
const message = input.message ?? "The agent could not be reached.";
|
|
35
|
+
if (httpStatus === 503) {
|
|
36
|
+
return {
|
|
37
|
+
status: "notProvisioned",
|
|
38
|
+
message,
|
|
39
|
+
code: code ?? "not_provisioned",
|
|
40
|
+
recoverable: false,
|
|
41
|
+
correlationId: void 0
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (httpStatus === 429) {
|
|
45
|
+
return { status: "error", message, code: code ?? "rate_limited", recoverable: true, retryAfterMs };
|
|
46
|
+
}
|
|
47
|
+
if (code === "content_filter") {
|
|
48
|
+
return { status: "blocked", message, code, recoverable: false };
|
|
49
|
+
}
|
|
50
|
+
return { status: "error", message, code, recoverable: httpStatus !== void 0 && httpStatus >= 500 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/core/grouping.ts
|
|
54
|
+
var DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1e3;
|
|
55
|
+
function dayKey(epochMs, timeZone) {
|
|
56
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
57
|
+
timeZone,
|
|
58
|
+
year: "numeric",
|
|
59
|
+
month: "2-digit",
|
|
60
|
+
day: "2-digit"
|
|
61
|
+
}).format(new Date(epochMs));
|
|
62
|
+
}
|
|
63
|
+
function isSameAuthorRun(a, b, windowMs = DEFAULT_GROUP_WINDOW_MS, timeZone) {
|
|
64
|
+
if (a.authorId !== b.authorId) return false;
|
|
65
|
+
if (a.role !== b.role) return false;
|
|
66
|
+
if (a.role === "system" || b.role === "system") return false;
|
|
67
|
+
if (b.createdAt < a.createdAt) return false;
|
|
68
|
+
if (b.createdAt - a.createdAt > windowMs) return false;
|
|
69
|
+
return dayKey(a.createdAt, timeZone) === dayKey(b.createdAt, timeZone);
|
|
70
|
+
}
|
|
71
|
+
function groupMessages(messages, options = {}) {
|
|
72
|
+
const windowMs = options.windowMs ?? DEFAULT_GROUP_WINDOW_MS;
|
|
73
|
+
const groups = [];
|
|
74
|
+
for (const message of messages) {
|
|
75
|
+
const current = groups[groups.length - 1];
|
|
76
|
+
const previousId = current?.messageIds[current.messageIds.length - 1];
|
|
77
|
+
const previous = previousId ? messages.find((m) => m.id === previousId) : void 0;
|
|
78
|
+
if (current && previous && isSameAuthorRun(previous, message, windowMs, options.timeZone)) {
|
|
79
|
+
current.messageIds.push(message.id);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
groups.push({
|
|
83
|
+
key: `${message.authorId}:${message.id}`,
|
|
84
|
+
authorId: message.authorId,
|
|
85
|
+
role: message.role,
|
|
86
|
+
startedAt: message.createdAt,
|
|
87
|
+
messageIds: [message.id]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return groups;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/core/dayDividers.ts
|
|
94
|
+
function dayKeyOf(epochMs, timeZone) {
|
|
95
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
96
|
+
timeZone,
|
|
97
|
+
year: "numeric",
|
|
98
|
+
month: "2-digit",
|
|
99
|
+
day: "2-digit"
|
|
100
|
+
}).format(new Date(epochMs));
|
|
101
|
+
}
|
|
102
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
103
|
+
function labelFor(dayKey2, nowMs, timeZone) {
|
|
104
|
+
const todayKey = dayKeyOf(nowMs, timeZone);
|
|
105
|
+
if (dayKey2 === todayKey) return { kind: "today" };
|
|
106
|
+
if (dayKey2 === dayKeyOf(nowMs - DAY_MS, timeZone)) return { kind: "yesterday" };
|
|
107
|
+
const asDate = /* @__PURE__ */ new Date(`${dayKey2}T00:00:00Z`);
|
|
108
|
+
for (let back = 2; back <= 6; back++) {
|
|
109
|
+
if (dayKey2 === dayKeyOf(nowMs - back * DAY_MS, timeZone)) {
|
|
110
|
+
return { kind: "weekday", weekday: asDate.getUTCDay(), iso: dayKey2 };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { kind: "date", iso: dayKey2 };
|
|
114
|
+
}
|
|
115
|
+
function bucketByDay(messages, options = {}) {
|
|
116
|
+
const nowMs = options.now ?? Date.now();
|
|
117
|
+
const buckets = [];
|
|
118
|
+
for (const message of messages) {
|
|
119
|
+
const key = dayKeyOf(message.createdAt, options.timeZone);
|
|
120
|
+
const current = buckets[buckets.length - 1];
|
|
121
|
+
if (current && current.dayKey === key) {
|
|
122
|
+
current.messageIds.push(message.id);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
buckets.push({
|
|
126
|
+
dayKey: key,
|
|
127
|
+
label: labelFor(key, nowMs, options.timeZone),
|
|
128
|
+
messageIds: [message.id]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return buckets;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/core/citations.ts
|
|
135
|
+
var BRACKET_MARKER = /\[(\d{1,3})\]/g;
|
|
136
|
+
var AOAI_MARKER = /【(\d{1,3}):\d+†[^】]*】/g;
|
|
137
|
+
function extractCitationMarkers(text) {
|
|
138
|
+
const found = [];
|
|
139
|
+
for (const re of [BRACKET_MARKER, AOAI_MARKER]) {
|
|
140
|
+
re.lastIndex = 0;
|
|
141
|
+
let m;
|
|
142
|
+
while ((m = re.exec(text)) !== null) {
|
|
143
|
+
found.push({ marker: m[1], index: m.index, length: m[0].length });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return found.sort((a, b) => a.index - b.index);
|
|
147
|
+
}
|
|
148
|
+
function renumberCitations(text, sources) {
|
|
149
|
+
const markers = extractCitationMarkers(text);
|
|
150
|
+
const order = [];
|
|
151
|
+
for (const { marker } of markers) if (!order.includes(marker)) order.push(marker);
|
|
152
|
+
const byOriginal = /* @__PURE__ */ new Map();
|
|
153
|
+
order.forEach((marker, i) => byOriginal.set(marker, i + 1));
|
|
154
|
+
const used = [];
|
|
155
|
+
const citations = [];
|
|
156
|
+
for (const marker of order) {
|
|
157
|
+
const source = sources[Number(marker) - 1];
|
|
158
|
+
if (!source) continue;
|
|
159
|
+
used.push(source);
|
|
160
|
+
citations.push({ marker: String(byOriginal.get(marker)), sourceId: source.id });
|
|
161
|
+
}
|
|
162
|
+
let out = "";
|
|
163
|
+
let cursor = 0;
|
|
164
|
+
for (const { marker, index, length } of markers) {
|
|
165
|
+
out += text.slice(cursor, index);
|
|
166
|
+
const renumbered = byOriginal.get(marker);
|
|
167
|
+
out += renumbered === void 0 ? "" : `[${renumbered}]`;
|
|
168
|
+
cursor = index + length;
|
|
169
|
+
}
|
|
170
|
+
out += text.slice(cursor);
|
|
171
|
+
return { text: out, used, citations };
|
|
172
|
+
}
|
|
173
|
+
function splitTextByCitations(text, citations) {
|
|
174
|
+
const markers = extractCitationMarkers(text);
|
|
175
|
+
if (markers.length === 0) return text ? [{ kind: "text", text }] : [];
|
|
176
|
+
const segments = [];
|
|
177
|
+
let cursor = 0;
|
|
178
|
+
for (const { marker, index, length } of markers) {
|
|
179
|
+
if (index > cursor) segments.push({ kind: "text", text: text.slice(cursor, index) });
|
|
180
|
+
const citation = citations.find((c) => c.marker === marker);
|
|
181
|
+
segments.push({
|
|
182
|
+
kind: "citation",
|
|
183
|
+
text: `[${marker}]`,
|
|
184
|
+
marker,
|
|
185
|
+
sourceId: citation?.sourceId
|
|
186
|
+
});
|
|
187
|
+
cursor = index + length;
|
|
188
|
+
}
|
|
189
|
+
if (cursor < text.length) segments.push({ kind: "text", text: text.slice(cursor) });
|
|
190
|
+
return segments;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/core/conversationReducer.ts
|
|
194
|
+
function initialConversation(seed = []) {
|
|
195
|
+
return { messages: [...seed], streamingMessageId: null, status: "idle" };
|
|
196
|
+
}
|
|
197
|
+
function patch(state, messageId, update) {
|
|
198
|
+
const i = state.messages.findIndex((m) => m.id === messageId);
|
|
199
|
+
if (i === -1) return state.messages;
|
|
200
|
+
const next = state.messages.slice();
|
|
201
|
+
next[i] = update(next[i]);
|
|
202
|
+
return next;
|
|
203
|
+
}
|
|
204
|
+
function appendUserMessage(state, input) {
|
|
205
|
+
const message = {
|
|
206
|
+
id: input.id,
|
|
207
|
+
role: "user",
|
|
208
|
+
authorId: input.authorId ?? "user",
|
|
209
|
+
text: input.text,
|
|
210
|
+
createdAt: input.now ?? Date.now(),
|
|
211
|
+
attachments: input.attachments
|
|
212
|
+
};
|
|
213
|
+
return { ...state, messages: [...state.messages, message], status: "connecting", failure: void 0 };
|
|
214
|
+
}
|
|
215
|
+
function applyAgentEvent(state, event) {
|
|
216
|
+
switch (event.type) {
|
|
217
|
+
case "turn_start": {
|
|
218
|
+
const message = {
|
|
219
|
+
id: event.messageId,
|
|
220
|
+
role: "assistant",
|
|
221
|
+
authorId: event.provider ?? "agent",
|
|
222
|
+
text: "",
|
|
223
|
+
createdAt: Date.now(),
|
|
224
|
+
streaming: true,
|
|
225
|
+
provider: event.provider
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
...state,
|
|
229
|
+
messages: [...state.messages, message],
|
|
230
|
+
streamingMessageId: event.messageId,
|
|
231
|
+
status: "streaming",
|
|
232
|
+
statusLabel: void 0,
|
|
233
|
+
failure: void 0,
|
|
234
|
+
suggestions: void 0
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
case "text_delta":
|
|
238
|
+
return {
|
|
239
|
+
...state,
|
|
240
|
+
messages: patch(state, event.messageId, (m) => ({ ...m, text: m.text + event.text }))
|
|
241
|
+
};
|
|
242
|
+
case "thinking_delta":
|
|
243
|
+
return {
|
|
244
|
+
...state,
|
|
245
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
246
|
+
...m,
|
|
247
|
+
thinking: (m.thinking ?? "") + event.text
|
|
248
|
+
}))
|
|
249
|
+
};
|
|
250
|
+
case "tool_call":
|
|
251
|
+
return {
|
|
252
|
+
...state,
|
|
253
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
254
|
+
...m,
|
|
255
|
+
toolCalls: [...m.toolCalls ?? [], event.call]
|
|
256
|
+
}))
|
|
257
|
+
};
|
|
258
|
+
case "tool_result":
|
|
259
|
+
return {
|
|
260
|
+
...state,
|
|
261
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
262
|
+
...m,
|
|
263
|
+
toolCalls: (m.toolCalls ?? []).map(
|
|
264
|
+
(c) => c.id === event.callId ? { ...c, status: event.status, resultText: event.resultText, failure: event.failure } : c
|
|
265
|
+
)
|
|
266
|
+
}))
|
|
267
|
+
};
|
|
268
|
+
case "sources":
|
|
269
|
+
return {
|
|
270
|
+
...state,
|
|
271
|
+
messages: patch(state, event.messageId, (m) => ({ ...m, sources: event.sources }))
|
|
272
|
+
};
|
|
273
|
+
case "citation":
|
|
274
|
+
return {
|
|
275
|
+
...state,
|
|
276
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
277
|
+
...m,
|
|
278
|
+
citations: [...m.citations ?? [], event.citation]
|
|
279
|
+
}))
|
|
280
|
+
};
|
|
281
|
+
case "suggestions":
|
|
282
|
+
return { ...state, suggestions: event.prompts };
|
|
283
|
+
case "status":
|
|
284
|
+
return { ...state, statusLabel: event.label };
|
|
285
|
+
case "turn_end": {
|
|
286
|
+
const { result } = event;
|
|
287
|
+
return {
|
|
288
|
+
...state,
|
|
289
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
290
|
+
...m,
|
|
291
|
+
// A non-streaming provider sends no deltas, so the assembled text arrives here.
|
|
292
|
+
text: m.text.length > 0 ? m.text : result.text,
|
|
293
|
+
streaming: false,
|
|
294
|
+
guarded: result.guarded,
|
|
295
|
+
provider: result.provider,
|
|
296
|
+
usage: result.usage,
|
|
297
|
+
failure: result.failure
|
|
298
|
+
})),
|
|
299
|
+
streamingMessageId: null,
|
|
300
|
+
status: result.status,
|
|
301
|
+
statusLabel: void 0,
|
|
302
|
+
failure: result.failure,
|
|
303
|
+
usage: result.usage
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/core/normalize.ts
|
|
310
|
+
function fromSseLine(line) {
|
|
311
|
+
const trimmed = line.trim();
|
|
312
|
+
if (trimmed === "" || trimmed.startsWith(":")) return void 0;
|
|
313
|
+
if (!trimmed.startsWith("data:")) return void 0;
|
|
314
|
+
const payload = trimmed.slice("data:".length).trim();
|
|
315
|
+
if (payload === "") return void 0;
|
|
316
|
+
if (payload === "[DONE]") return "[DONE]";
|
|
317
|
+
try {
|
|
318
|
+
return JSON.parse(payload);
|
|
319
|
+
} catch {
|
|
320
|
+
return void 0;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function fromOpenAiChatChunk(chunk, ctx) {
|
|
324
|
+
const events = [];
|
|
325
|
+
const delta = chunk?.choices?.[0]?.delta;
|
|
326
|
+
if (!delta) return events;
|
|
327
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
328
|
+
events.push({ type: "text_delta", messageId: ctx.messageId, text: delta.content });
|
|
329
|
+
}
|
|
330
|
+
for (const call of delta.tool_calls ?? []) {
|
|
331
|
+
if (!call.id) continue;
|
|
332
|
+
const toolCall = {
|
|
333
|
+
id: call.id,
|
|
334
|
+
name: call.function?.name ?? "",
|
|
335
|
+
argumentsJson: call.function?.arguments ?? "",
|
|
336
|
+
status: "proposed"
|
|
337
|
+
};
|
|
338
|
+
events.push({ type: "tool_call", messageId: ctx.messageId, call: toolCall });
|
|
339
|
+
}
|
|
340
|
+
return events;
|
|
341
|
+
}
|
|
342
|
+
function fromClaudeStreamEvent(event, ctx) {
|
|
343
|
+
switch (event.type) {
|
|
344
|
+
case "init":
|
|
345
|
+
return [
|
|
346
|
+
{
|
|
347
|
+
type: "turn_start",
|
|
348
|
+
turnId: ctx.turnId,
|
|
349
|
+
messageId: ctx.messageId,
|
|
350
|
+
model: event.model,
|
|
351
|
+
provider: "claude-bridge",
|
|
352
|
+
sessionId: event.sessionId
|
|
353
|
+
}
|
|
354
|
+
];
|
|
355
|
+
case "text_delta":
|
|
356
|
+
return event.text ? [{ type: "text_delta", messageId: ctx.messageId, text: event.text }] : [];
|
|
357
|
+
case "thinking_delta":
|
|
358
|
+
return event.text ? [{ type: "thinking_delta", messageId: ctx.messageId, text: event.text }] : [];
|
|
359
|
+
case "done":
|
|
360
|
+
return [
|
|
361
|
+
{
|
|
362
|
+
type: "turn_end",
|
|
363
|
+
turnId: ctx.turnId,
|
|
364
|
+
messageId: ctx.messageId,
|
|
365
|
+
result: {
|
|
366
|
+
status: "succeeded",
|
|
367
|
+
text: event.fullText ?? "",
|
|
368
|
+
provider: "claude-bridge",
|
|
369
|
+
model: event.model,
|
|
370
|
+
usage: {
|
|
371
|
+
inputTokens: event.inputTokens,
|
|
372
|
+
outputTokens: event.outputTokens,
|
|
373
|
+
cachedTokens: event.cacheReadTokens,
|
|
374
|
+
costUsd: event.costUsd,
|
|
375
|
+
durationMs: event.durationMs,
|
|
376
|
+
model: event.model
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
];
|
|
381
|
+
case "error":
|
|
382
|
+
return [
|
|
383
|
+
{
|
|
384
|
+
type: "turn_end",
|
|
385
|
+
turnId: ctx.turnId,
|
|
386
|
+
messageId: ctx.messageId,
|
|
387
|
+
result: {
|
|
388
|
+
status: "error",
|
|
389
|
+
text: "",
|
|
390
|
+
provider: "claude-bridge",
|
|
391
|
+
failure: {
|
|
392
|
+
status: "error",
|
|
393
|
+
message: event.message ?? "The agent failed.",
|
|
394
|
+
recoverable: event.recoverable ?? false
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function fromFoundryChatResult(result, ctx, provider = "foundry") {
|
|
402
|
+
return [
|
|
403
|
+
{ type: "turn_start", turnId: ctx.turnId, messageId: ctx.messageId, model: result.model, provider },
|
|
404
|
+
{
|
|
405
|
+
type: "turn_end",
|
|
406
|
+
turnId: ctx.turnId,
|
|
407
|
+
messageId: ctx.messageId,
|
|
408
|
+
result: {
|
|
409
|
+
status: "succeeded",
|
|
410
|
+
text: result.text,
|
|
411
|
+
provider,
|
|
412
|
+
model: result.model,
|
|
413
|
+
usage: {
|
|
414
|
+
inputTokens: result.promptTokens,
|
|
415
|
+
outputTokens: result.completionTokens,
|
|
416
|
+
model: result.model
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/core/announce.ts
|
|
424
|
+
var ANNOUNCE_FULL_TEXT_MAX_CHARS = 240;
|
|
425
|
+
var NO_ANNOUNCEMENT = { polite: "", assertive: "" };
|
|
426
|
+
var TERMINAL2 = /* @__PURE__ */ new Set(["succeeded", "blocked", "refused", "error", "notProvisioned", "cancelled"]);
|
|
427
|
+
function deriveAnnouncement(state, previous) {
|
|
428
|
+
const wasTerminal = previous ? TERMINAL2.has(previous.status) : false;
|
|
429
|
+
const isTerminal = TERMINAL2.has(state.status);
|
|
430
|
+
if (!isTerminal || wasTerminal) return NO_ANNOUNCEMENT;
|
|
431
|
+
if (state.failure) {
|
|
432
|
+
return { polite: "", assertive: state.failure.message };
|
|
433
|
+
}
|
|
434
|
+
const last = state.messages[state.messages.length - 1];
|
|
435
|
+
const text = last?.text ?? "";
|
|
436
|
+
if (!text) return NO_ANNOUNCEMENT;
|
|
437
|
+
const guarded = last?.guarded ? "The response was withheld. " : "";
|
|
438
|
+
if (text.length <= ANNOUNCE_FULL_TEXT_MAX_CHARS) {
|
|
439
|
+
return { polite: `${guarded}${text}`, assertive: "" };
|
|
440
|
+
}
|
|
441
|
+
const words = text.trim().split(/\s+/).length;
|
|
442
|
+
return {
|
|
443
|
+
polite: `${guarded}Response complete, ${words} words.`,
|
|
444
|
+
assertive: ""
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/core/scroll.ts
|
|
449
|
+
var STICK_TO_BOTTOM_THRESHOLD_PX = 32;
|
|
450
|
+
function shouldStickToBottom(geometry, thresholdPx = STICK_TO_BOTTOM_THRESHOLD_PX) {
|
|
451
|
+
const { scrollTop, scrollHeight, clientHeight } = geometry;
|
|
452
|
+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
453
|
+
return distanceFromBottom <= thresholdPx;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/core/ids.ts
|
|
457
|
+
function createIdPrefixCounter(tag) {
|
|
458
|
+
let n = 0;
|
|
459
|
+
return () => `${tag}${++n}-`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/a11y/LiveAnnouncer.tsx
|
|
463
|
+
import * as React from "react";
|
|
464
|
+
|
|
465
|
+
// src/a11y/visuallyHidden.ts
|
|
466
|
+
import { makeStyles } from "@fluentui/react-components";
|
|
467
|
+
var useVisuallyHiddenStyles = makeStyles({
|
|
468
|
+
root: {
|
|
469
|
+
position: "absolute",
|
|
470
|
+
width: "1px",
|
|
471
|
+
height: "1px",
|
|
472
|
+
margin: "-1px",
|
|
473
|
+
padding: "0",
|
|
474
|
+
overflow: "hidden",
|
|
475
|
+
clip: "rect(0 0 0 0)",
|
|
476
|
+
clipPath: "inset(50%)",
|
|
477
|
+
whiteSpace: "nowrap"
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// src/a11y/LiveAnnouncer.tsx
|
|
482
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
483
|
+
function useAnnounceWithRepeat(next, set) {
|
|
484
|
+
React.useEffect(() => {
|
|
485
|
+
if (!next) return;
|
|
486
|
+
let cancelled = false;
|
|
487
|
+
set("");
|
|
488
|
+
const id = setTimeout(() => {
|
|
489
|
+
if (!cancelled) set(next);
|
|
490
|
+
}, 0);
|
|
491
|
+
return () => {
|
|
492
|
+
cancelled = true;
|
|
493
|
+
clearTimeout(id);
|
|
494
|
+
};
|
|
495
|
+
}, [next, set]);
|
|
496
|
+
}
|
|
497
|
+
function LiveAnnouncer({
|
|
498
|
+
polite = "",
|
|
499
|
+
assertive = ""
|
|
500
|
+
}) {
|
|
501
|
+
const styles = useVisuallyHiddenStyles();
|
|
502
|
+
const [politeText, setPoliteText] = React.useState("");
|
|
503
|
+
const [assertiveText, setAssertiveText] = React.useState("");
|
|
504
|
+
useAnnounceWithRepeat(polite, setPoliteText);
|
|
505
|
+
useAnnounceWithRepeat(assertive, setAssertiveText);
|
|
506
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
507
|
+
/* @__PURE__ */ jsx(
|
|
508
|
+
"div",
|
|
509
|
+
{
|
|
510
|
+
className: styles.root,
|
|
511
|
+
"aria-live": "polite",
|
|
512
|
+
"aria-atomic": "true",
|
|
513
|
+
"data-testid": "agentkit-live-polite",
|
|
514
|
+
children: politeText
|
|
515
|
+
}
|
|
516
|
+
),
|
|
517
|
+
/* @__PURE__ */ jsx(
|
|
518
|
+
"div",
|
|
519
|
+
{
|
|
520
|
+
className: styles.root,
|
|
521
|
+
role: "alert",
|
|
522
|
+
"aria-live": "assertive",
|
|
523
|
+
"aria-atomic": "true",
|
|
524
|
+
"data-testid": "agentkit-live-assertive",
|
|
525
|
+
children: assertiveText
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
] });
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/a11y/useFocusRestore.ts
|
|
532
|
+
import * as React2 from "react";
|
|
533
|
+
function useFocusRestore(containerRef) {
|
|
534
|
+
return React2.useCallback(() => {
|
|
535
|
+
const container = containerRef.current;
|
|
536
|
+
if (!container) return;
|
|
537
|
+
const doc = container.ownerDocument;
|
|
538
|
+
if (doc.activeElement && doc.activeElement !== doc.body) return;
|
|
539
|
+
const focusable = container.querySelector(
|
|
540
|
+
'a[href],button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])'
|
|
541
|
+
);
|
|
542
|
+
(focusable ?? container).focus();
|
|
543
|
+
}, [containerRef]);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/a11y/useRovingTabindex.ts
|
|
547
|
+
import * as React3 from "react";
|
|
548
|
+
function useRovingTabindex(options) {
|
|
549
|
+
const { orientation = "horizontal", loop = true, itemCount, onActivate } = options;
|
|
550
|
+
const [activeIndex, setActiveIndex] = React3.useState(0);
|
|
551
|
+
const clamp = React3.useCallback(
|
|
552
|
+
(index) => {
|
|
553
|
+
if (itemCount === 0) return 0;
|
|
554
|
+
return loop ? (index + itemCount) % itemCount : Math.min(Math.max(index, 0), itemCount - 1);
|
|
555
|
+
},
|
|
556
|
+
[itemCount, loop]
|
|
557
|
+
);
|
|
558
|
+
const getItemProps = React3.useCallback(
|
|
559
|
+
(index) => ({
|
|
560
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
561
|
+
"data-roving-item": true,
|
|
562
|
+
onFocus: () => setActiveIndex(index),
|
|
563
|
+
onKeyDown: (event) => {
|
|
564
|
+
const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
|
|
565
|
+
const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
|
|
566
|
+
let target = null;
|
|
567
|
+
if (event.key === nextKey) target = clamp(index + 1);
|
|
568
|
+
else if (event.key === prevKey) target = clamp(index - 1);
|
|
569
|
+
else if (event.key === "Home") target = 0;
|
|
570
|
+
else if (event.key === "End") target = itemCount - 1;
|
|
571
|
+
else if (event.key === "Enter" || event.key === " ") {
|
|
572
|
+
onActivate?.(index);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (target === null) return;
|
|
576
|
+
event.preventDefault();
|
|
577
|
+
event.stopPropagation();
|
|
578
|
+
setActiveIndex(target);
|
|
579
|
+
const group = event.currentTarget.parentElement;
|
|
580
|
+
group?.querySelectorAll("[data-roving-item]")[target]?.focus();
|
|
581
|
+
}
|
|
582
|
+
}),
|
|
583
|
+
[activeIndex, clamp, itemCount, onActivate, orientation]
|
|
584
|
+
);
|
|
585
|
+
return { activeIndex, setActiveIndex, getItemProps };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/hooks/useStickyScroll.ts
|
|
589
|
+
import * as React4 from "react";
|
|
590
|
+
function useStickyScroll(ref, messageCount) {
|
|
591
|
+
const wasPinnedRef = React4.useRef(true);
|
|
592
|
+
const [isPinned, setIsPinned] = React4.useState(true);
|
|
593
|
+
const onScroll = React4.useCallback(() => {
|
|
594
|
+
const el = ref.current;
|
|
595
|
+
if (!el) return;
|
|
596
|
+
const pinned = shouldStickToBottom({
|
|
597
|
+
scrollTop: el.scrollTop,
|
|
598
|
+
scrollHeight: el.scrollHeight,
|
|
599
|
+
clientHeight: el.clientHeight
|
|
600
|
+
});
|
|
601
|
+
wasPinnedRef.current = pinned;
|
|
602
|
+
setIsPinned(pinned);
|
|
603
|
+
}, [ref]);
|
|
604
|
+
const scrollToBottom = React4.useCallback(() => {
|
|
605
|
+
const el = ref.current;
|
|
606
|
+
if (!el) return;
|
|
607
|
+
el.scrollTo({ top: el.scrollHeight, behavior: "auto" });
|
|
608
|
+
wasPinnedRef.current = true;
|
|
609
|
+
setIsPinned(true);
|
|
610
|
+
}, [ref]);
|
|
611
|
+
React4.useEffect(() => {
|
|
612
|
+
if (!wasPinnedRef.current) return;
|
|
613
|
+
const el = ref.current;
|
|
614
|
+
if (!el) return;
|
|
615
|
+
el.scrollTo({ top: el.scrollHeight, behavior: "auto" });
|
|
616
|
+
}, [messageCount, ref]);
|
|
617
|
+
return { onScroll, scrollToBottom, isPinned };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/hooks/useConversation.ts
|
|
621
|
+
import * as React5 from "react";
|
|
622
|
+
var fallbackId = 0;
|
|
623
|
+
function useConversation(options) {
|
|
624
|
+
const { service, seed, grounding, conversationId, createId } = options;
|
|
625
|
+
const [state, setState] = React5.useState(() => initialConversation(seed));
|
|
626
|
+
const [announcement, setAnnouncement] = React5.useState(NO_ANNOUNCEMENT);
|
|
627
|
+
const abortRef = React5.useRef(null);
|
|
628
|
+
const stateRef = React5.useRef(state);
|
|
629
|
+
stateRef.current = state;
|
|
630
|
+
const nextId = React5.useCallback(
|
|
631
|
+
() => createId?.() ?? `m${++fallbackId}-${Date.now().toString(36)}`,
|
|
632
|
+
[createId]
|
|
633
|
+
);
|
|
634
|
+
const commit = React5.useCallback((next) => {
|
|
635
|
+
const previous = stateRef.current;
|
|
636
|
+
stateRef.current = next;
|
|
637
|
+
setState(next);
|
|
638
|
+
const derived = deriveAnnouncement(next, previous);
|
|
639
|
+
if (derived.polite || derived.assertive) setAnnouncement(derived);
|
|
640
|
+
}, []);
|
|
641
|
+
const cancel = React5.useCallback(() => {
|
|
642
|
+
abortRef.current?.abort();
|
|
643
|
+
}, []);
|
|
644
|
+
const send = React5.useCallback(
|
|
645
|
+
async (text, attachments) => {
|
|
646
|
+
if (!text.trim()) return;
|
|
647
|
+
abortRef.current?.abort();
|
|
648
|
+
const controller = new AbortController();
|
|
649
|
+
abortRef.current = controller;
|
|
650
|
+
let next = appendUserMessage(stateRef.current, { id: nextId(), text, attachments });
|
|
651
|
+
commit(next);
|
|
652
|
+
try {
|
|
653
|
+
for await (const event of service.send({
|
|
654
|
+
text,
|
|
655
|
+
history: next.messages,
|
|
656
|
+
grounding,
|
|
657
|
+
attachments,
|
|
658
|
+
conversationId,
|
|
659
|
+
cancelSignal: controller.signal
|
|
660
|
+
})) {
|
|
661
|
+
next = applyAgentEvent(next, event);
|
|
662
|
+
commit(next);
|
|
663
|
+
}
|
|
664
|
+
} catch (error) {
|
|
665
|
+
commit({
|
|
666
|
+
...next,
|
|
667
|
+
streamingMessageId: null,
|
|
668
|
+
status: "error",
|
|
669
|
+
failure: {
|
|
670
|
+
status: "error",
|
|
671
|
+
message: error instanceof Error ? error.message : String(error),
|
|
672
|
+
recoverable: true
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
} finally {
|
|
676
|
+
if (abortRef.current === controller) abortRef.current = null;
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
[service, grounding, conversationId, nextId, commit]
|
|
680
|
+
);
|
|
681
|
+
React5.useEffect(() => () => abortRef.current?.abort(), []);
|
|
682
|
+
const busy = state.status === "connecting" || state.status === "streaming";
|
|
683
|
+
return { state, announcement, busy, send, cancel };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/conversation/MessageBubble.tsx
|
|
687
|
+
import * as React9 from "react";
|
|
688
|
+
import { Avatar, makeStyles as makeStyles7, mergeClasses as mergeClasses4, tokens as tokens6 } from "@fluentui/react-components";
|
|
689
|
+
|
|
690
|
+
// src/conversation/StreamingText.tsx
|
|
691
|
+
import { makeStyles as makeStyles2, tokens } from "@fluentui/react-components";
|
|
692
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
693
|
+
var useStyles = makeStyles2({
|
|
694
|
+
root: { whiteSpace: "pre-wrap", wordBreak: "break-word" },
|
|
695
|
+
caret: {
|
|
696
|
+
display: "inline-block",
|
|
697
|
+
width: "2px",
|
|
698
|
+
height: "1em",
|
|
699
|
+
marginLeft: "2px",
|
|
700
|
+
verticalAlign: "text-bottom",
|
|
701
|
+
backgroundColor: tokens.colorBrandForeground1,
|
|
702
|
+
animationName: { "0%,49%": { opacity: 1 }, "50%,100%": { opacity: 0 } },
|
|
703
|
+
animationDuration: "1s",
|
|
704
|
+
animationIterationCount: "infinite",
|
|
705
|
+
// WCAG 2.2.2: a blinking caret is motion, and some readers cannot tolerate it.
|
|
706
|
+
"@media (prefers-reduced-motion: reduce)": { animationName: "none", opacity: 1 }
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
function StreamingText({ text, streaming }) {
|
|
710
|
+
const styles = useStyles();
|
|
711
|
+
return /* @__PURE__ */ jsxs2("span", { className: styles.root, "data-testid": "agentkit-streaming-text", children: [
|
|
712
|
+
text,
|
|
713
|
+
streaming ? /* @__PURE__ */ jsx2("span", { className: styles.caret, "aria-hidden": "true" }) : null
|
|
714
|
+
] });
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/conversation/MessageActions.tsx
|
|
718
|
+
import { CopyRegular, ArrowClockwiseRegular, ThumbLikeRegular, ThumbDislikeRegular } from "@fluentui/react-icons";
|
|
719
|
+
|
|
720
|
+
// src/internal/ActionToolbar.tsx
|
|
721
|
+
import { Button, Tooltip, makeStyles as makeStyles3, tokens as tokens2, mergeClasses } from "@fluentui/react-components";
|
|
722
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
723
|
+
var useStyles2 = makeStyles3({
|
|
724
|
+
root: {
|
|
725
|
+
display: "inline-flex",
|
|
726
|
+
gap: tokens2.spacingHorizontalXXS,
|
|
727
|
+
alignItems: "center",
|
|
728
|
+
backgroundColor: tokens2.colorNeutralBackground1,
|
|
729
|
+
borderRadius: tokens2.borderRadiusMedium,
|
|
730
|
+
boxShadow: tokens2.shadow4,
|
|
731
|
+
padding: tokens2.spacingHorizontalXXS
|
|
732
|
+
},
|
|
733
|
+
button: {
|
|
734
|
+
// ★ A focus ring that survives Windows forced-colors. Once the kit owns its own focus
|
|
735
|
+
// management it owns the indicator too, and `outline: none` + box-shadow — the usual
|
|
736
|
+
// custom-focus recipe — disappears entirely under forced-colors.
|
|
737
|
+
":focus-visible": {
|
|
738
|
+
outline: `2px solid ${tokens2.colorStrokeFocus2}`,
|
|
739
|
+
outlineOffset: "1px"
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
function ActionToolbar({ actions, label, className }) {
|
|
744
|
+
const styles = useStyles2();
|
|
745
|
+
const roving = useRovingTabindex({ itemCount: actions.length, orientation: "horizontal" });
|
|
746
|
+
return /* @__PURE__ */ jsx3("div", { role: "toolbar", "aria-label": label, "aria-orientation": "horizontal", className: mergeClasses(styles.root, className), children: actions.map((action, index) => {
|
|
747
|
+
const item = roving.getItemProps(index);
|
|
748
|
+
return /* @__PURE__ */ jsx3(Tooltip, { content: action.label, relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx3(
|
|
749
|
+
Button,
|
|
750
|
+
{
|
|
751
|
+
appearance: "subtle",
|
|
752
|
+
size: "small",
|
|
753
|
+
icon: action.icon,
|
|
754
|
+
disabled: action.disabled,
|
|
755
|
+
onClick: action.onClick,
|
|
756
|
+
className: styles.button,
|
|
757
|
+
...item
|
|
758
|
+
}
|
|
759
|
+
) }, action.key);
|
|
760
|
+
}) });
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/internal/CardSurface.tsx
|
|
764
|
+
import * as React6 from "react";
|
|
765
|
+
import { makeStyles as makeStyles4, mergeClasses as mergeClasses2, tokens as tokens3 } from "@fluentui/react-components";
|
|
766
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
767
|
+
var useStyles3 = makeStyles4({
|
|
768
|
+
root: {
|
|
769
|
+
display: "flex",
|
|
770
|
+
flexDirection: "column",
|
|
771
|
+
gap: tokens3.spacingVerticalXS,
|
|
772
|
+
padding: tokens3.spacingVerticalM,
|
|
773
|
+
borderRadius: tokens3.borderRadiusMedium,
|
|
774
|
+
backgroundColor: tokens3.colorNeutralBackground1,
|
|
775
|
+
border: `1px solid ${tokens3.colorNeutralStroke2}`,
|
|
776
|
+
boxShadow: tokens3.shadow2
|
|
777
|
+
},
|
|
778
|
+
attention: { borderLeft: `3px solid ${tokens3.colorBrandStroke1}` }
|
|
779
|
+
});
|
|
780
|
+
var CardSurface = React6.forwardRef(
|
|
781
|
+
function CardSurface2({ emphasis = "none", className, children, ...rest }, ref) {
|
|
782
|
+
const styles = useStyles3();
|
|
783
|
+
return /* @__PURE__ */ jsx4(
|
|
784
|
+
"div",
|
|
785
|
+
{
|
|
786
|
+
ref,
|
|
787
|
+
className: mergeClasses2(styles.root, emphasis === "attention" && styles.attention, className),
|
|
788
|
+
...rest,
|
|
789
|
+
children
|
|
790
|
+
}
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
// src/internal/ConfirmInline.tsx
|
|
796
|
+
import * as React7 from "react";
|
|
797
|
+
import { Button as Button2, makeStyles as makeStyles5, tokens as tokens4, Text } from "@fluentui/react-components";
|
|
798
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
799
|
+
var useStyles4 = makeStyles5({
|
|
800
|
+
root: { maxWidth: "420px" },
|
|
801
|
+
actions: { display: "flex", gap: tokens4.spacingHorizontalS, justifyContent: "flex-end", marginTop: tokens4.spacingVerticalS }
|
|
802
|
+
});
|
|
803
|
+
var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
804
|
+
function ConfirmInline(props) {
|
|
805
|
+
const { open, title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", destructive, onConfirm, onCancel } = props;
|
|
806
|
+
const styles = useStyles4();
|
|
807
|
+
const ref = React7.useRef(null);
|
|
808
|
+
const returnTo = React7.useRef(null);
|
|
809
|
+
const titleId = React7.useId();
|
|
810
|
+
const bodyId = React7.useId();
|
|
811
|
+
React7.useEffect(() => {
|
|
812
|
+
if (!open) return;
|
|
813
|
+
returnTo.current = document.activeElement ?? null;
|
|
814
|
+
const first = ref.current?.querySelector(FOCUSABLE);
|
|
815
|
+
first?.focus();
|
|
816
|
+
return () => {
|
|
817
|
+
const target = returnTo.current;
|
|
818
|
+
if (target && target.isConnected) target.focus();
|
|
819
|
+
};
|
|
820
|
+
}, [open]);
|
|
821
|
+
if (!open) return null;
|
|
822
|
+
const onKeyDown = (event) => {
|
|
823
|
+
if (event.key === "Escape") {
|
|
824
|
+
event.stopPropagation();
|
|
825
|
+
onCancel();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (event.key !== "Tab") return;
|
|
829
|
+
const items = [...ref.current?.querySelectorAll(FOCUSABLE) ?? []];
|
|
830
|
+
if (items.length === 0) return;
|
|
831
|
+
const first = items[0];
|
|
832
|
+
const last = items[items.length - 1];
|
|
833
|
+
const active = document.activeElement;
|
|
834
|
+
if (event.shiftKey && active === first) {
|
|
835
|
+
event.preventDefault();
|
|
836
|
+
last.focus();
|
|
837
|
+
} else if (!event.shiftKey && active === last) {
|
|
838
|
+
event.preventDefault();
|
|
839
|
+
first.focus();
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
return /* @__PURE__ */ jsxs3(
|
|
843
|
+
CardSurface,
|
|
844
|
+
{
|
|
845
|
+
ref,
|
|
846
|
+
role: "alertdialog",
|
|
847
|
+
"aria-modal": "true",
|
|
848
|
+
"aria-labelledby": titleId,
|
|
849
|
+
"aria-describedby": bodyId,
|
|
850
|
+
onKeyDown,
|
|
851
|
+
className: styles.root,
|
|
852
|
+
emphasis: "attention",
|
|
853
|
+
children: [
|
|
854
|
+
/* @__PURE__ */ jsx5(Text, { id: titleId, weight: "semibold", children: title }),
|
|
855
|
+
/* @__PURE__ */ jsx5(Text, { id: bodyId, size: 200, children: body }),
|
|
856
|
+
/* @__PURE__ */ jsxs3("div", { className: styles.actions, children: [
|
|
857
|
+
/* @__PURE__ */ jsx5(Button2, { size: "small", onClick: onCancel, children: cancelLabel }),
|
|
858
|
+
/* @__PURE__ */ jsx5(Button2, { size: "small", appearance: destructive ? "primary" : "primary", onClick: onConfirm, children: confirmLabel })
|
|
859
|
+
] })
|
|
860
|
+
]
|
|
861
|
+
}
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/internal/Disclosure.tsx
|
|
866
|
+
import * as React8 from "react";
|
|
867
|
+
import { makeStyles as makeStyles6, tokens as tokens5, mergeClasses as mergeClasses3 } from "@fluentui/react-components";
|
|
868
|
+
import { ChevronRightRegular, ChevronDownRegular } from "@fluentui/react-icons";
|
|
869
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
870
|
+
var useStyles5 = makeStyles6({
|
|
871
|
+
trigger: {
|
|
872
|
+
display: "flex",
|
|
873
|
+
alignItems: "center",
|
|
874
|
+
gap: tokens5.spacingHorizontalXS,
|
|
875
|
+
background: "none",
|
|
876
|
+
border: "none",
|
|
877
|
+
cursor: "pointer",
|
|
878
|
+
padding: `${tokens5.spacingVerticalXXS} 0`,
|
|
879
|
+
color: tokens5.colorNeutralForeground2,
|
|
880
|
+
font: "inherit",
|
|
881
|
+
fontSize: tokens5.fontSizeBase200,
|
|
882
|
+
":focus-visible": { outline: `2px solid ${tokens5.colorStrokeFocus2}`, outlineOffset: "1px" }
|
|
883
|
+
},
|
|
884
|
+
panel: { paddingTop: tokens5.spacingVerticalXS }
|
|
885
|
+
});
|
|
886
|
+
function Disclosure({
|
|
887
|
+
id,
|
|
888
|
+
summary,
|
|
889
|
+
children,
|
|
890
|
+
defaultOpen = false,
|
|
891
|
+
className
|
|
892
|
+
}) {
|
|
893
|
+
const styles = useStyles5();
|
|
894
|
+
const [open, setOpen] = React8.useState(defaultOpen);
|
|
895
|
+
const panelId = `${id}-panel`;
|
|
896
|
+
return /* @__PURE__ */ jsxs4("div", { className, children: [
|
|
897
|
+
/* @__PURE__ */ jsxs4(
|
|
898
|
+
"button",
|
|
899
|
+
{
|
|
900
|
+
type: "button",
|
|
901
|
+
id,
|
|
902
|
+
className: styles.trigger,
|
|
903
|
+
"aria-expanded": open,
|
|
904
|
+
"aria-controls": panelId,
|
|
905
|
+
onClick: () => setOpen((v) => !v),
|
|
906
|
+
children: [
|
|
907
|
+
open ? /* @__PURE__ */ jsx6(ChevronDownRegular, {}) : /* @__PURE__ */ jsx6(ChevronRightRegular, {}),
|
|
908
|
+
summary
|
|
909
|
+
]
|
|
910
|
+
}
|
|
911
|
+
),
|
|
912
|
+
/* @__PURE__ */ jsx6(
|
|
913
|
+
"div",
|
|
914
|
+
{
|
|
915
|
+
id: panelId,
|
|
916
|
+
role: "region",
|
|
917
|
+
"aria-labelledby": id,
|
|
918
|
+
hidden: !open,
|
|
919
|
+
className: mergeClasses3(styles.panel),
|
|
920
|
+
children: open ? children : null
|
|
921
|
+
}
|
|
922
|
+
)
|
|
923
|
+
] });
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/conversation/MessageActions.tsx
|
|
927
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
928
|
+
function MessageActions({
|
|
929
|
+
message,
|
|
930
|
+
onCopy,
|
|
931
|
+
onRetry,
|
|
932
|
+
onFeedback
|
|
933
|
+
}) {
|
|
934
|
+
const actions = [];
|
|
935
|
+
if (onCopy) {
|
|
936
|
+
actions.push({ key: "copy", label: "Copy message", icon: /* @__PURE__ */ jsx7(CopyRegular, {}), onClick: () => onCopy(message) });
|
|
937
|
+
}
|
|
938
|
+
if (onRetry && message.role === "assistant") {
|
|
939
|
+
actions.push({ key: "retry", label: "Regenerate", icon: /* @__PURE__ */ jsx7(ArrowClockwiseRegular, {}), onClick: () => onRetry(message) });
|
|
940
|
+
}
|
|
941
|
+
if (onFeedback && message.role === "assistant") {
|
|
942
|
+
actions.push(
|
|
943
|
+
{ key: "up", label: "Good response", icon: /* @__PURE__ */ jsx7(ThumbLikeRegular, {}), onClick: () => onFeedback(message, "up") },
|
|
944
|
+
{ key: "down", label: "Bad response", icon: /* @__PURE__ */ jsx7(ThumbDislikeRegular, {}), onClick: () => onFeedback(message, "down") }
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
if (actions.length === 0) return null;
|
|
948
|
+
return /* @__PURE__ */ jsx7(ActionToolbar, { actions, label: `Actions for this message` });
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/conversation/MessageBubble.tsx
|
|
952
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
953
|
+
var useStyles6 = makeStyles7({
|
|
954
|
+
row: {
|
|
955
|
+
display: "flex",
|
|
956
|
+
gap: tokens6.spacingHorizontalS,
|
|
957
|
+
position: "relative",
|
|
958
|
+
paddingRight: tokens6.spacingHorizontalXXL
|
|
959
|
+
},
|
|
960
|
+
own: { flexDirection: "row-reverse", paddingRight: 0, paddingLeft: tokens6.spacingHorizontalXXL },
|
|
961
|
+
gutter: { width: "32px", flexShrink: 0 },
|
|
962
|
+
stack: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0, maxWidth: "80%" },
|
|
963
|
+
name: { fontSize: tokens6.fontSizeBase200, color: tokens6.colorNeutralForeground3 },
|
|
964
|
+
bubble: {
|
|
965
|
+
padding: `${tokens6.spacingVerticalS} ${tokens6.spacingHorizontalM}`,
|
|
966
|
+
borderRadius: tokens6.borderRadiusLarge,
|
|
967
|
+
backgroundColor: tokens6.colorNeutralBackground3,
|
|
968
|
+
color: tokens6.colorNeutralForeground1,
|
|
969
|
+
fontSize: tokens6.fontSizeBase300
|
|
970
|
+
},
|
|
971
|
+
ownBubble: {
|
|
972
|
+
backgroundColor: tokens6.colorBrandBackground,
|
|
973
|
+
color: tokens6.colorNeutralForegroundOnBrand
|
|
974
|
+
},
|
|
975
|
+
actions: {
|
|
976
|
+
position: "absolute",
|
|
977
|
+
top: "-12px",
|
|
978
|
+
right: tokens6.spacingHorizontalS,
|
|
979
|
+
opacity: 0,
|
|
980
|
+
pointerEvents: "none",
|
|
981
|
+
transitionProperty: "opacity",
|
|
982
|
+
transitionDuration: tokens6.durationFaster
|
|
983
|
+
},
|
|
984
|
+
// ★ hover OR focus-within. Without the second selector the row is unreachable by keyboard.
|
|
985
|
+
actionsVisible: { opacity: 1, pointerEvents: "auto" }
|
|
986
|
+
});
|
|
987
|
+
function MessageBubble(props) {
|
|
988
|
+
const { message, author, isRunStart = true, locale, ...actionHandlers } = props;
|
|
989
|
+
const styles = useStyles6();
|
|
990
|
+
const hidden = useVisuallyHiddenStyles();
|
|
991
|
+
const [revealed, setRevealed] = React9.useState(false);
|
|
992
|
+
const own = message.role === "user";
|
|
993
|
+
const name = author?.displayName ?? (own ? "You" : message.provider ?? "Assistant");
|
|
994
|
+
const time = new Intl.DateTimeFormat(locale, { timeStyle: "short" }).format(new Date(message.createdAt));
|
|
995
|
+
return /* @__PURE__ */ jsxs5(
|
|
996
|
+
"div",
|
|
997
|
+
{
|
|
998
|
+
className: mergeClasses4(styles.row, own && styles.own),
|
|
999
|
+
onMouseEnter: () => setRevealed(true),
|
|
1000
|
+
onMouseLeave: () => setRevealed(false),
|
|
1001
|
+
onFocus: () => setRevealed(true),
|
|
1002
|
+
onBlur: (e) => {
|
|
1003
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setRevealed(false);
|
|
1004
|
+
},
|
|
1005
|
+
"data-testid": "agentkit-message",
|
|
1006
|
+
children: [
|
|
1007
|
+
/* @__PURE__ */ jsx8("div", { className: styles.gutter, children: isRunStart ? /* @__PURE__ */ jsx8(Avatar, { name, size: 32, image: author?.imageUrl ? { src: author.imageUrl } : void 0 }) : null }),
|
|
1008
|
+
/* @__PURE__ */ jsxs5("div", { className: styles.stack, children: [
|
|
1009
|
+
/* @__PURE__ */ jsxs5("span", { className: hidden.root, children: [
|
|
1010
|
+
name,
|
|
1011
|
+
", ",
|
|
1012
|
+
/* @__PURE__ */ jsx8("time", { dateTime: new Date(message.createdAt).toISOString(), children: time })
|
|
1013
|
+
] }),
|
|
1014
|
+
isRunStart ? /* @__PURE__ */ jsxs5("span", { className: styles.name, "aria-hidden": "true", children: [
|
|
1015
|
+
name,
|
|
1016
|
+
" \xB7 ",
|
|
1017
|
+
time
|
|
1018
|
+
] }) : null,
|
|
1019
|
+
/* @__PURE__ */ jsx8(
|
|
1020
|
+
"div",
|
|
1021
|
+
{
|
|
1022
|
+
className: mergeClasses4(styles.bubble, own && styles.ownBubble),
|
|
1023
|
+
"aria-busy": message.streaming || void 0,
|
|
1024
|
+
children: /* @__PURE__ */ jsx8(StreamingText, { text: message.text, streaming: message.streaming })
|
|
1025
|
+
}
|
|
1026
|
+
)
|
|
1027
|
+
] }),
|
|
1028
|
+
/* @__PURE__ */ jsx8("div", { className: mergeClasses4(styles.actions, revealed && styles.actionsVisible), children: /* @__PURE__ */ jsx8(MessageActions, { message, ...actionHandlers }) })
|
|
1029
|
+
]
|
|
1030
|
+
}
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/conversation/DayDivider.tsx
|
|
1035
|
+
import { makeStyles as makeStyles8, tokens as tokens7 } from "@fluentui/react-components";
|
|
1036
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1037
|
+
var useStyles7 = makeStyles8({
|
|
1038
|
+
root: {
|
|
1039
|
+
display: "flex",
|
|
1040
|
+
alignItems: "center",
|
|
1041
|
+
gap: tokens7.spacingHorizontalM,
|
|
1042
|
+
margin: `${tokens7.spacingVerticalM} 0`,
|
|
1043
|
+
color: tokens7.colorNeutralForeground3,
|
|
1044
|
+
fontSize: tokens7.fontSizeBase200
|
|
1045
|
+
},
|
|
1046
|
+
rule: { flex: 1, height: "1px", backgroundColor: tokens7.colorNeutralStroke2 }
|
|
1047
|
+
});
|
|
1048
|
+
function formatDayLabel(label, locale) {
|
|
1049
|
+
switch (label.kind) {
|
|
1050
|
+
case "today":
|
|
1051
|
+
return "Today";
|
|
1052
|
+
case "yesterday":
|
|
1053
|
+
return "Yesterday";
|
|
1054
|
+
case "weekday":
|
|
1055
|
+
return new Intl.DateTimeFormat(locale, { weekday: "long", timeZone: "UTC" }).format(
|
|
1056
|
+
/* @__PURE__ */ new Date(`${label.iso}T00:00:00Z`)
|
|
1057
|
+
);
|
|
1058
|
+
case "date":
|
|
1059
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeZone: "UTC" }).format(
|
|
1060
|
+
/* @__PURE__ */ new Date(`${label.iso}T00:00:00Z`)
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function DayDivider({ label, locale }) {
|
|
1065
|
+
const styles = useStyles7();
|
|
1066
|
+
const text = formatDayLabel(label, locale);
|
|
1067
|
+
return /* @__PURE__ */ jsxs6("div", { className: styles.root, role: "separator", "aria-label": text, children: [
|
|
1068
|
+
/* @__PURE__ */ jsx9("span", { className: styles.rule, "aria-hidden": "true" }),
|
|
1069
|
+
/* @__PURE__ */ jsx9("span", { children: text }),
|
|
1070
|
+
/* @__PURE__ */ jsx9("span", { className: styles.rule, "aria-hidden": "true" })
|
|
1071
|
+
] });
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/conversation/TypingIndicator.tsx
|
|
1075
|
+
import { makeStyles as makeStyles9, tokens as tokens8 } from "@fluentui/react-components";
|
|
1076
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1077
|
+
var useStyles8 = makeStyles9({
|
|
1078
|
+
root: { display: "inline-flex", gap: "4px", alignItems: "center", padding: tokens8.spacingVerticalXS },
|
|
1079
|
+
dot: {
|
|
1080
|
+
width: "6px",
|
|
1081
|
+
height: "6px",
|
|
1082
|
+
borderRadius: tokens8.borderRadiusCircular,
|
|
1083
|
+
backgroundColor: tokens8.colorNeutralForeground3,
|
|
1084
|
+
animationName: { "0%,80%,100%": { opacity: 0.3 }, "40%": { opacity: 1 } },
|
|
1085
|
+
animationDuration: "1.4s",
|
|
1086
|
+
animationIterationCount: "infinite",
|
|
1087
|
+
"@media (prefers-reduced-motion: reduce)": { animationName: "none", opacity: 0.6 }
|
|
1088
|
+
},
|
|
1089
|
+
d2: { animationDelay: "0.2s" },
|
|
1090
|
+
d3: { animationDelay: "0.4s" }
|
|
1091
|
+
});
|
|
1092
|
+
function TypingIndicator({ label = "Assistant is responding" }) {
|
|
1093
|
+
const styles = useStyles8();
|
|
1094
|
+
return /* @__PURE__ */ jsxs7("div", { className: styles.root, role: "status", "aria-label": label, "data-testid": "agentkit-typing", children: [
|
|
1095
|
+
/* @__PURE__ */ jsx10("span", { className: styles.dot, "aria-hidden": "true" }),
|
|
1096
|
+
/* @__PURE__ */ jsx10("span", { className: `${styles.dot} ${styles.d2}`, "aria-hidden": "true" }),
|
|
1097
|
+
/* @__PURE__ */ jsx10("span", { className: `${styles.dot} ${styles.d3}`, "aria-hidden": "true" })
|
|
1098
|
+
] });
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/conversation/MessageList.tsx
|
|
1102
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1103
|
+
var useStyles9 = makeStyles10({
|
|
1104
|
+
root: { position: "relative", height: "100%", minHeight: 0, display: "flex", flexDirection: "column" },
|
|
1105
|
+
scroller: {
|
|
1106
|
+
flex: 1,
|
|
1107
|
+
overflowY: "auto",
|
|
1108
|
+
padding: tokens9.spacingVerticalM,
|
|
1109
|
+
display: "flex",
|
|
1110
|
+
flexDirection: "column",
|
|
1111
|
+
gap: tokens9.spacingVerticalS
|
|
1112
|
+
},
|
|
1113
|
+
jump: { position: "absolute", bottom: tokens9.spacingVerticalM, alignSelf: "center", zIndex: 1 }
|
|
1114
|
+
});
|
|
1115
|
+
function MessageList(props) {
|
|
1116
|
+
const {
|
|
1117
|
+
messages,
|
|
1118
|
+
participants,
|
|
1119
|
+
busy = false,
|
|
1120
|
+
announcement,
|
|
1121
|
+
groupWindowMs,
|
|
1122
|
+
timeZone,
|
|
1123
|
+
locale,
|
|
1124
|
+
now,
|
|
1125
|
+
emptyState,
|
|
1126
|
+
...actionHandlers
|
|
1127
|
+
} = props;
|
|
1128
|
+
const styles = useStyles9();
|
|
1129
|
+
const scrollerRef = React10.useRef(null);
|
|
1130
|
+
const { onScroll, scrollToBottom, isPinned } = useStickyScroll(scrollerRef, messages.length);
|
|
1131
|
+
const byId = React10.useMemo(
|
|
1132
|
+
() => new Map((participants ?? []).map((p) => [p.id, p])),
|
|
1133
|
+
[participants]
|
|
1134
|
+
);
|
|
1135
|
+
const runStarts = React10.useMemo(() => {
|
|
1136
|
+
const groups = groupMessages(messages, { windowMs: groupWindowMs, timeZone });
|
|
1137
|
+
return new Set(groups.map((g) => g.messageIds[0]));
|
|
1138
|
+
}, [messages, groupWindowMs, timeZone]);
|
|
1139
|
+
const buckets = React10.useMemo(
|
|
1140
|
+
() => bucketByDay(messages, { timeZone, now }),
|
|
1141
|
+
[messages, timeZone, now]
|
|
1142
|
+
);
|
|
1143
|
+
const dividerBefore = React10.useMemo(() => {
|
|
1144
|
+
const map = /* @__PURE__ */ new Map();
|
|
1145
|
+
for (const bucket of buckets) map.set(bucket.messageIds[0], bucket.label);
|
|
1146
|
+
return map;
|
|
1147
|
+
}, [buckets]);
|
|
1148
|
+
return /* @__PURE__ */ jsxs8("div", { className: styles.root, children: [
|
|
1149
|
+
/* @__PURE__ */ jsx11(LiveAnnouncer, { polite: announcement?.polite, assertive: announcement?.assertive }),
|
|
1150
|
+
/* @__PURE__ */ jsxs8(
|
|
1151
|
+
"div",
|
|
1152
|
+
{
|
|
1153
|
+
ref: scrollerRef,
|
|
1154
|
+
className: styles.scroller,
|
|
1155
|
+
onScroll,
|
|
1156
|
+
role: "log",
|
|
1157
|
+
"aria-label": "Conversation",
|
|
1158
|
+
tabIndex: 0,
|
|
1159
|
+
children: [
|
|
1160
|
+
messages.length === 0 ? emptyState ?? null : messages.map((message) => /* @__PURE__ */ jsxs8(React10.Fragment, { children: [
|
|
1161
|
+
dividerBefore.has(message.id) ? /* @__PURE__ */ jsx11(DayDivider, { label: dividerBefore.get(message.id), locale }) : null,
|
|
1162
|
+
/* @__PURE__ */ jsx11(
|
|
1163
|
+
MessageBubble,
|
|
1164
|
+
{
|
|
1165
|
+
message,
|
|
1166
|
+
author: byId.get(message.authorId),
|
|
1167
|
+
isRunStart: runStarts.has(message.id),
|
|
1168
|
+
locale,
|
|
1169
|
+
...actionHandlers
|
|
1170
|
+
}
|
|
1171
|
+
)
|
|
1172
|
+
] }, message.id)),
|
|
1173
|
+
busy ? /* @__PURE__ */ jsx11(TypingIndicator, {}) : null
|
|
1174
|
+
]
|
|
1175
|
+
}
|
|
1176
|
+
),
|
|
1177
|
+
!isPinned && messages.length > 0 ? /* @__PURE__ */ jsx11(
|
|
1178
|
+
Button3,
|
|
1179
|
+
{
|
|
1180
|
+
className: styles.jump,
|
|
1181
|
+
size: "small",
|
|
1182
|
+
appearance: "primary",
|
|
1183
|
+
icon: /* @__PURE__ */ jsx11(ArrowDownRegular, {}),
|
|
1184
|
+
onClick: scrollToBottom,
|
|
1185
|
+
children: "Jump to latest"
|
|
1186
|
+
}
|
|
1187
|
+
) : null
|
|
1188
|
+
] });
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/conversation/Composer.tsx
|
|
1192
|
+
import * as React11 from "react";
|
|
1193
|
+
import { Button as Button4, Textarea, makeStyles as makeStyles11, tokens as tokens10, Tooltip as Tooltip2 } from "@fluentui/react-components";
|
|
1194
|
+
import { SendRegular, StopRegular } from "@fluentui/react-icons";
|
|
1195
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1196
|
+
var useStyles10 = makeStyles11({
|
|
1197
|
+
root: {
|
|
1198
|
+
display: "flex",
|
|
1199
|
+
gap: tokens10.spacingHorizontalS,
|
|
1200
|
+
alignItems: "flex-end",
|
|
1201
|
+
padding: tokens10.spacingVerticalS,
|
|
1202
|
+
borderTop: `1px solid ${tokens10.colorNeutralStroke2}`
|
|
1203
|
+
},
|
|
1204
|
+
input: { flex: 1 },
|
|
1205
|
+
hint: { fontSize: tokens10.fontSizeBase100, color: tokens10.colorNeutralForeground3 }
|
|
1206
|
+
});
|
|
1207
|
+
function Composer(props) {
|
|
1208
|
+
const {
|
|
1209
|
+
value,
|
|
1210
|
+
onChange,
|
|
1211
|
+
onSend,
|
|
1212
|
+
onStop,
|
|
1213
|
+
busy = false,
|
|
1214
|
+
disabled = false,
|
|
1215
|
+
placeholder = "Type a message",
|
|
1216
|
+
maxLength,
|
|
1217
|
+
sendOn = "enter",
|
|
1218
|
+
toolbar
|
|
1219
|
+
} = props;
|
|
1220
|
+
const styles = useStyles10();
|
|
1221
|
+
const hintId = React11.useId();
|
|
1222
|
+
const submit = () => {
|
|
1223
|
+
const text = value.trim();
|
|
1224
|
+
if (!text || busy || disabled) return;
|
|
1225
|
+
onSend(text);
|
|
1226
|
+
};
|
|
1227
|
+
const onKeyDown = (event) => {
|
|
1228
|
+
if (event.key !== "Enter") return;
|
|
1229
|
+
const wants = sendOn === "enter" ? !event.shiftKey : event.metaKey || event.ctrlKey;
|
|
1230
|
+
if (!wants) return;
|
|
1231
|
+
event.preventDefault();
|
|
1232
|
+
submit();
|
|
1233
|
+
};
|
|
1234
|
+
const hint = sendOn === "enter" ? "Press Enter to send, Shift plus Enter for a new line." : "Press Control or Command plus Enter to send.";
|
|
1235
|
+
return /* @__PURE__ */ jsxs9("div", { className: styles.root, children: [
|
|
1236
|
+
/* @__PURE__ */ jsxs9("div", { className: styles.input, children: [
|
|
1237
|
+
/* @__PURE__ */ jsx12(
|
|
1238
|
+
Textarea,
|
|
1239
|
+
{
|
|
1240
|
+
value,
|
|
1241
|
+
onChange: (_, data) => onChange(data.value),
|
|
1242
|
+
onKeyDown,
|
|
1243
|
+
placeholder,
|
|
1244
|
+
disabled,
|
|
1245
|
+
maxLength,
|
|
1246
|
+
resize: "vertical",
|
|
1247
|
+
"aria-label": placeholder,
|
|
1248
|
+
"aria-describedby": hintId,
|
|
1249
|
+
textarea: { "aria-multiline": true }
|
|
1250
|
+
}
|
|
1251
|
+
),
|
|
1252
|
+
/* @__PURE__ */ jsx12("span", { id: hintId, className: styles.hint, children: hint })
|
|
1253
|
+
] }),
|
|
1254
|
+
toolbar,
|
|
1255
|
+
busy && onStop ? /* @__PURE__ */ jsx12(Tooltip2, { content: "Stop generating", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(Button4, { appearance: "secondary", icon: /* @__PURE__ */ jsx12(StopRegular, {}), onClick: onStop }) }) : /* @__PURE__ */ jsx12(Tooltip2, { content: "Send", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(
|
|
1256
|
+
Button4,
|
|
1257
|
+
{
|
|
1258
|
+
appearance: "primary",
|
|
1259
|
+
icon: /* @__PURE__ */ jsx12(SendRegular, {}),
|
|
1260
|
+
onClick: submit,
|
|
1261
|
+
disabled: !value.trim() || busy || disabled
|
|
1262
|
+
}
|
|
1263
|
+
) })
|
|
1264
|
+
] });
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/conversation/SuggestedPrompts.tsx
|
|
1268
|
+
import { Button as Button5, makeStyles as makeStyles12, tokens as tokens11 } from "@fluentui/react-components";
|
|
1269
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1270
|
+
var useStyles11 = makeStyles12({
|
|
1271
|
+
root: { display: "flex", flexWrap: "wrap", gap: tokens11.spacingHorizontalXS, padding: tokens11.spacingVerticalXS },
|
|
1272
|
+
heading: { width: "100%", fontSize: tokens11.fontSizeBase200, color: tokens11.colorNeutralForeground3 }
|
|
1273
|
+
});
|
|
1274
|
+
function SuggestedPrompts({
|
|
1275
|
+
prompts,
|
|
1276
|
+
onPick,
|
|
1277
|
+
heading = "Suggested follow-ups"
|
|
1278
|
+
}) {
|
|
1279
|
+
const styles = useStyles11();
|
|
1280
|
+
if (prompts.length === 0) return null;
|
|
1281
|
+
return /* @__PURE__ */ jsxs10("div", { className: styles.root, role: "group", "aria-label": heading, children: [
|
|
1282
|
+
/* @__PURE__ */ jsx13("span", { className: styles.heading, children: heading }),
|
|
1283
|
+
prompts.map((prompt) => /* @__PURE__ */ jsx13(Button5, { size: "small", appearance: "outline", onClick: () => onPick(prompt), children: prompt }, prompt))
|
|
1284
|
+
] });
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/conversation/EmptyConversation.tsx
|
|
1288
|
+
import { makeStyles as makeStyles13, tokens as tokens12, Text as Text2 } from "@fluentui/react-components";
|
|
1289
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1290
|
+
var useStyles12 = makeStyles13({
|
|
1291
|
+
root: {
|
|
1292
|
+
display: "flex",
|
|
1293
|
+
flexDirection: "column",
|
|
1294
|
+
alignItems: "center",
|
|
1295
|
+
justifyContent: "center",
|
|
1296
|
+
gap: tokens12.spacingVerticalS,
|
|
1297
|
+
height: "100%",
|
|
1298
|
+
padding: tokens12.spacingVerticalXXL,
|
|
1299
|
+
textAlign: "center",
|
|
1300
|
+
color: tokens12.colorNeutralForeground3
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
function EmptyConversation({
|
|
1304
|
+
title = "Ask the agent",
|
|
1305
|
+
description = "It answers from the records you have open.",
|
|
1306
|
+
prompts = [],
|
|
1307
|
+
onPick
|
|
1308
|
+
}) {
|
|
1309
|
+
const styles = useStyles12();
|
|
1310
|
+
return /* @__PURE__ */ jsxs11("div", { className: styles.root, children: [
|
|
1311
|
+
/* @__PURE__ */ jsx14(Text2, { as: "h2", size: 500, weight: "semibold", children: title }),
|
|
1312
|
+
/* @__PURE__ */ jsx14(Text2, { children: description }),
|
|
1313
|
+
onPick && prompts.length > 0 ? /* @__PURE__ */ jsx14(SuggestedPrompts, { prompts, onPick, heading: "Try one of these" }) : null
|
|
1314
|
+
] });
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// src/conversation/SystemNotice.tsx
|
|
1318
|
+
import {
|
|
1319
|
+
MessageBar,
|
|
1320
|
+
MessageBarBody,
|
|
1321
|
+
MessageBarTitle,
|
|
1322
|
+
MessageBarActions,
|
|
1323
|
+
Button as Button6
|
|
1324
|
+
} from "@fluentui/react-components";
|
|
1325
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1326
|
+
var INTENT = { success: "success", warning: "warning", error: "error", info: "info" };
|
|
1327
|
+
function SystemNotice({ failure, guarded, onRetry }) {
|
|
1328
|
+
if (!failure && !guarded) return null;
|
|
1329
|
+
if (!failure && guarded) {
|
|
1330
|
+
return /* @__PURE__ */ jsx15(MessageBar, { intent: "warning", children: /* @__PURE__ */ jsxs12(MessageBarBody, { children: [
|
|
1331
|
+
/* @__PURE__ */ jsx15(MessageBarTitle, { children: "Response withheld" }),
|
|
1332
|
+
"The provider substituted safe text for the generated answer."
|
|
1333
|
+
] }) });
|
|
1334
|
+
}
|
|
1335
|
+
if (!failure) return null;
|
|
1336
|
+
const title = {
|
|
1337
|
+
blocked: "Response withheld",
|
|
1338
|
+
refused: "The agent declined",
|
|
1339
|
+
error: "Something went wrong",
|
|
1340
|
+
notProvisioned: "Not configured",
|
|
1341
|
+
cancelled: "Stopped"
|
|
1342
|
+
};
|
|
1343
|
+
return /* @__PURE__ */ jsxs12(MessageBar, { intent: INTENT[runStatusSeverity(failure.status)], children: [
|
|
1344
|
+
/* @__PURE__ */ jsxs12(MessageBarBody, { children: [
|
|
1345
|
+
/* @__PURE__ */ jsx15(MessageBarTitle, { children: title[failure.status] ?? "Notice" }),
|
|
1346
|
+
failure.message
|
|
1347
|
+
] }),
|
|
1348
|
+
failure.recoverable && onRetry ? /* @__PURE__ */ jsx15(MessageBarActions, { children: /* @__PURE__ */ jsx15(Button6, { size: "small", onClick: onRetry, children: "Try again" }) }) : null
|
|
1349
|
+
] });
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/ops/AgentStatusBadge.tsx
|
|
1353
|
+
import { Badge, Tooltip as Tooltip3 } from "@fluentui/react-components";
|
|
1354
|
+
import {
|
|
1355
|
+
CheckmarkCircleRegular,
|
|
1356
|
+
ShieldErrorRegular,
|
|
1357
|
+
DismissCircleRegular,
|
|
1358
|
+
ErrorCircleRegular,
|
|
1359
|
+
PlugDisconnectedRegular,
|
|
1360
|
+
StopRegular as StopRegular2
|
|
1361
|
+
} from "@fluentui/react-icons";
|
|
1362
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1363
|
+
var PRESENTATION = {
|
|
1364
|
+
succeeded: { label: "Succeeded", color: "success", icon: /* @__PURE__ */ jsx16(CheckmarkCircleRegular, {}) },
|
|
1365
|
+
blocked: { label: "Blocked by content filter", color: "warning", icon: /* @__PURE__ */ jsx16(ShieldErrorRegular, {}) },
|
|
1366
|
+
refused: { label: "Declined", color: "warning", icon: /* @__PURE__ */ jsx16(DismissCircleRegular, {}) },
|
|
1367
|
+
error: { label: "Error", color: "danger", icon: /* @__PURE__ */ jsx16(ErrorCircleRegular, {}) },
|
|
1368
|
+
notProvisioned: { label: "Not configured", color: "informative", icon: /* @__PURE__ */ jsx16(PlugDisconnectedRegular, {}) },
|
|
1369
|
+
cancelled: { label: "Stopped", color: "subtle", icon: /* @__PURE__ */ jsx16(StopRegular2, {}) }
|
|
1370
|
+
};
|
|
1371
|
+
function runStatusLabel(status) {
|
|
1372
|
+
return PRESENTATION[status].label;
|
|
1373
|
+
}
|
|
1374
|
+
function AgentStatusBadge({ status, reason, size = "medium" }) {
|
|
1375
|
+
const { label, color, icon } = PRESENTATION[status];
|
|
1376
|
+
const badge = /* @__PURE__ */ jsx16(Badge, { appearance: "tint", color, icon, size, children: label });
|
|
1377
|
+
return reason ? /* @__PURE__ */ jsx16(Tooltip3, { content: reason, relationship: "description", withArrow: true, children: badge }) : badge;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// src/ops/AvailabilityBanner.tsx
|
|
1381
|
+
import { MessageBar as MessageBar2, MessageBarBody as MessageBarBody2, MessageBarTitle as MessageBarTitle2, MessageBarActions as MessageBarActions2, Button as Button7 } from "@fluentui/react-components";
|
|
1382
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1383
|
+
function AvailabilityBanner({
|
|
1384
|
+
availability,
|
|
1385
|
+
onRetry,
|
|
1386
|
+
actions
|
|
1387
|
+
}) {
|
|
1388
|
+
if (!availability || availability.available) return null;
|
|
1389
|
+
const notProvisioned = availability.status === "notProvisioned";
|
|
1390
|
+
return /* @__PURE__ */ jsxs13(MessageBar2, { intent: notProvisioned ? "info" : "warning", children: [
|
|
1391
|
+
/* @__PURE__ */ jsxs13(MessageBarBody2, { children: [
|
|
1392
|
+
/* @__PURE__ */ jsx17(MessageBarTitle2, { children: notProvisioned ? "No agent configured" : "The agent is unavailable" }),
|
|
1393
|
+
availability.reason ?? "No reason was reported."
|
|
1394
|
+
] }),
|
|
1395
|
+
onRetry || actions ? /* @__PURE__ */ jsxs13(MessageBarActions2, { children: [
|
|
1396
|
+
actions,
|
|
1397
|
+
onRetry ? /* @__PURE__ */ jsx17(Button7, { size: "small", onClick: onRetry, children: "Check again" }) : null
|
|
1398
|
+
] }) : null
|
|
1399
|
+
] });
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/ops/UsageMeter.tsx
|
|
1403
|
+
import { makeStyles as makeStyles14, tokens as tokens13 } from "@fluentui/react-components";
|
|
1404
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1405
|
+
var useStyles13 = makeStyles14({
|
|
1406
|
+
root: {
|
|
1407
|
+
display: "flex",
|
|
1408
|
+
flexWrap: "wrap",
|
|
1409
|
+
gap: tokens13.spacingHorizontalM,
|
|
1410
|
+
fontSize: tokens13.fontSizeBase100,
|
|
1411
|
+
color: tokens13.colorNeutralForeground3
|
|
1412
|
+
},
|
|
1413
|
+
item: { display: "flex", gap: tokens13.spacingHorizontalXXS },
|
|
1414
|
+
value: { fontFamily: tokens13.fontFamilyMonospace },
|
|
1415
|
+
unknown: { fontStyle: "italic" }
|
|
1416
|
+
});
|
|
1417
|
+
function UsageMeter({
|
|
1418
|
+
usage,
|
|
1419
|
+
show = ["tokens", "cost", "duration"]
|
|
1420
|
+
}) {
|
|
1421
|
+
const styles = useStyles13();
|
|
1422
|
+
if (!usage) return null;
|
|
1423
|
+
const items = [];
|
|
1424
|
+
if (show.includes("tokens")) {
|
|
1425
|
+
const known = usage.inputTokens !== void 0 || usage.outputTokens !== void 0;
|
|
1426
|
+
items.push({
|
|
1427
|
+
key: "tokens",
|
|
1428
|
+
label: "Tokens",
|
|
1429
|
+
value: known ? `${usage.inputTokens ?? 0} in / ${usage.outputTokens ?? 0} out${usage.cachedTokens ? ` / ${usage.cachedTokens} cached` : ""}` : "not reported",
|
|
1430
|
+
known
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
if (show.includes("cost")) {
|
|
1434
|
+
const known = usage.costUsd !== void 0;
|
|
1435
|
+
items.push({
|
|
1436
|
+
key: "cost",
|
|
1437
|
+
label: "Cost",
|
|
1438
|
+
value: known ? `$${usage.costUsd.toFixed(6)}` : "not reported",
|
|
1439
|
+
known
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
if (show.includes("duration")) {
|
|
1443
|
+
const known = usage.durationMs !== void 0;
|
|
1444
|
+
items.push({
|
|
1445
|
+
key: "duration",
|
|
1446
|
+
label: "Duration",
|
|
1447
|
+
value: known ? `${usage.durationMs} ms` : "not reported",
|
|
1448
|
+
known
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
if (show.includes("model") && usage.model) {
|
|
1452
|
+
items.push({ key: "model", label: "Model", value: usage.model, known: true });
|
|
1453
|
+
}
|
|
1454
|
+
return /* @__PURE__ */ jsx18("div", { className: styles.root, children: items.map((item) => /* @__PURE__ */ jsxs14("span", { className: styles.item, children: [
|
|
1455
|
+
item.label,
|
|
1456
|
+
":",
|
|
1457
|
+
" ",
|
|
1458
|
+
/* @__PURE__ */ jsx18("span", { className: item.known ? styles.value : styles.unknown, children: item.value })
|
|
1459
|
+
] }, item.key)) });
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/ops/ThrottleNotice.tsx
|
|
1463
|
+
import * as React12 from "react";
|
|
1464
|
+
import { MessageBar as MessageBar3, MessageBarBody as MessageBarBody3, MessageBarTitle as MessageBarTitle3, MessageBarActions as MessageBarActions3, Button as Button8 } from "@fluentui/react-components";
|
|
1465
|
+
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1466
|
+
function ThrottleNotice({ failure, onRetry, now = Date.now }) {
|
|
1467
|
+
const retryAfterMs = failure?.retryAfterMs;
|
|
1468
|
+
const eligible = Boolean(failure?.recoverable && retryAfterMs && retryAfterMs > 0);
|
|
1469
|
+
const [remaining, setRemaining] = React12.useState(retryAfterMs ?? 0);
|
|
1470
|
+
React12.useEffect(() => {
|
|
1471
|
+
if (!eligible) return;
|
|
1472
|
+
const deadline = now() + (retryAfterMs ?? 0);
|
|
1473
|
+
setRemaining(retryAfterMs ?? 0);
|
|
1474
|
+
const id = setInterval(() => {
|
|
1475
|
+
const left = deadline - now();
|
|
1476
|
+
setRemaining(left > 0 ? left : 0);
|
|
1477
|
+
if (left <= 0) clearInterval(id);
|
|
1478
|
+
}, 250);
|
|
1479
|
+
return () => clearInterval(id);
|
|
1480
|
+
}, [eligible, retryAfterMs, now]);
|
|
1481
|
+
if (!eligible || !failure) return null;
|
|
1482
|
+
const seconds = Math.ceil(remaining / 1e3);
|
|
1483
|
+
const ready = remaining <= 0;
|
|
1484
|
+
return /* @__PURE__ */ jsxs15(MessageBar3, { intent: "warning", children: [
|
|
1485
|
+
/* @__PURE__ */ jsxs15(MessageBarBody3, { children: [
|
|
1486
|
+
/* @__PURE__ */ jsx19(MessageBarTitle3, { children: "Rate limited" }),
|
|
1487
|
+
ready ? "You can try again now." : `${failure.message} Try again in ${seconds}s.`
|
|
1488
|
+
] }),
|
|
1489
|
+
onRetry ? /* @__PURE__ */ jsx19(MessageBarActions3, { children: /* @__PURE__ */ jsx19(Button8, { size: "small", onClick: onRetry, disabled: !ready, children: "Try again" }) }) : null
|
|
1490
|
+
] });
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/ops/ModelBadge.tsx
|
|
1494
|
+
import { Badge as Badge2 } from "@fluentui/react-components";
|
|
1495
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
1496
|
+
function ModelBadge({ provider, model, version }) {
|
|
1497
|
+
const parts = [provider, model, version ? `v${version}` : void 0].filter(Boolean);
|
|
1498
|
+
if (parts.length === 0) return null;
|
|
1499
|
+
return /* @__PURE__ */ jsx20(Badge2, { appearance: "outline", color: "informative", children: parts.join(" \xB7 ") });
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// src/ops/AgentPicker.tsx
|
|
1503
|
+
import { Dropdown, Option, Field } from "@fluentui/react-components";
|
|
1504
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
1505
|
+
function AgentPicker({
|
|
1506
|
+
agents,
|
|
1507
|
+
selectedId,
|
|
1508
|
+
onSelect,
|
|
1509
|
+
label = "Agent",
|
|
1510
|
+
disabled
|
|
1511
|
+
}) {
|
|
1512
|
+
const selected = agents.find((a) => a.id === selectedId);
|
|
1513
|
+
return /* @__PURE__ */ jsx21(Field, { label, children: /* @__PURE__ */ jsx21(
|
|
1514
|
+
Dropdown,
|
|
1515
|
+
{
|
|
1516
|
+
disabled,
|
|
1517
|
+
value: selected ? selected.name : "",
|
|
1518
|
+
selectedOptions: selectedId ? [selectedId] : [],
|
|
1519
|
+
onOptionSelect: (_, data) => {
|
|
1520
|
+
if (data.optionValue) onSelect(data.optionValue);
|
|
1521
|
+
},
|
|
1522
|
+
placeholder: "Select an agent",
|
|
1523
|
+
children: agents.map((agent) => /* @__PURE__ */ jsx21(Option, { value: agent.id, text: agent.name, children: agent.model ? `${agent.name} \u2014 ${agent.model}` : agent.name }, agent.id))
|
|
1524
|
+
}
|
|
1525
|
+
) });
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// src/ops/RunTimeline.tsx
|
|
1529
|
+
import * as React13 from "react";
|
|
1530
|
+
import { makeStyles as makeStyles15, tokens as tokens14, Text as Text3 } from "@fluentui/react-components";
|
|
1531
|
+
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1532
|
+
var useStyles14 = makeStyles15({
|
|
1533
|
+
list: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: tokens14.spacingVerticalXS },
|
|
1534
|
+
step: { display: "flex", gap: tokens14.spacingHorizontalS, alignItems: "baseline" },
|
|
1535
|
+
index: { fontFamily: tokens14.fontFamilyMonospace, fontSize: tokens14.fontSizeBase100, color: tokens14.colorNeutralForeground3, minWidth: "18px" },
|
|
1536
|
+
name: { fontFamily: tokens14.fontFamilyMonospace, fontSize: tokens14.fontSizeBase200 },
|
|
1537
|
+
status: { fontSize: tokens14.fontSizeBase100, color: tokens14.colorNeutralForeground3 },
|
|
1538
|
+
section: { marginTop: tokens14.spacingVerticalS }
|
|
1539
|
+
});
|
|
1540
|
+
var TOOL_STATUS = {
|
|
1541
|
+
proposed: "proposed",
|
|
1542
|
+
running: "running",
|
|
1543
|
+
succeeded: "succeeded",
|
|
1544
|
+
failed: "failed",
|
|
1545
|
+
denied: "denied"
|
|
1546
|
+
};
|
|
1547
|
+
function RunTimeline({ message, defaultOpen = false }) {
|
|
1548
|
+
const styles = useStyles14();
|
|
1549
|
+
const id = React13.useId();
|
|
1550
|
+
const calls = message.toolCalls ?? [];
|
|
1551
|
+
const sources = message.sources ?? [];
|
|
1552
|
+
const hasAnything = calls.length > 0 || sources.length > 0 || message.usage || message.failure;
|
|
1553
|
+
if (!hasAnything) return null;
|
|
1554
|
+
const status = message.failure?.status ?? (message.guarded ? "blocked" : "succeeded");
|
|
1555
|
+
return /* @__PURE__ */ jsxs16(
|
|
1556
|
+
Disclosure,
|
|
1557
|
+
{
|
|
1558
|
+
id,
|
|
1559
|
+
defaultOpen,
|
|
1560
|
+
summary: `Trace \u2014 ${calls.length} tool call${calls.length === 1 ? "" : "s"}`,
|
|
1561
|
+
children: [
|
|
1562
|
+
/* @__PURE__ */ jsx22("div", { className: styles.step, children: /* @__PURE__ */ jsx22(AgentStatusBadge, { status, reason: message.failure?.message, size: "small" }) }),
|
|
1563
|
+
calls.length > 0 ? /* @__PURE__ */ jsx22("ol", { className: styles.list, "aria-label": "Tool calls", children: calls.map((call, index) => /* @__PURE__ */ jsxs16("li", { className: styles.step, children: [
|
|
1564
|
+
/* @__PURE__ */ jsxs16("span", { className: styles.index, children: [
|
|
1565
|
+
index + 1,
|
|
1566
|
+
"."
|
|
1567
|
+
] }),
|
|
1568
|
+
/* @__PURE__ */ jsx22("span", { className: styles.name, children: call.name }),
|
|
1569
|
+
/* @__PURE__ */ jsx22("span", { className: styles.status, children: TOOL_STATUS[call.status] })
|
|
1570
|
+
] }, call.id)) }) : null,
|
|
1571
|
+
sources.length > 0 ? /* @__PURE__ */ jsxs16(Text3, { as: "p", size: 200, className: styles.section, children: [
|
|
1572
|
+
sources.length,
|
|
1573
|
+
" source",
|
|
1574
|
+
sources.length === 1 ? "" : "s",
|
|
1575
|
+
" retrieved"
|
|
1576
|
+
] }) : null,
|
|
1577
|
+
/* @__PURE__ */ jsx22("div", { className: styles.section, children: /* @__PURE__ */ jsx22(UsageMeter, { usage: message.usage, show: ["tokens", "cost", "duration", "model"] }) })
|
|
1578
|
+
]
|
|
1579
|
+
}
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/ops/RunHistoryPanel.tsx
|
|
1584
|
+
import { makeStyles as makeStyles16, tokens as tokens15, Text as Text4 } from "@fluentui/react-components";
|
|
1585
|
+
import { Fragment as Fragment3, jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1586
|
+
var useStyles15 = makeStyles16({
|
|
1587
|
+
list: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: tokens15.spacingVerticalXS },
|
|
1588
|
+
row: {
|
|
1589
|
+
display: "flex",
|
|
1590
|
+
flexDirection: "column",
|
|
1591
|
+
gap: "2px",
|
|
1592
|
+
padding: tokens15.spacingVerticalXS,
|
|
1593
|
+
borderBottom: `1px solid ${tokens15.colorNeutralStroke2}`
|
|
1594
|
+
},
|
|
1595
|
+
head: { display: "flex", alignItems: "center", gap: tokens15.spacingHorizontalS, flexWrap: "wrap" },
|
|
1596
|
+
time: { fontSize: tokens15.fontSizeBase100, color: tokens15.colorNeutralForeground3 },
|
|
1597
|
+
prompt: { fontSize: tokens15.fontSizeBase200, color: tokens15.colorNeutralForeground2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
1598
|
+
id: { fontFamily: tokens15.fontFamilyMonospace, fontSize: tokens15.fontSizeBase100, color: tokens15.colorNeutralForeground3 },
|
|
1599
|
+
empty: { padding: tokens15.spacingVerticalM, color: tokens15.colorNeutralForeground3 }
|
|
1600
|
+
});
|
|
1601
|
+
function RunHistoryPanel({
|
|
1602
|
+
runs,
|
|
1603
|
+
grid,
|
|
1604
|
+
locale,
|
|
1605
|
+
emptyLabel = "No runs yet."
|
|
1606
|
+
}) {
|
|
1607
|
+
const styles = useStyles15();
|
|
1608
|
+
if (grid) return /* @__PURE__ */ jsx23(Fragment3, { children: grid });
|
|
1609
|
+
if (runs.length === 0) {
|
|
1610
|
+
return /* @__PURE__ */ jsx23(Text4, { className: styles.empty, size: 200, children: emptyLabel });
|
|
1611
|
+
}
|
|
1612
|
+
return /* @__PURE__ */ jsx23("ul", { className: styles.list, "aria-label": "Run history", children: runs.map((run) => /* @__PURE__ */ jsxs17("li", { className: styles.row, children: [
|
|
1613
|
+
/* @__PURE__ */ jsxs17("div", { className: styles.head, children: [
|
|
1614
|
+
/* @__PURE__ */ jsx23(AgentStatusBadge, { status: run.status, size: "small" }),
|
|
1615
|
+
/* @__PURE__ */ jsx23("span", { className: styles.time, children: new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "short" }).format(
|
|
1616
|
+
new Date(run.startedAt)
|
|
1617
|
+
) }),
|
|
1618
|
+
run.correlationId ? /* @__PURE__ */ jsx23("span", { className: styles.id, children: run.correlationId }) : null
|
|
1619
|
+
] }),
|
|
1620
|
+
run.prompt ? /* @__PURE__ */ jsx23("span", { className: styles.prompt, children: run.prompt }) : null,
|
|
1621
|
+
/* @__PURE__ */ jsx23(UsageMeter, { usage: run.usage })
|
|
1622
|
+
] }, run.id)) });
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// src/ops/AgentOpsPanel.tsx
|
|
1626
|
+
import { makeStyles as makeStyles17, tokens as tokens16, Text as Text5 } from "@fluentui/react-components";
|
|
1627
|
+
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1628
|
+
var useStyles16 = makeStyles17({
|
|
1629
|
+
root: { display: "flex", flexDirection: "column", gap: tokens16.spacingVerticalM, minWidth: 0 },
|
|
1630
|
+
head: { display: "flex", alignItems: "center", gap: tokens16.spacingHorizontalS, flexWrap: "wrap" },
|
|
1631
|
+
section: { display: "flex", flexDirection: "column", gap: tokens16.spacingVerticalXS },
|
|
1632
|
+
caps: { fontSize: tokens16.fontSizeBase100, color: tokens16.colorNeutralForeground3 }
|
|
1633
|
+
});
|
|
1634
|
+
function AgentOpsPanel(props) {
|
|
1635
|
+
const { availability, agents, selectedAgentId, onSelectAgent, runs = [], runsGrid, onRefresh, locale } = props;
|
|
1636
|
+
const styles = useStyles16();
|
|
1637
|
+
const selected = agents?.find((a) => a.id === selectedAgentId);
|
|
1638
|
+
const caps = availability?.capabilities;
|
|
1639
|
+
return /* @__PURE__ */ jsxs18("div", { className: styles.root, children: [
|
|
1640
|
+
/* @__PURE__ */ jsx24(AvailabilityBanner, { availability, onRetry: onRefresh }),
|
|
1641
|
+
agents && agents.length > 0 && onSelectAgent ? /* @__PURE__ */ jsxs18("div", { className: styles.section, children: [
|
|
1642
|
+
/* @__PURE__ */ jsx24(AgentPicker, { agents, selectedId: selectedAgentId, onSelect: onSelectAgent }),
|
|
1643
|
+
/* @__PURE__ */ jsx24("div", { className: styles.head, children: /* @__PURE__ */ jsx24(ModelBadge, { provider: selected?.name, model: selected?.model, version: selected?.version }) })
|
|
1644
|
+
] }) : null,
|
|
1645
|
+
caps ? /* @__PURE__ */ jsxs18("span", { className: styles.caps, children: [
|
|
1646
|
+
"Supports:",
|
|
1647
|
+
" ",
|
|
1648
|
+
Object.entries(caps).filter(([, on]) => on).map(([name]) => name).join(", ") || "nothing reported"
|
|
1649
|
+
] }) : null,
|
|
1650
|
+
/* @__PURE__ */ jsxs18("div", { className: styles.section, children: [
|
|
1651
|
+
/* @__PURE__ */ jsx24(Text5, { weight: "semibold", size: 300, children: "Recent runs" }),
|
|
1652
|
+
/* @__PURE__ */ jsx24(RunHistoryPanel, { runs, grid: runsGrid, locale })
|
|
1653
|
+
] })
|
|
1654
|
+
] });
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// src/shell/AgentAppShell.tsx
|
|
1658
|
+
import * as React14 from "react";
|
|
1659
|
+
import { makeStyles as makeStyles18, tokens as tokens17, Button as Button9 } from "@fluentui/react-components";
|
|
1660
|
+
import { NavigationRegular } from "@fluentui/react-icons";
|
|
1661
|
+
import { BREAKPOINTS, resolveBreakpoint, useContainerWidth } from "@dataverse-kit/surface-kit/v9";
|
|
1662
|
+
import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1663
|
+
var useStyles17 = makeStyles18({
|
|
1664
|
+
root: {
|
|
1665
|
+
display: "flex",
|
|
1666
|
+
height: "100%",
|
|
1667
|
+
minHeight: 0,
|
|
1668
|
+
backgroundColor: tokens17.colorNeutralBackground2,
|
|
1669
|
+
position: "relative",
|
|
1670
|
+
overflow: "hidden"
|
|
1671
|
+
},
|
|
1672
|
+
sidebar: {
|
|
1673
|
+
display: "flex",
|
|
1674
|
+
flexDirection: "column",
|
|
1675
|
+
minHeight: 0,
|
|
1676
|
+
backgroundColor: tokens17.colorNeutralBackground1,
|
|
1677
|
+
borderRight: `1px solid ${tokens17.colorNeutralStroke2}`
|
|
1678
|
+
},
|
|
1679
|
+
// Below `md` the list overlays the conversation instead of shrinking it to nothing.
|
|
1680
|
+
sidebarOverlay: {
|
|
1681
|
+
position: "absolute",
|
|
1682
|
+
top: 0,
|
|
1683
|
+
bottom: 0,
|
|
1684
|
+
left: 0,
|
|
1685
|
+
zIndex: 2,
|
|
1686
|
+
boxShadow: tokens17.shadow16
|
|
1687
|
+
},
|
|
1688
|
+
scrim: { position: "absolute", inset: "0", zIndex: 1, backgroundColor: tokens17.colorBackgroundOverlay },
|
|
1689
|
+
main: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0, minHeight: 0 },
|
|
1690
|
+
body: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" },
|
|
1691
|
+
toggle: { position: "absolute", top: tokens17.spacingVerticalS, left: tokens17.spacingHorizontalS, zIndex: 3 }
|
|
1692
|
+
});
|
|
1693
|
+
function AgentAppShell(props) {
|
|
1694
|
+
const {
|
|
1695
|
+
rail,
|
|
1696
|
+
sidebar,
|
|
1697
|
+
header,
|
|
1698
|
+
children,
|
|
1699
|
+
rightPane,
|
|
1700
|
+
sidebarWidth = 280,
|
|
1701
|
+
sidebarOpen,
|
|
1702
|
+
onSidebarOpenChange,
|
|
1703
|
+
label = "Agent workspace"
|
|
1704
|
+
} = props;
|
|
1705
|
+
const styles = useStyles17();
|
|
1706
|
+
const [containerRef, measuredWidth] = useContainerWidth();
|
|
1707
|
+
const width = props.containerWidth ?? (measuredWidth > 0 ? measuredWidth : void 0);
|
|
1708
|
+
const effective = width ?? BREAKPOINTS.xl;
|
|
1709
|
+
const breakpoint = resolveBreakpoint(effective);
|
|
1710
|
+
const railVisible = effective >= BREAKPOINTS.sm;
|
|
1711
|
+
const sidebarInline = effective >= BREAKPOINTS.md;
|
|
1712
|
+
const rightPaneVisible = effective >= BREAKPOINTS.lg;
|
|
1713
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React14.useState(false);
|
|
1714
|
+
const overlayOpen = sidebarOpen ?? uncontrolledOpen;
|
|
1715
|
+
const setOverlayOpen = (open) => {
|
|
1716
|
+
onSidebarOpenChange?.(open);
|
|
1717
|
+
if (sidebarOpen === void 0) setUncontrolledOpen(open);
|
|
1718
|
+
};
|
|
1719
|
+
return /* @__PURE__ */ jsxs19("div", { ref: containerRef, className: styles.root, "data-breakpoint": breakpoint, "aria-label": label, children: [
|
|
1720
|
+
rail && railVisible ? rail : null,
|
|
1721
|
+
sidebar && sidebarInline ? /* @__PURE__ */ jsx25("div", { className: styles.sidebar, style: { width: sidebarWidth }, children: sidebar }) : null,
|
|
1722
|
+
sidebar && !sidebarInline && overlayOpen ? /* @__PURE__ */ jsxs19(Fragment4, { children: [
|
|
1723
|
+
/* @__PURE__ */ jsx25(
|
|
1724
|
+
"div",
|
|
1725
|
+
{
|
|
1726
|
+
className: styles.scrim,
|
|
1727
|
+
onClick: () => setOverlayOpen(false),
|
|
1728
|
+
"aria-hidden": "true"
|
|
1729
|
+
}
|
|
1730
|
+
),
|
|
1731
|
+
/* @__PURE__ */ jsx25("div", { className: `${styles.sidebar} ${styles.sidebarOverlay}`, style: { width: sidebarWidth }, children: sidebar })
|
|
1732
|
+
] }) : null,
|
|
1733
|
+
/* @__PURE__ */ jsxs19("div", { className: styles.main, children: [
|
|
1734
|
+
sidebar && !sidebarInline ? /* @__PURE__ */ jsx25(
|
|
1735
|
+
Button9,
|
|
1736
|
+
{
|
|
1737
|
+
className: styles.toggle,
|
|
1738
|
+
size: "small",
|
|
1739
|
+
appearance: "subtle",
|
|
1740
|
+
icon: /* @__PURE__ */ jsx25(NavigationRegular, {}),
|
|
1741
|
+
"aria-label": overlayOpen ? "Hide conversations" : "Show conversations",
|
|
1742
|
+
"aria-expanded": overlayOpen,
|
|
1743
|
+
onClick: () => setOverlayOpen(!overlayOpen)
|
|
1744
|
+
}
|
|
1745
|
+
) : null,
|
|
1746
|
+
header,
|
|
1747
|
+
/* @__PURE__ */ jsx25("div", { className: styles.body, children })
|
|
1748
|
+
] }),
|
|
1749
|
+
rightPaneVisible ? rightPane : null
|
|
1750
|
+
] });
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/shell/AppRail.tsx
|
|
1754
|
+
import {
|
|
1755
|
+
Button as Button10,
|
|
1756
|
+
Menu,
|
|
1757
|
+
MenuTrigger,
|
|
1758
|
+
MenuPopover,
|
|
1759
|
+
MenuList,
|
|
1760
|
+
MenuItem,
|
|
1761
|
+
Tooltip as Tooltip4,
|
|
1762
|
+
makeStyles as makeStyles19,
|
|
1763
|
+
mergeClasses as mergeClasses5,
|
|
1764
|
+
tokens as tokens18
|
|
1765
|
+
} from "@fluentui/react-components";
|
|
1766
|
+
import { MoreHorizontalRegular } from "@fluentui/react-icons";
|
|
1767
|
+
import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1768
|
+
var useStyles18 = makeStyles19({
|
|
1769
|
+
root: {
|
|
1770
|
+
display: "flex",
|
|
1771
|
+
flexDirection: "column",
|
|
1772
|
+
alignItems: "center",
|
|
1773
|
+
gap: tokens18.spacingVerticalXS,
|
|
1774
|
+
padding: tokens18.spacingVerticalS,
|
|
1775
|
+
backgroundColor: tokens18.colorNeutralBackground3,
|
|
1776
|
+
borderRight: `1px solid ${tokens18.colorNeutralStroke2}`,
|
|
1777
|
+
minHeight: 0
|
|
1778
|
+
},
|
|
1779
|
+
spacer: { flex: 1 },
|
|
1780
|
+
button: {
|
|
1781
|
+
position: "relative",
|
|
1782
|
+
":focus-visible": { outline: `2px solid ${tokens18.colorStrokeFocus2}`, outlineOffset: "1px" }
|
|
1783
|
+
},
|
|
1784
|
+
selected: { backgroundColor: tokens18.colorNeutralBackground1Selected },
|
|
1785
|
+
badge: {
|
|
1786
|
+
position: "absolute",
|
|
1787
|
+
top: "2px",
|
|
1788
|
+
right: "2px",
|
|
1789
|
+
minWidth: "16px",
|
|
1790
|
+
height: "16px",
|
|
1791
|
+
padding: "0 3px",
|
|
1792
|
+
borderRadius: tokens18.borderRadiusCircular,
|
|
1793
|
+
backgroundColor: tokens18.colorPaletteRedBackground3,
|
|
1794
|
+
color: tokens18.colorNeutralForegroundOnBrand,
|
|
1795
|
+
fontSize: "10px",
|
|
1796
|
+
lineHeight: "16px"
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
function AppRail(props) {
|
|
1800
|
+
const { items, selectedId, onSelect, maxVisible = 6, label = "Apps", footer } = props;
|
|
1801
|
+
const styles = useStyles18();
|
|
1802
|
+
const visible = items.slice(0, maxVisible);
|
|
1803
|
+
const overflow = items.slice(maxVisible);
|
|
1804
|
+
return /* @__PURE__ */ jsxs20("nav", { className: styles.root, "aria-label": label, children: [
|
|
1805
|
+
visible.map((item) => /* @__PURE__ */ jsx26(Tooltip4, { content: item.label, relationship: "label", positioning: "after", withArrow: true, children: /* @__PURE__ */ jsx26(
|
|
1806
|
+
Button10,
|
|
1807
|
+
{
|
|
1808
|
+
appearance: "subtle",
|
|
1809
|
+
icon: item.icon,
|
|
1810
|
+
className: mergeClasses5(styles.button, item.id === selectedId && styles.selected),
|
|
1811
|
+
"aria-pressed": item.id === selectedId,
|
|
1812
|
+
"aria-label": item.badgeCount ? `${item.label}, ${item.badgeCount} unread` : item.label,
|
|
1813
|
+
onClick: () => onSelect(item.id),
|
|
1814
|
+
children: item.badgeCount ? /* @__PURE__ */ jsx26("span", { className: styles.badge, "aria-hidden": "true", children: item.badgeCount }) : null
|
|
1815
|
+
}
|
|
1816
|
+
) }, item.id)),
|
|
1817
|
+
overflow.length > 0 ? /* @__PURE__ */ jsxs20(Menu, { children: [
|
|
1818
|
+
/* @__PURE__ */ jsx26(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx26(
|
|
1819
|
+
Button10,
|
|
1820
|
+
{
|
|
1821
|
+
appearance: "subtle",
|
|
1822
|
+
icon: /* @__PURE__ */ jsx26(MoreHorizontalRegular, {}),
|
|
1823
|
+
className: styles.button,
|
|
1824
|
+
"aria-label": `More apps, ${overflow.length} hidden`
|
|
1825
|
+
}
|
|
1826
|
+
) }),
|
|
1827
|
+
/* @__PURE__ */ jsx26(MenuPopover, { children: /* @__PURE__ */ jsx26(MenuList, { children: overflow.map((item) => /* @__PURE__ */ jsx26(MenuItem, { icon: item.icon, onClick: () => onSelect(item.id), children: item.label }, item.id)) }) })
|
|
1828
|
+
] }) : null,
|
|
1829
|
+
/* @__PURE__ */ jsx26("div", { className: styles.spacer }),
|
|
1830
|
+
footer
|
|
1831
|
+
] });
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/shell/ConversationList.tsx
|
|
1835
|
+
import * as React15 from "react";
|
|
1836
|
+
import { makeStyles as makeStyles21, tokens as tokens20, Text as Text6 } from "@fluentui/react-components";
|
|
1837
|
+
|
|
1838
|
+
// src/shell/ConversationListItem.tsx
|
|
1839
|
+
import { makeStyles as makeStyles20, mergeClasses as mergeClasses6, tokens as tokens19 } from "@fluentui/react-components";
|
|
1840
|
+
|
|
1841
|
+
// src/shell/AgentAvatar.tsx
|
|
1842
|
+
import { Avatar as Avatar2 } from "@fluentui/react-components";
|
|
1843
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
1844
|
+
var STATUS = {
|
|
1845
|
+
available: "available",
|
|
1846
|
+
busy: "busy",
|
|
1847
|
+
away: "away",
|
|
1848
|
+
offline: "offline",
|
|
1849
|
+
dnd: "do-not-disturb",
|
|
1850
|
+
unknown: "unknown"
|
|
1851
|
+
};
|
|
1852
|
+
function AgentAvatar({ participant, presence, size = 32, name }) {
|
|
1853
|
+
const displayName = name ?? participant?.displayName ?? "Unknown";
|
|
1854
|
+
return /* @__PURE__ */ jsx27(
|
|
1855
|
+
Avatar2,
|
|
1856
|
+
{
|
|
1857
|
+
name: displayName,
|
|
1858
|
+
initials: participant?.initials,
|
|
1859
|
+
image: participant?.imageUrl ? { src: participant.imageUrl } : void 0,
|
|
1860
|
+
size,
|
|
1861
|
+
badge: presence ? { status: STATUS[presence] ?? "unknown" } : void 0,
|
|
1862
|
+
"aria-hidden": void 0
|
|
1863
|
+
}
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// src/shell/ConversationListItem.tsx
|
|
1868
|
+
import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1869
|
+
var useStyles19 = makeStyles20({
|
|
1870
|
+
row: {
|
|
1871
|
+
display: "flex",
|
|
1872
|
+
gap: tokens19.spacingHorizontalS,
|
|
1873
|
+
alignItems: "center",
|
|
1874
|
+
padding: `${tokens19.spacingVerticalS} ${tokens19.spacingHorizontalS}`,
|
|
1875
|
+
borderRadius: tokens19.borderRadiusMedium,
|
|
1876
|
+
cursor: "pointer",
|
|
1877
|
+
":hover": { backgroundColor: tokens19.colorNeutralBackground1Hover }
|
|
1878
|
+
},
|
|
1879
|
+
// ★ The ACTIVE row (aria-activedescendant target) gets a visible focus indicator, because
|
|
1880
|
+
// real DOM focus stays on the listbox — without this the keyboard user sees nothing move.
|
|
1881
|
+
active: { outline: `2px solid ${tokens19.colorStrokeFocus2}`, outlineOffset: "-2px" },
|
|
1882
|
+
selected: { backgroundColor: tokens19.colorNeutralBackground1Selected },
|
|
1883
|
+
body: { flex: 1, minWidth: 0 },
|
|
1884
|
+
title: {
|
|
1885
|
+
display: "block",
|
|
1886
|
+
fontSize: tokens19.fontSizeBase300,
|
|
1887
|
+
fontWeight: tokens19.fontWeightSemibold,
|
|
1888
|
+
overflow: "hidden",
|
|
1889
|
+
textOverflow: "ellipsis",
|
|
1890
|
+
whiteSpace: "nowrap"
|
|
1891
|
+
},
|
|
1892
|
+
muted: { fontWeight: tokens19.fontWeightRegular, color: tokens19.colorNeutralForeground3 },
|
|
1893
|
+
preview: {
|
|
1894
|
+
display: "block",
|
|
1895
|
+
fontSize: tokens19.fontSizeBase200,
|
|
1896
|
+
color: tokens19.colorNeutralForeground3,
|
|
1897
|
+
overflow: "hidden",
|
|
1898
|
+
textOverflow: "ellipsis",
|
|
1899
|
+
whiteSpace: "nowrap"
|
|
1900
|
+
},
|
|
1901
|
+
meta: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "2px" },
|
|
1902
|
+
time: { fontSize: tokens19.fontSizeBase100, color: tokens19.colorNeutralForeground3 },
|
|
1903
|
+
unread: {
|
|
1904
|
+
minWidth: "18px",
|
|
1905
|
+
height: "18px",
|
|
1906
|
+
padding: `0 ${tokens19.spacingHorizontalXXS}`,
|
|
1907
|
+
borderRadius: tokens19.borderRadiusCircular,
|
|
1908
|
+
backgroundColor: tokens19.colorBrandBackground,
|
|
1909
|
+
color: tokens19.colorNeutralForegroundOnBrand,
|
|
1910
|
+
fontSize: tokens19.fontSizeBase100,
|
|
1911
|
+
lineHeight: "18px",
|
|
1912
|
+
textAlign: "center"
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
function conversationAccessibleName(summary, locale) {
|
|
1916
|
+
const parts = [summary.title];
|
|
1917
|
+
if (summary.presence && summary.presence !== "unknown") parts.push(summary.presence);
|
|
1918
|
+
if (!summary.muted && summary.unreadCount) {
|
|
1919
|
+
parts.push(`${summary.unreadCount} unread`);
|
|
1920
|
+
}
|
|
1921
|
+
if (summary.muted) parts.push("muted");
|
|
1922
|
+
if (summary.timestamp) {
|
|
1923
|
+
parts.push(new Intl.DateTimeFormat(locale, { timeStyle: "short" }).format(new Date(summary.timestamp)));
|
|
1924
|
+
}
|
|
1925
|
+
return parts.join(", ");
|
|
1926
|
+
}
|
|
1927
|
+
function ConversationListItem(props) {
|
|
1928
|
+
const { summary, selected, active, id, locale, onSelect } = props;
|
|
1929
|
+
const styles = useStyles19();
|
|
1930
|
+
const time = summary.timestamp ? new Intl.DateTimeFormat(locale, { timeStyle: "short" }).format(new Date(summary.timestamp)) : void 0;
|
|
1931
|
+
return /* @__PURE__ */ jsxs21(
|
|
1932
|
+
"div",
|
|
1933
|
+
{
|
|
1934
|
+
id,
|
|
1935
|
+
role: "option",
|
|
1936
|
+
"aria-selected": selected,
|
|
1937
|
+
"aria-label": conversationAccessibleName(summary, locale),
|
|
1938
|
+
className: mergeClasses6(styles.row, selected && styles.selected, active && styles.active),
|
|
1939
|
+
onClick: () => onSelect(summary.id),
|
|
1940
|
+
"data-conversation-option": true,
|
|
1941
|
+
children: [
|
|
1942
|
+
/* @__PURE__ */ jsx28(AgentAvatar, { participant: summary.participant, presence: summary.presence, size: 32 }),
|
|
1943
|
+
/* @__PURE__ */ jsxs21("div", { className: styles.body, "aria-hidden": "true", children: [
|
|
1944
|
+
/* @__PURE__ */ jsx28("span", { className: mergeClasses6(styles.title, summary.muted && styles.muted), children: summary.title }),
|
|
1945
|
+
summary.preview ? /* @__PURE__ */ jsx28("span", { className: styles.preview, children: summary.preview }) : null
|
|
1946
|
+
] }),
|
|
1947
|
+
/* @__PURE__ */ jsxs21("div", { className: styles.meta, "aria-hidden": "true", children: [
|
|
1948
|
+
time ? /* @__PURE__ */ jsx28("span", { className: styles.time, children: time }) : null,
|
|
1949
|
+
!summary.muted && summary.unreadCount ? /* @__PURE__ */ jsx28("span", { className: styles.unread, children: summary.unreadCount }) : null
|
|
1950
|
+
] })
|
|
1951
|
+
]
|
|
1952
|
+
}
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
// src/shell/ConversationList.tsx
|
|
1957
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
1958
|
+
var useStyles20 = makeStyles21({
|
|
1959
|
+
root: {
|
|
1960
|
+
display: "flex",
|
|
1961
|
+
flexDirection: "column",
|
|
1962
|
+
gap: "2px",
|
|
1963
|
+
padding: tokens20.spacingHorizontalXS,
|
|
1964
|
+
overflowY: "auto",
|
|
1965
|
+
minHeight: 0,
|
|
1966
|
+
":focus-visible": { outline: `2px solid ${tokens20.colorStrokeFocus2}`, outlineOffset: "-2px" }
|
|
1967
|
+
},
|
|
1968
|
+
empty: { padding: tokens20.spacingVerticalM, color: tokens20.colorNeutralForeground3 }
|
|
1969
|
+
});
|
|
1970
|
+
function ConversationList(props) {
|
|
1971
|
+
const { items, selectedId, onSelect, label = "Conversations", locale, empty } = props;
|
|
1972
|
+
const styles = useStyles20();
|
|
1973
|
+
const listRef = React15.useRef(null);
|
|
1974
|
+
const idBase = React15.useId();
|
|
1975
|
+
const selectedIndex = Math.max(0, items.findIndex((i) => i.id === selectedId));
|
|
1976
|
+
const [active, setActive] = React15.useState(selectedIndex);
|
|
1977
|
+
React15.useEffect(() => {
|
|
1978
|
+
setActive((current) => {
|
|
1979
|
+
if (items.length === 0) return 0;
|
|
1980
|
+
if (selectedIndex >= 0 && items[selectedIndex]) return selectedIndex;
|
|
1981
|
+
return Math.min(current, items.length - 1);
|
|
1982
|
+
});
|
|
1983
|
+
}, [selectedIndex, items]);
|
|
1984
|
+
const optionId = (index) => `${idBase}-opt-${index}`;
|
|
1985
|
+
React15.useEffect(() => {
|
|
1986
|
+
const el = listRef.current?.ownerDocument.getElementById(optionId(active));
|
|
1987
|
+
el?.scrollIntoView?.({ block: "nearest" });
|
|
1988
|
+
}, [active, idBase]);
|
|
1989
|
+
const onKeyDown = (event) => {
|
|
1990
|
+
const last = items.length - 1;
|
|
1991
|
+
if (last < 0) return;
|
|
1992
|
+
let next = null;
|
|
1993
|
+
if (event.key === "ArrowDown") next = Math.min(last, active + 1);
|
|
1994
|
+
else if (event.key === "ArrowUp") next = Math.max(0, active - 1);
|
|
1995
|
+
else if (event.key === "Home") next = 0;
|
|
1996
|
+
else if (event.key === "End") next = last;
|
|
1997
|
+
else if (event.key === "Enter" || event.key === " ") {
|
|
1998
|
+
event.preventDefault();
|
|
1999
|
+
const picked = items[active];
|
|
2000
|
+
if (picked) onSelect(picked.id);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (next === null) return;
|
|
2004
|
+
event.preventDefault();
|
|
2005
|
+
setActive(next);
|
|
2006
|
+
};
|
|
2007
|
+
if (items.length === 0) {
|
|
2008
|
+
return /* @__PURE__ */ jsx29("div", { className: styles.root, children: empty ?? /* @__PURE__ */ jsx29(Text6, { className: styles.empty, size: 200, children: "No conversations yet." }) });
|
|
2009
|
+
}
|
|
2010
|
+
return /* @__PURE__ */ jsx29(
|
|
2011
|
+
"div",
|
|
2012
|
+
{
|
|
2013
|
+
ref: listRef,
|
|
2014
|
+
className: styles.root,
|
|
2015
|
+
role: "listbox",
|
|
2016
|
+
"aria-label": label,
|
|
2017
|
+
tabIndex: 0,
|
|
2018
|
+
"aria-activedescendant": optionId(active),
|
|
2019
|
+
onKeyDown,
|
|
2020
|
+
children: items.map((summary, index) => /* @__PURE__ */ jsx29(
|
|
2021
|
+
ConversationListItem,
|
|
2022
|
+
{
|
|
2023
|
+
id: optionId(index),
|
|
2024
|
+
summary,
|
|
2025
|
+
selected: summary.id === selectedId,
|
|
2026
|
+
active: index === active,
|
|
2027
|
+
locale,
|
|
2028
|
+
onSelect
|
|
2029
|
+
},
|
|
2030
|
+
summary.id
|
|
2031
|
+
))
|
|
2032
|
+
}
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
// src/shell/ConversationHeader.tsx
|
|
2037
|
+
import {
|
|
2038
|
+
Button as Button11,
|
|
2039
|
+
Menu as Menu2,
|
|
2040
|
+
MenuTrigger as MenuTrigger2,
|
|
2041
|
+
MenuPopover as MenuPopover2,
|
|
2042
|
+
MenuList as MenuList2,
|
|
2043
|
+
MenuItem as MenuItem2,
|
|
2044
|
+
Tab,
|
|
2045
|
+
TabList,
|
|
2046
|
+
Text as Text7,
|
|
2047
|
+
makeStyles as makeStyles22,
|
|
2048
|
+
tokens as tokens21
|
|
2049
|
+
} from "@fluentui/react-components";
|
|
2050
|
+
import { MoreHorizontalRegular as MoreHorizontalRegular2 } from "@fluentui/react-icons";
|
|
2051
|
+
import { jsx as jsx30, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2052
|
+
var useStyles21 = makeStyles22({
|
|
2053
|
+
root: {
|
|
2054
|
+
display: "flex",
|
|
2055
|
+
flexDirection: "column",
|
|
2056
|
+
gap: tokens21.spacingVerticalXS,
|
|
2057
|
+
padding: `${tokens21.spacingVerticalS} ${tokens21.spacingHorizontalM} 0`,
|
|
2058
|
+
borderBottom: `1px solid ${tokens21.colorNeutralStroke2}`,
|
|
2059
|
+
backgroundColor: tokens21.colorNeutralBackground1
|
|
2060
|
+
},
|
|
2061
|
+
top: { display: "flex", alignItems: "center", gap: tokens21.spacingHorizontalS },
|
|
2062
|
+
identity: { display: "flex", alignItems: "center", gap: tokens21.spacingHorizontalS, minWidth: 0 },
|
|
2063
|
+
titles: { display: "flex", flexDirection: "column", minWidth: 0 },
|
|
2064
|
+
subtitle: { color: tokens21.colorNeutralForeground3, fontSize: tokens21.fontSizeBase200 },
|
|
2065
|
+
spacer: { flex: 1 },
|
|
2066
|
+
tabRow: { display: "flex", alignItems: "flex-end", gap: tokens21.spacingHorizontalS }
|
|
2067
|
+
});
|
|
2068
|
+
function ConversationHeader(props) {
|
|
2069
|
+
const {
|
|
2070
|
+
title,
|
|
2071
|
+
subtitle,
|
|
2072
|
+
participant,
|
|
2073
|
+
presence,
|
|
2074
|
+
tabs,
|
|
2075
|
+
selectedTabId,
|
|
2076
|
+
onTabSelect,
|
|
2077
|
+
actions,
|
|
2078
|
+
trailing
|
|
2079
|
+
} = props;
|
|
2080
|
+
const styles = useStyles21();
|
|
2081
|
+
const inline = (actions ?? []).slice(0, 2);
|
|
2082
|
+
const overflow = (actions ?? []).slice(2);
|
|
2083
|
+
return /* @__PURE__ */ jsxs22("header", { className: styles.root, children: [
|
|
2084
|
+
/* @__PURE__ */ jsxs22("div", { className: styles.top, children: [
|
|
2085
|
+
/* @__PURE__ */ jsxs22("div", { className: styles.identity, children: [
|
|
2086
|
+
/* @__PURE__ */ jsx30(AgentAvatar, { participant, presence, size: 32, name: title }),
|
|
2087
|
+
/* @__PURE__ */ jsxs22("div", { className: styles.titles, children: [
|
|
2088
|
+
/* @__PURE__ */ jsx30(Text7, { weight: "semibold", truncate: true, wrap: false, children: title }),
|
|
2089
|
+
subtitle ? /* @__PURE__ */ jsx30("span", { className: styles.subtitle, children: subtitle }) : null
|
|
2090
|
+
] })
|
|
2091
|
+
] }),
|
|
2092
|
+
/* @__PURE__ */ jsx30("div", { className: styles.spacer }),
|
|
2093
|
+
trailing,
|
|
2094
|
+
inline.map((action) => /* @__PURE__ */ jsx30(
|
|
2095
|
+
Button11,
|
|
2096
|
+
{
|
|
2097
|
+
appearance: "subtle",
|
|
2098
|
+
size: "small",
|
|
2099
|
+
icon: action.icon,
|
|
2100
|
+
"aria-label": action.label,
|
|
2101
|
+
onClick: action.onClick
|
|
2102
|
+
},
|
|
2103
|
+
action.key
|
|
2104
|
+
)),
|
|
2105
|
+
overflow.length > 0 ? /* @__PURE__ */ jsxs22(Menu2, { children: [
|
|
2106
|
+
/* @__PURE__ */ jsx30(MenuTrigger2, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx30(
|
|
2107
|
+
Button11,
|
|
2108
|
+
{
|
|
2109
|
+
appearance: "subtle",
|
|
2110
|
+
size: "small",
|
|
2111
|
+
icon: /* @__PURE__ */ jsx30(MoreHorizontalRegular2, {}),
|
|
2112
|
+
"aria-label": `More actions, ${overflow.length} available`
|
|
2113
|
+
}
|
|
2114
|
+
) }),
|
|
2115
|
+
/* @__PURE__ */ jsx30(MenuPopover2, { children: /* @__PURE__ */ jsx30(MenuList2, { children: overflow.map((action) => /* @__PURE__ */ jsx30(MenuItem2, { icon: action.icon, onClick: action.onClick, children: action.label }, action.key)) }) })
|
|
2116
|
+
] }) : null
|
|
2117
|
+
] }),
|
|
2118
|
+
tabs && tabs.length > 0 ? /* @__PURE__ */ jsx30("div", { className: styles.tabRow, children: /* @__PURE__ */ jsx30(
|
|
2119
|
+
TabList,
|
|
2120
|
+
{
|
|
2121
|
+
selectedValue: selectedTabId,
|
|
2122
|
+
onTabSelect: (_, data) => onTabSelect?.(data.value),
|
|
2123
|
+
size: "small",
|
|
2124
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsx30(Tab, { value: tab.id, children: tab.label }, tab.id))
|
|
2125
|
+
}
|
|
2126
|
+
) }) : null
|
|
2127
|
+
] });
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// src/shell/RightPane.tsx
|
|
2131
|
+
import * as React16 from "react";
|
|
2132
|
+
import { Button as Button12, makeStyles as makeStyles23, tokens as tokens22, Text as Text8 } from "@fluentui/react-components";
|
|
2133
|
+
import { DismissRegular } from "@fluentui/react-icons";
|
|
2134
|
+
import { jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2135
|
+
var useStyles22 = makeStyles23({
|
|
2136
|
+
root: {
|
|
2137
|
+
display: "flex",
|
|
2138
|
+
flexDirection: "column",
|
|
2139
|
+
borderLeft: `1px solid ${tokens22.colorNeutralStroke2}`,
|
|
2140
|
+
backgroundColor: tokens22.colorNeutralBackground1,
|
|
2141
|
+
minHeight: 0
|
|
2142
|
+
},
|
|
2143
|
+
head: {
|
|
2144
|
+
display: "flex",
|
|
2145
|
+
alignItems: "center",
|
|
2146
|
+
justifyContent: "space-between",
|
|
2147
|
+
gap: tokens22.spacingHorizontalS,
|
|
2148
|
+
padding: tokens22.spacingVerticalS,
|
|
2149
|
+
borderBottom: `1px solid ${tokens22.colorNeutralStroke2}`
|
|
2150
|
+
},
|
|
2151
|
+
body: { flex: 1, overflowY: "auto", padding: tokens22.spacingVerticalM }
|
|
2152
|
+
});
|
|
2153
|
+
function RightPane({ open, title, onClose, children, width = 320 }) {
|
|
2154
|
+
const styles = useStyles22();
|
|
2155
|
+
const labelId = React16.useId();
|
|
2156
|
+
if (!open) return null;
|
|
2157
|
+
return /* @__PURE__ */ jsxs23("aside", { className: styles.root, style: { width }, "aria-labelledby": labelId, children: [
|
|
2158
|
+
/* @__PURE__ */ jsxs23("div", { className: styles.head, children: [
|
|
2159
|
+
/* @__PURE__ */ jsx31(Text8, { id: labelId, weight: "semibold", children: title }),
|
|
2160
|
+
/* @__PURE__ */ jsx31(
|
|
2161
|
+
Button12,
|
|
2162
|
+
{
|
|
2163
|
+
appearance: "subtle",
|
|
2164
|
+
size: "small",
|
|
2165
|
+
icon: /* @__PURE__ */ jsx31(DismissRegular, {}),
|
|
2166
|
+
onClick: onClose,
|
|
2167
|
+
"aria-label": `Close ${title}`
|
|
2168
|
+
}
|
|
2169
|
+
)
|
|
2170
|
+
] }),
|
|
2171
|
+
/* @__PURE__ */ jsx31("div", { className: styles.body, children })
|
|
2172
|
+
] });
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// src/shell/GlobalSearchBar.tsx
|
|
2176
|
+
import { SearchBox, makeStyles as makeStyles24 } from "@fluentui/react-components";
|
|
2177
|
+
import { jsx as jsx32 } from "react/jsx-runtime";
|
|
2178
|
+
var useStyles23 = makeStyles24({
|
|
2179
|
+
root: { width: "100%", maxWidth: "640px" }
|
|
2180
|
+
});
|
|
2181
|
+
function GlobalSearchBar({
|
|
2182
|
+
value,
|
|
2183
|
+
onChange,
|
|
2184
|
+
onSubmit,
|
|
2185
|
+
placeholder = "Search conversations"
|
|
2186
|
+
}) {
|
|
2187
|
+
const styles = useStyles23();
|
|
2188
|
+
return /* @__PURE__ */ jsx32(
|
|
2189
|
+
SearchBox,
|
|
2190
|
+
{
|
|
2191
|
+
className: styles.root,
|
|
2192
|
+
value,
|
|
2193
|
+
placeholder,
|
|
2194
|
+
"aria-label": placeholder,
|
|
2195
|
+
onChange: (_, data) => onChange(data.value),
|
|
2196
|
+
onKeyDown: (e) => {
|
|
2197
|
+
if (e.key === "Enter" && onSubmit) onSubmit(value);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
// src/grounding/CitationChip.tsx
|
|
2204
|
+
import { Tooltip as Tooltip5, makeStyles as makeStyles25, tokens as tokens23 } from "@fluentui/react-components";
|
|
2205
|
+
import { jsx as jsx33 } from "react/jsx-runtime";
|
|
2206
|
+
var useStyles24 = makeStyles25({
|
|
2207
|
+
chip: {
|
|
2208
|
+
display: "inline-flex",
|
|
2209
|
+
alignItems: "center",
|
|
2210
|
+
justifyContent: "center",
|
|
2211
|
+
minWidth: "18px",
|
|
2212
|
+
height: "18px",
|
|
2213
|
+
padding: `0 ${tokens23.spacingHorizontalXXS}`,
|
|
2214
|
+
marginLeft: "2px",
|
|
2215
|
+
verticalAlign: "super",
|
|
2216
|
+
fontSize: tokens23.fontSizeBase100,
|
|
2217
|
+
lineHeight: "18px",
|
|
2218
|
+
borderRadius: tokens23.borderRadiusSmall,
|
|
2219
|
+
border: `1px solid ${tokens23.colorNeutralStroke2}`,
|
|
2220
|
+
backgroundColor: tokens23.colorNeutralBackground3,
|
|
2221
|
+
color: tokens23.colorNeutralForeground2,
|
|
2222
|
+
cursor: "pointer",
|
|
2223
|
+
":hover": { backgroundColor: tokens23.colorNeutralBackground3Hover },
|
|
2224
|
+
":focus-visible": { outline: `2px solid ${tokens23.colorStrokeFocus2}`, outlineOffset: "1px" }
|
|
2225
|
+
},
|
|
2226
|
+
inert: { cursor: "default" }
|
|
2227
|
+
});
|
|
2228
|
+
function CitationChip({ marker, source, onOpen }) {
|
|
2229
|
+
const styles = useStyles24();
|
|
2230
|
+
const label = source ? `Source ${marker}: ${source.title}` : `Source ${marker}`;
|
|
2231
|
+
if (!source || !onOpen) {
|
|
2232
|
+
return /* @__PURE__ */ jsx33("span", { className: `${styles.chip} ${styles.inert}`, "aria-label": label, children: marker });
|
|
2233
|
+
}
|
|
2234
|
+
return /* @__PURE__ */ jsx33(Tooltip5, { content: source.title, relationship: "description", withArrow: true, children: /* @__PURE__ */ jsx33("button", { type: "button", className: styles.chip, "aria-label": label, onClick: () => onOpen(source), children: marker }) });
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/grounding/CitationList.tsx
|
|
2238
|
+
import * as React17 from "react";
|
|
2239
|
+
import { makeStyles as makeStyles27, tokens as tokens25 } from "@fluentui/react-components";
|
|
2240
|
+
|
|
2241
|
+
// src/grounding/SourceCard.tsx
|
|
2242
|
+
import { Button as Button13, Text as Text9, makeStyles as makeStyles26, tokens as tokens24 } from "@fluentui/react-components";
|
|
2243
|
+
import {
|
|
2244
|
+
DocumentRegular,
|
|
2245
|
+
DatabaseRegular,
|
|
2246
|
+
GlobeRegular,
|
|
2247
|
+
BookRegular,
|
|
2248
|
+
DocumentBulletListRegular
|
|
2249
|
+
} from "@fluentui/react-icons";
|
|
2250
|
+
import { jsx as jsx34, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2251
|
+
var ICONS = {
|
|
2252
|
+
dataverse: DatabaseRegular,
|
|
2253
|
+
document: DocumentRegular,
|
|
2254
|
+
web: GlobeRegular,
|
|
2255
|
+
knowledge: BookRegular,
|
|
2256
|
+
other: DocumentBulletListRegular
|
|
2257
|
+
};
|
|
2258
|
+
var useStyles25 = makeStyles26({
|
|
2259
|
+
root: { gap: tokens24.spacingVerticalXXS },
|
|
2260
|
+
head: { display: "flex", alignItems: "center", gap: tokens24.spacingHorizontalXS },
|
|
2261
|
+
marker: { color: tokens24.colorNeutralForeground3, fontSize: tokens24.fontSizeBase200 },
|
|
2262
|
+
snippet: { color: tokens24.colorNeutralForeground2, fontSize: tokens24.fontSizeBase200 },
|
|
2263
|
+
meta: { display: "flex", alignItems: "center", gap: tokens24.spacingHorizontalS, marginTop: tokens24.spacingVerticalXXS }
|
|
2264
|
+
});
|
|
2265
|
+
function SourceCard({ source, marker, onOpen, showSnippet = true }) {
|
|
2266
|
+
const styles = useStyles25();
|
|
2267
|
+
const Icon = ICONS[source.kind] ?? ICONS.other;
|
|
2268
|
+
return /* @__PURE__ */ jsxs24(CardSurface, { className: styles.root, children: [
|
|
2269
|
+
/* @__PURE__ */ jsxs24("div", { className: styles.head, children: [
|
|
2270
|
+
/* @__PURE__ */ jsx34(Icon, { "aria-hidden": "true" }),
|
|
2271
|
+
marker ? /* @__PURE__ */ jsxs24("span", { className: styles.marker, children: [
|
|
2272
|
+
"[",
|
|
2273
|
+
marker,
|
|
2274
|
+
"]"
|
|
2275
|
+
] }) : null,
|
|
2276
|
+
/* @__PURE__ */ jsx34(Text9, { weight: "semibold", children: source.title })
|
|
2277
|
+
] }),
|
|
2278
|
+
showSnippet && source.snippet ? /* @__PURE__ */ jsx34("span", { className: styles.snippet, children: source.snippet }) : null,
|
|
2279
|
+
/* @__PURE__ */ jsxs24("div", { className: styles.meta, children: [
|
|
2280
|
+
onOpen && (source.recordId || source.url) ? /* @__PURE__ */ jsx34(Button13, { size: "small", appearance: "subtle", onClick: () => onOpen(source), children: source.recordId ? "Open record" : "Open" }) : null,
|
|
2281
|
+
/* @__PURE__ */ jsx34("span", { className: styles.snippet, children: source.score === void 0 ? "Relevance not reported" : `Relevance ${Math.round(source.score * 100)}%` })
|
|
2282
|
+
] })
|
|
2283
|
+
] });
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// src/grounding/CitationList.tsx
|
|
2287
|
+
import { jsx as jsx35 } from "react/jsx-runtime";
|
|
2288
|
+
var useStyles26 = makeStyles27({
|
|
2289
|
+
list: { display: "flex", flexDirection: "column", gap: tokens25.spacingVerticalXS, listStyle: "none", margin: 0, padding: 0 }
|
|
2290
|
+
});
|
|
2291
|
+
function CitationList({
|
|
2292
|
+
text,
|
|
2293
|
+
sources,
|
|
2294
|
+
onOpenSource,
|
|
2295
|
+
defaultOpen = false
|
|
2296
|
+
}) {
|
|
2297
|
+
const styles = useStyles26();
|
|
2298
|
+
const id = React17.useId();
|
|
2299
|
+
const { used, citations } = React17.useMemo(() => renumberCitations(text, sources), [text, sources]);
|
|
2300
|
+
if (used.length === 0) return null;
|
|
2301
|
+
const markerFor = (sourceId) => citations.find((c) => c.sourceId === sourceId)?.marker;
|
|
2302
|
+
return /* @__PURE__ */ jsx35(
|
|
2303
|
+
Disclosure,
|
|
2304
|
+
{
|
|
2305
|
+
id,
|
|
2306
|
+
defaultOpen,
|
|
2307
|
+
summary: `${used.length} source${used.length === 1 ? "" : "s"}`,
|
|
2308
|
+
children: /* @__PURE__ */ jsx35("ul", { className: styles.list, children: used.map((source) => /* @__PURE__ */ jsx35("li", { children: /* @__PURE__ */ jsx35(SourceCard, { source, marker: markerFor(source.id), onOpen: onOpenSource }) }, source.id)) })
|
|
2309
|
+
}
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// src/grounding/AnswerWithCitations.tsx
|
|
2314
|
+
import * as React18 from "react";
|
|
2315
|
+
import { makeStyles as makeStyles28 } from "@fluentui/react-components";
|
|
2316
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
2317
|
+
var useStyles27 = makeStyles28({ root: { whiteSpace: "pre-wrap", wordBreak: "break-word" } });
|
|
2318
|
+
function AnswerWithCitations({
|
|
2319
|
+
text,
|
|
2320
|
+
citations = [],
|
|
2321
|
+
sources = [],
|
|
2322
|
+
onOpenSource
|
|
2323
|
+
}) {
|
|
2324
|
+
const styles = useStyles27();
|
|
2325
|
+
const segments = React18.useMemo(() => splitTextByCitations(text, citations), [text, citations]);
|
|
2326
|
+
const byId = React18.useMemo(() => new Map(sources.map((s) => [s.id, s])), [sources]);
|
|
2327
|
+
return /* @__PURE__ */ jsx36("span", { className: styles.root, children: segments.map(
|
|
2328
|
+
(segment, i) => segment.kind === "text" ? /* @__PURE__ */ jsx36(React18.Fragment, { children: segment.text }, i) : /* @__PURE__ */ jsx36(
|
|
2329
|
+
CitationChip,
|
|
2330
|
+
{
|
|
2331
|
+
marker: segment.marker ?? "",
|
|
2332
|
+
source: segment.sourceId ? byId.get(segment.sourceId) : void 0,
|
|
2333
|
+
onOpen: onOpenSource
|
|
2334
|
+
},
|
|
2335
|
+
i
|
|
2336
|
+
)
|
|
2337
|
+
) });
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// src/grounding/GroundingPanel.tsx
|
|
2341
|
+
import * as React19 from "react";
|
|
2342
|
+
import { Text as Text10, makeStyles as makeStyles29, tokens as tokens26 } from "@fluentui/react-components";
|
|
2343
|
+
import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2344
|
+
var useStyles28 = makeStyles29({
|
|
2345
|
+
grid: {
|
|
2346
|
+
display: "grid",
|
|
2347
|
+
gridTemplateColumns: "minmax(96px, max-content) 1fr",
|
|
2348
|
+
gap: `${tokens26.spacingVerticalXXS} ${tokens26.spacingHorizontalM}`,
|
|
2349
|
+
margin: 0,
|
|
2350
|
+
fontSize: tokens26.fontSizeBase200
|
|
2351
|
+
},
|
|
2352
|
+
key: { color: tokens26.colorNeutralForeground3, margin: 0 },
|
|
2353
|
+
value: { margin: 0, wordBreak: "break-word", fontFamily: tokens26.fontFamilyMonospace },
|
|
2354
|
+
empty: { fontSize: tokens26.fontSizeBase200, color: tokens26.colorNeutralForeground3 }
|
|
2355
|
+
});
|
|
2356
|
+
function render(value) {
|
|
2357
|
+
if (value === null || value === void 0) return "\u2014";
|
|
2358
|
+
if (typeof value === "string") return value;
|
|
2359
|
+
return JSON.stringify(value);
|
|
2360
|
+
}
|
|
2361
|
+
function GroundingPanel({
|
|
2362
|
+
grounding,
|
|
2363
|
+
title = "What this answer used",
|
|
2364
|
+
defaultOpen = false
|
|
2365
|
+
}) {
|
|
2366
|
+
const styles = useStyles28();
|
|
2367
|
+
const id = React19.useId();
|
|
2368
|
+
const entries = Object.entries(grounding ?? {});
|
|
2369
|
+
if (entries.length === 0) return null;
|
|
2370
|
+
return /* @__PURE__ */ jsxs25(Disclosure, { id, defaultOpen, summary: title, children: [
|
|
2371
|
+
/* @__PURE__ */ jsx37("dl", { className: styles.grid, children: entries.map(([key, value]) => /* @__PURE__ */ jsxs25(React19.Fragment, { children: [
|
|
2372
|
+
/* @__PURE__ */ jsx37("dt", { className: styles.key, children: key }),
|
|
2373
|
+
/* @__PURE__ */ jsx37("dd", { className: styles.value, children: render(value) })
|
|
2374
|
+
] }, key)) }),
|
|
2375
|
+
/* @__PURE__ */ jsx37(Text10, { as: "p", className: styles.empty, children: "These facts were supplied by the app, not produced by the model." })
|
|
2376
|
+
] });
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// src/grounding/ProviderAttribution.tsx
|
|
2380
|
+
import { makeStyles as makeStyles30, tokens as tokens27 } from "@fluentui/react-components";
|
|
2381
|
+
import { ShieldErrorRegular as ShieldErrorRegular2 } from "@fluentui/react-icons";
|
|
2382
|
+
import { jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2383
|
+
var useStyles29 = makeStyles30({
|
|
2384
|
+
root: {
|
|
2385
|
+
display: "flex",
|
|
2386
|
+
alignItems: "center",
|
|
2387
|
+
gap: tokens27.spacingHorizontalXXS,
|
|
2388
|
+
fontSize: tokens27.fontSizeBase100,
|
|
2389
|
+
color: tokens27.colorNeutralForeground3
|
|
2390
|
+
},
|
|
2391
|
+
guarded: { color: tokens27.colorPaletteDarkOrangeForeground1 }
|
|
2392
|
+
});
|
|
2393
|
+
function ProviderAttribution({ provider, model, guarded }) {
|
|
2394
|
+
const styles = useStyles29();
|
|
2395
|
+
if (!provider && !model && !guarded) return null;
|
|
2396
|
+
const who = [provider, model].filter(Boolean).join(" \xB7 ");
|
|
2397
|
+
if (guarded) {
|
|
2398
|
+
return /* @__PURE__ */ jsxs26("span", { className: `${styles.root} ${styles.guarded}`, children: [
|
|
2399
|
+
/* @__PURE__ */ jsx38(ShieldErrorRegular2, { "aria-hidden": "true" }),
|
|
2400
|
+
"Withheld and replaced with safe text",
|
|
2401
|
+
who ? ` \u2014 not ${who}'s words` : ""
|
|
2402
|
+
] });
|
|
2403
|
+
}
|
|
2404
|
+
return /* @__PURE__ */ jsxs26("span", { className: styles.root, children: [
|
|
2405
|
+
"Answered by ",
|
|
2406
|
+
who || "the agent"
|
|
2407
|
+
] });
|
|
2408
|
+
}
|
|
2409
|
+
function messageAttribution(message) {
|
|
2410
|
+
return { provider: message.provider, model: message.usage?.model, guarded: message.guarded };
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// src/actions/ActionCard.tsx
|
|
2414
|
+
import * as React20 from "react";
|
|
2415
|
+
import { Text as Text11, makeStyles as makeStyles31, tokens as tokens28 } from "@fluentui/react-components";
|
|
2416
|
+
import { jsx as jsx39, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2417
|
+
var useStyles30 = makeStyles31({
|
|
2418
|
+
head: { display: "flex", alignItems: "center", gap: tokens28.spacingHorizontalXS },
|
|
2419
|
+
description: { color: tokens28.colorNeutralForeground2, fontSize: tokens28.fontSizeBase200 },
|
|
2420
|
+
footer: { display: "flex", gap: tokens28.spacingHorizontalS, justifyContent: "flex-end", marginTop: tokens28.spacingVerticalXS }
|
|
2421
|
+
});
|
|
2422
|
+
function ActionCard({ title, description, icon, pending, children, footer }) {
|
|
2423
|
+
const styles = useStyles30();
|
|
2424
|
+
return /* @__PURE__ */ jsxs27(CardSurface, { emphasis: pending ? "attention" : "none", children: [
|
|
2425
|
+
/* @__PURE__ */ jsxs27("div", { className: styles.head, children: [
|
|
2426
|
+
icon ? React20.cloneElement(icon, { "aria-hidden": "true" }) : null,
|
|
2427
|
+
/* @__PURE__ */ jsx39(Text11, { weight: "semibold", children: title })
|
|
2428
|
+
] }),
|
|
2429
|
+
description ? /* @__PURE__ */ jsx39("span", { className: styles.description, children: description }) : null,
|
|
2430
|
+
children,
|
|
2431
|
+
footer ? /* @__PURE__ */ jsx39("div", { className: styles.footer, children: footer }) : null
|
|
2432
|
+
] });
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// src/actions/ApprovalCard.tsx
|
|
2436
|
+
import * as React22 from "react";
|
|
2437
|
+
import { Button as Button15, Spinner, makeStyles as makeStyles34, tokens as tokens31 } from "@fluentui/react-components";
|
|
2438
|
+
import { CheckmarkRegular, DismissRegular as DismissRegular2, EditRegular } from "@fluentui/react-icons";
|
|
2439
|
+
|
|
2440
|
+
// src/actions/ParameterReview.tsx
|
|
2441
|
+
import * as React21 from "react";
|
|
2442
|
+
import { Input, makeStyles as makeStyles32, tokens as tokens29, Text as Text12 } from "@fluentui/react-components";
|
|
2443
|
+
import { jsx as jsx40, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2444
|
+
var useStyles31 = makeStyles32({
|
|
2445
|
+
grid: {
|
|
2446
|
+
display: "grid",
|
|
2447
|
+
gridTemplateColumns: "minmax(110px, max-content) 1fr",
|
|
2448
|
+
gap: `${tokens29.spacingVerticalXS} ${tokens29.spacingHorizontalM}`,
|
|
2449
|
+
alignItems: "center",
|
|
2450
|
+
margin: 0
|
|
2451
|
+
},
|
|
2452
|
+
key: { margin: 0, color: tokens29.colorNeutralForeground2, fontSize: tokens29.fontSizeBase200 },
|
|
2453
|
+
value: { margin: 0 },
|
|
2454
|
+
raw: {
|
|
2455
|
+
whiteSpace: "pre-wrap",
|
|
2456
|
+
wordBreak: "break-all",
|
|
2457
|
+
fontFamily: tokens29.fontFamilyMonospace,
|
|
2458
|
+
fontSize: tokens29.fontSizeBase200,
|
|
2459
|
+
color: tokens29.colorNeutralForeground2
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
function ParameterReview({ argumentsJson, readOnly = true, onChange }) {
|
|
2463
|
+
const styles = useStyles31();
|
|
2464
|
+
const parsed = React21.useMemo(() => {
|
|
2465
|
+
try {
|
|
2466
|
+
const value = JSON.parse(argumentsJson);
|
|
2467
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2468
|
+
} catch {
|
|
2469
|
+
return null;
|
|
2470
|
+
}
|
|
2471
|
+
}, [argumentsJson]);
|
|
2472
|
+
if (!parsed) {
|
|
2473
|
+
return /* @__PURE__ */ jsxs28("div", { children: [
|
|
2474
|
+
/* @__PURE__ */ jsx40(Text12, { size: 200, children: "Arguments (unparsed)" }),
|
|
2475
|
+
/* @__PURE__ */ jsx40("pre", { className: styles.raw, children: argumentsJson || "(empty)" })
|
|
2476
|
+
] });
|
|
2477
|
+
}
|
|
2478
|
+
const set = (key, next) => {
|
|
2479
|
+
if (!onChange) return;
|
|
2480
|
+
onChange(JSON.stringify({ ...parsed, [key]: next }));
|
|
2481
|
+
};
|
|
2482
|
+
return /* @__PURE__ */ jsx40("dl", { className: styles.grid, children: Object.entries(parsed).map(([key, value]) => /* @__PURE__ */ jsxs28(React21.Fragment, { children: [
|
|
2483
|
+
/* @__PURE__ */ jsx40("dt", { className: styles.key, children: key }),
|
|
2484
|
+
/* @__PURE__ */ jsx40("dd", { className: styles.value, children: readOnly ? /* @__PURE__ */ jsx40("span", { className: styles.raw, children: typeof value === "string" ? value : JSON.stringify(value) }) : /* @__PURE__ */ jsx40(
|
|
2485
|
+
Input,
|
|
2486
|
+
{
|
|
2487
|
+
size: "small",
|
|
2488
|
+
"aria-label": key,
|
|
2489
|
+
value: typeof value === "string" ? value : JSON.stringify(value),
|
|
2490
|
+
onChange: (_, data) => set(key, data.value)
|
|
2491
|
+
}
|
|
2492
|
+
) })
|
|
2493
|
+
] }, key)) });
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
// src/actions/ActionResult.tsx
|
|
2497
|
+
import { MessageBar as MessageBar4, MessageBarBody as MessageBarBody4, MessageBarTitle as MessageBarTitle4, MessageBarActions as MessageBarActions4, Button as Button14, makeStyles as makeStyles33, tokens as tokens30 } from "@fluentui/react-components";
|
|
2498
|
+
import { jsx as jsx41, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
2499
|
+
var INTENT2 = { succeeded: "success", failed: "error", denied: "warning" };
|
|
2500
|
+
var TITLE = { succeeded: "Done", failed: "Failed", denied: "Not approved" };
|
|
2501
|
+
var useStyles32 = makeStyles33({
|
|
2502
|
+
id: { display: "block", marginTop: tokens30.spacingVerticalXXS, fontSize: tokens30.fontSizeBase100, color: tokens30.colorNeutralForeground3, fontFamily: tokens30.fontFamilyMonospace }
|
|
2503
|
+
});
|
|
2504
|
+
function ActionResult({ status, message, correlationId, onRetry }) {
|
|
2505
|
+
const styles = useStyles32();
|
|
2506
|
+
return /* @__PURE__ */ jsxs29(MessageBar4, { intent: INTENT2[status], children: [
|
|
2507
|
+
/* @__PURE__ */ jsxs29(MessageBarBody4, { children: [
|
|
2508
|
+
/* @__PURE__ */ jsx41(MessageBarTitle4, { children: TITLE[status] }),
|
|
2509
|
+
message,
|
|
2510
|
+
correlationId ? /* @__PURE__ */ jsxs29("span", { className: styles.id, children: [
|
|
2511
|
+
"Correlation id: ",
|
|
2512
|
+
correlationId
|
|
2513
|
+
] }) : null
|
|
2514
|
+
] }),
|
|
2515
|
+
status === "failed" && onRetry ? /* @__PURE__ */ jsx41(MessageBarActions4, { children: /* @__PURE__ */ jsx41(Button14, { size: "small", onClick: onRetry, children: "Try again" }) }) : null
|
|
2516
|
+
] });
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/actions/ApprovalCard.tsx
|
|
2520
|
+
import { Fragment as Fragment8, jsx as jsx42, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
2521
|
+
var useStyles33 = makeStyles34({
|
|
2522
|
+
busy: { display: "flex", alignItems: "center", gap: tokens31.spacingHorizontalXS, fontSize: tokens31.fontSizeBase200 }
|
|
2523
|
+
});
|
|
2524
|
+
function ApprovalCard({
|
|
2525
|
+
toolCall,
|
|
2526
|
+
onApprove,
|
|
2527
|
+
onDeny,
|
|
2528
|
+
allowEdit = false,
|
|
2529
|
+
busy = false
|
|
2530
|
+
}) {
|
|
2531
|
+
const styles = useStyles33();
|
|
2532
|
+
const [args, setArgs] = React22.useState(toolCall.argumentsJson);
|
|
2533
|
+
const [editing, setEditing] = React22.useState(false);
|
|
2534
|
+
const [decided, setDecided] = React22.useState(false);
|
|
2535
|
+
const settled = toolCall.status === "succeeded" || toolCall.status === "failed" || toolCall.status === "denied";
|
|
2536
|
+
if (settled) {
|
|
2537
|
+
return /* @__PURE__ */ jsxs30(ActionCard, { title: toolCall.name, description: "Tool call", children: [
|
|
2538
|
+
/* @__PURE__ */ jsx42(ParameterReview, { argumentsJson: toolCall.argumentsJson }),
|
|
2539
|
+
/* @__PURE__ */ jsx42(
|
|
2540
|
+
ActionResult,
|
|
2541
|
+
{
|
|
2542
|
+
status: toolCall.status === "succeeded" ? "succeeded" : toolCall.status === "denied" ? "denied" : "failed",
|
|
2543
|
+
message: toolCall.resultText ?? toolCall.failure?.message ?? "Completed.",
|
|
2544
|
+
correlationId: toolCall.failure?.correlationId
|
|
2545
|
+
}
|
|
2546
|
+
)
|
|
2547
|
+
] });
|
|
2548
|
+
}
|
|
2549
|
+
const locked = decided || busy || toolCall.status === "running";
|
|
2550
|
+
const approve = () => {
|
|
2551
|
+
if (locked) return;
|
|
2552
|
+
setDecided(true);
|
|
2553
|
+
onApprove(toolCall, args);
|
|
2554
|
+
};
|
|
2555
|
+
const deny = () => {
|
|
2556
|
+
if (locked) return;
|
|
2557
|
+
setDecided(true);
|
|
2558
|
+
onDeny(toolCall);
|
|
2559
|
+
};
|
|
2560
|
+
return /* @__PURE__ */ jsx42(
|
|
2561
|
+
ActionCard,
|
|
2562
|
+
{
|
|
2563
|
+
title: toolCall.name,
|
|
2564
|
+
description: "The agent is asking to run this. Check the details before approving.",
|
|
2565
|
+
pending: !locked,
|
|
2566
|
+
footer: locked ? /* @__PURE__ */ jsxs30("span", { className: styles.busy, children: [
|
|
2567
|
+
/* @__PURE__ */ jsx42(Spinner, { size: "tiny" }),
|
|
2568
|
+
" Submitting\u2026"
|
|
2569
|
+
] }) : /* @__PURE__ */ jsxs30(Fragment8, { children: [
|
|
2570
|
+
allowEdit ? /* @__PURE__ */ jsx42(Button15, { size: "small", icon: /* @__PURE__ */ jsx42(EditRegular, {}), onClick: () => setEditing((v) => !v), children: editing ? "Done editing" : "Edit" }) : null,
|
|
2571
|
+
/* @__PURE__ */ jsx42(Button15, { size: "small", icon: /* @__PURE__ */ jsx42(DismissRegular2, {}), onClick: deny, disabled: locked, children: "Deny" }),
|
|
2572
|
+
/* @__PURE__ */ jsx42(Button15, { size: "small", appearance: "primary", icon: /* @__PURE__ */ jsx42(CheckmarkRegular, {}), onClick: approve, disabled: locked, children: "Approve" })
|
|
2573
|
+
] }),
|
|
2574
|
+
children: /* @__PURE__ */ jsx42(ParameterReview, { argumentsJson: args, readOnly: !editing, onChange: setArgs })
|
|
2575
|
+
}
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// src/actions/ToolCallCard.tsx
|
|
2580
|
+
import * as React23 from "react";
|
|
2581
|
+
import { Badge as Badge3, Spinner as Spinner2, makeStyles as makeStyles35, tokens as tokens32 } from "@fluentui/react-components";
|
|
2582
|
+
import { jsx as jsx43, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
2583
|
+
var LABEL = {
|
|
2584
|
+
proposed: "Proposed",
|
|
2585
|
+
running: "Running",
|
|
2586
|
+
succeeded: "Succeeded",
|
|
2587
|
+
failed: "Failed",
|
|
2588
|
+
denied: "Denied"
|
|
2589
|
+
};
|
|
2590
|
+
var COLOUR = {
|
|
2591
|
+
proposed: "informative",
|
|
2592
|
+
running: "informative",
|
|
2593
|
+
succeeded: "success",
|
|
2594
|
+
failed: "danger",
|
|
2595
|
+
denied: "warning"
|
|
2596
|
+
};
|
|
2597
|
+
var useStyles34 = makeStyles35({
|
|
2598
|
+
head: { display: "flex", alignItems: "center", gap: tokens32.spacingHorizontalXS },
|
|
2599
|
+
name: { fontFamily: tokens32.fontFamilyMonospace, fontSize: tokens32.fontSizeBase200 },
|
|
2600
|
+
result: {
|
|
2601
|
+
whiteSpace: "pre-wrap",
|
|
2602
|
+
wordBreak: "break-all",
|
|
2603
|
+
fontFamily: tokens32.fontFamilyMonospace,
|
|
2604
|
+
fontSize: tokens32.fontSizeBase200,
|
|
2605
|
+
marginTop: tokens32.spacingVerticalXS
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2608
|
+
function ToolCallCard({ toolCall, defaultExpanded = false }) {
|
|
2609
|
+
const styles = useStyles34();
|
|
2610
|
+
const id = React23.useId();
|
|
2611
|
+
return /* @__PURE__ */ jsx43(CardSurface, { children: /* @__PURE__ */ jsxs31(
|
|
2612
|
+
Disclosure,
|
|
2613
|
+
{
|
|
2614
|
+
id,
|
|
2615
|
+
defaultOpen: defaultExpanded,
|
|
2616
|
+
summary: /* @__PURE__ */ jsxs31("span", { className: styles.head, children: [
|
|
2617
|
+
/* @__PURE__ */ jsx43("span", { className: styles.name, children: toolCall.name }),
|
|
2618
|
+
/* @__PURE__ */ jsx43(Badge3, { appearance: "tint", color: COLOUR[toolCall.status], children: LABEL[toolCall.status] }),
|
|
2619
|
+
toolCall.status === "running" ? /* @__PURE__ */ jsx43(Spinner2, { size: "extra-tiny" }) : null
|
|
2620
|
+
] }),
|
|
2621
|
+
children: [
|
|
2622
|
+
/* @__PURE__ */ jsx43(ParameterReview, { argumentsJson: toolCall.argumentsJson }),
|
|
2623
|
+
toolCall.resultText ? /* @__PURE__ */ jsx43("div", { className: styles.result, children: toolCall.resultText }) : null,
|
|
2624
|
+
toolCall.failure ? /* @__PURE__ */ jsx43("div", { className: styles.result, children: toolCall.failure.message }) : null
|
|
2625
|
+
]
|
|
2626
|
+
}
|
|
2627
|
+
) });
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
// src/host/AgentKitHostProvider.tsx
|
|
2631
|
+
import * as React24 from "react";
|
|
2632
|
+
import { FluentProvider, IdPrefixProvider } from "@fluentui/react-components";
|
|
2633
|
+
|
|
2634
|
+
// src/theme/index.ts
|
|
2635
|
+
import {
|
|
2636
|
+
createDynamicsV9Theme,
|
|
2637
|
+
createDynamicsV9DarkTheme,
|
|
2638
|
+
dynamicsBrandRamp
|
|
2639
|
+
} from "@dataverse-kit/fluent-theme";
|
|
2640
|
+
import { typography, radius } from "@dataverse-kit/design-tokens";
|
|
2641
|
+
function applyAgentOverrides(base) {
|
|
2642
|
+
return {
|
|
2643
|
+
...base,
|
|
2644
|
+
// Fluent's web theme is close to CE but does not pin the Web variant of Segoe.
|
|
2645
|
+
fontFamilyBase: typography.fontFamily,
|
|
2646
|
+
// CE is squarer than Fluent web's default. Bubbles opt back up to borderRadiusLarge.
|
|
2647
|
+
borderRadiusMedium: radius.card
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
function createAgentTheme(brand = dynamicsBrandRamp) {
|
|
2651
|
+
return applyAgentOverrides(createDynamicsV9Theme(brand));
|
|
2652
|
+
}
|
|
2653
|
+
function createAgentDarkTheme(brand = dynamicsBrandRamp) {
|
|
2654
|
+
return applyAgentOverrides(createDynamicsV9DarkTheme(brand));
|
|
2655
|
+
}
|
|
2656
|
+
var agentTheme = createAgentTheme();
|
|
2657
|
+
var agentDarkTheme = createAgentDarkTheme();
|
|
2658
|
+
function resolveHostTheme(input = {}) {
|
|
2659
|
+
if (input.theme) return input.theme;
|
|
2660
|
+
if (input.hostTheme) return input.hostTheme;
|
|
2661
|
+
return input.isDarkTheme ? agentDarkTheme : agentTheme;
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
// src/host/idPrefix.ts
|
|
2665
|
+
var nextAgentIdPrefix = createIdPrefixCounter("dvkak");
|
|
2666
|
+
|
|
2667
|
+
// src/host/AgentKitHostProvider.tsx
|
|
2668
|
+
import { jsx as jsx44 } from "react/jsx-runtime";
|
|
2669
|
+
function AgentKitHostProvider(props) {
|
|
2670
|
+
const { children, theme, hostTheme, isDarkTheme, targetDocument, className, style } = props;
|
|
2671
|
+
const [generated] = React24.useState(nextAgentIdPrefix);
|
|
2672
|
+
const idPrefix = props.idPrefix ?? generated;
|
|
2673
|
+
const resolved = React24.useMemo(
|
|
2674
|
+
() => resolveHostTheme({ theme, hostTheme, isDarkTheme }),
|
|
2675
|
+
[theme, hostTheme, isDarkTheme]
|
|
2676
|
+
);
|
|
2677
|
+
return /* @__PURE__ */ jsx44(IdPrefixProvider, { value: idPrefix, children: /* @__PURE__ */ jsx44(
|
|
2678
|
+
FluentProvider,
|
|
2679
|
+
{
|
|
2680
|
+
theme: resolved,
|
|
2681
|
+
targetDocument,
|
|
2682
|
+
className,
|
|
2683
|
+
style,
|
|
2684
|
+
children
|
|
2685
|
+
}
|
|
2686
|
+
) });
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// src/services/scenarios.ts
|
|
2690
|
+
var SOURCES = [
|
|
2691
|
+
{ id: "src-acct", title: "Contoso Ltd", kind: "dataverse", entityLogicalName: "account", recordId: "11111111-1111-1111-1111-111111111111", snippet: "Annual revenue \xA34.2m, 38 employees." },
|
|
2692
|
+
{ id: "src-case", title: "Case 4417 \u2014 invoice mismatch", kind: "dataverse", entityLogicalName: "incident", recordId: "22222222-2222-2222-2222-222222222222", snippet: "Raised 12 Mar, priority High." },
|
|
2693
|
+
{ id: "src-kb", title: "KB-208: credit note policy", kind: "knowledge", url: "https://example.invalid/kb/208", snippet: "Credit notes above \xA35,000 require finance approval.", score: 0.82 },
|
|
2694
|
+
{ id: "src-doc", title: "Statement-Mar.pdf", kind: "document", snippet: "Page 3 lists the disputed line." },
|
|
2695
|
+
{ id: "src-web", title: "Companies House filing", kind: "web", url: "https://example.invalid/ch" }
|
|
2696
|
+
];
|
|
2697
|
+
var mockScenarios = {
|
|
2698
|
+
happy: {
|
|
2699
|
+
text: "Contoso has three open cases. The oldest, case 4417, has been waiting eleven days for a credit note.",
|
|
2700
|
+
status: "succeeded",
|
|
2701
|
+
suggestions: ["Draft a credit note", "Show all three cases", "Who owns case 4417?"]
|
|
2702
|
+
},
|
|
2703
|
+
// ★ FIVE sources, but the text cites only [3] and [1] — and out of order. The panel must
|
|
2704
|
+
// show TWO, renumbered 1 and 2 in first-appearance order. A fixture where every source
|
|
2705
|
+
// is cited cannot tell a correct implementation from one that lists everything it was given.
|
|
2706
|
+
grounded: {
|
|
2707
|
+
text: "Credit notes over \xA35,000 need finance approval [3], and case 4417 is for \xA36,200 [1]. So this one needs sign-off.",
|
|
2708
|
+
status: "succeeded",
|
|
2709
|
+
sources: SOURCES
|
|
2710
|
+
},
|
|
2711
|
+
toolApproval: {
|
|
2712
|
+
text: "I can raise the credit note for you \u2014 approve the details below and I will submit it.",
|
|
2713
|
+
status: "succeeded",
|
|
2714
|
+
toolCall: {
|
|
2715
|
+
id: "call-1",
|
|
2716
|
+
name: "create_credit_note",
|
|
2717
|
+
argumentsJson: '{"accountId":"11111111-1111-1111-1111-111111111111","amount":6200,"currency":"GBP","reason":"Invoice mismatch, case 4417"}',
|
|
2718
|
+
status: "proposed",
|
|
2719
|
+
requiresApproval: true
|
|
2720
|
+
}
|
|
2721
|
+
},
|
|
2722
|
+
toolAuto: {
|
|
2723
|
+
text: "I checked the balance for you.",
|
|
2724
|
+
status: "succeeded",
|
|
2725
|
+
toolCall: { id: "call-2", name: "get_account_balance", argumentsJson: '{"accountId":"1111"}', status: "running" },
|
|
2726
|
+
autoToolResult: '{"balance":6200,"currency":"GBP"}'
|
|
2727
|
+
},
|
|
2728
|
+
// ★ blocked and refused MUST look different on screen: a content filter fired, versus the
|
|
2729
|
+
// model declining. They need different fixes, so they are different states.
|
|
2730
|
+
blocked: {
|
|
2731
|
+
text: "I can\u2019t help with that request.",
|
|
2732
|
+
status: "blocked",
|
|
2733
|
+
guarded: true,
|
|
2734
|
+
failureMessage: "The response was withheld by the content filter.",
|
|
2735
|
+
failureCode: "content_filter",
|
|
2736
|
+
recoverable: false
|
|
2737
|
+
},
|
|
2738
|
+
refused: {
|
|
2739
|
+
text: "I don\u2019t have enough information to answer that safely. Could you say which account you mean?",
|
|
2740
|
+
status: "refused",
|
|
2741
|
+
failureMessage: "The agent declined to answer.",
|
|
2742
|
+
recoverable: true
|
|
2743
|
+
},
|
|
2744
|
+
error: {
|
|
2745
|
+
text: "",
|
|
2746
|
+
status: "error",
|
|
2747
|
+
failureMessage: "The agent is rate limited. Try again shortly.",
|
|
2748
|
+
failureCode: "rate_limited",
|
|
2749
|
+
recoverable: true,
|
|
2750
|
+
retryAfterMs: 4e3
|
|
2751
|
+
},
|
|
2752
|
+
notProvisioned: {
|
|
2753
|
+
text: "",
|
|
2754
|
+
status: "notProvisioned",
|
|
2755
|
+
failureMessage: "No Foundry project is configured for this environment.",
|
|
2756
|
+
failureCode: "not_provisioned",
|
|
2757
|
+
recoverable: false,
|
|
2758
|
+
unavailable: true
|
|
2759
|
+
},
|
|
2760
|
+
cancelled: {
|
|
2761
|
+
text: "Let me pull the last six months of activity for Contoso and work through it case by case, starting with the oldest\u2026",
|
|
2762
|
+
status: "cancelled",
|
|
2763
|
+
failureMessage: "Stopped."
|
|
2764
|
+
},
|
|
2765
|
+
// The fixture the autoscroll and live-region gates run against: enough deltas that
|
|
2766
|
+
// "once, not per token" is a meaningful distinction.
|
|
2767
|
+
longAnswer: {
|
|
2768
|
+
text: Array.from({ length: 200 }, (_, i) => `word${i + 1}`).join(" "),
|
|
2769
|
+
status: "succeeded"
|
|
2770
|
+
},
|
|
2771
|
+
throws: {
|
|
2772
|
+
text: "partial\u2026",
|
|
2773
|
+
status: "error",
|
|
2774
|
+
throwsMidStream: true,
|
|
2775
|
+
failureMessage: "never reached \u2014 the iterator throws instead"
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
|
|
2779
|
+
// src/services/MockAgentService.ts
|
|
2780
|
+
var realScheduler = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2781
|
+
var immediateScheduler = () => Promise.resolve();
|
|
2782
|
+
function mulberry32(seed) {
|
|
2783
|
+
let a = seed >>> 0;
|
|
2784
|
+
return () => {
|
|
2785
|
+
a = a + 1831565813 >>> 0;
|
|
2786
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
2787
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2788
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
var MockAgentService = class {
|
|
2792
|
+
constructor(options = {}) {
|
|
2793
|
+
this.name = "mock";
|
|
2794
|
+
this.scenarioName = options.scenario ?? "happy";
|
|
2795
|
+
this.scenario = mockScenarios[this.scenarioName];
|
|
2796
|
+
this.scheduler = options.scheduler ?? realScheduler;
|
|
2797
|
+
this.firstTokenMs = options.firstTokenMs ?? 400;
|
|
2798
|
+
this.chunkMs = options.chunkMs ?? 28;
|
|
2799
|
+
this.rand = mulberry32(options.seed ?? 1);
|
|
2800
|
+
this.availabilityOverride = options.availability;
|
|
2801
|
+
}
|
|
2802
|
+
async isAvailable() {
|
|
2803
|
+
if (this.availabilityOverride) return this.availabilityOverride;
|
|
2804
|
+
if (this.scenario.unavailable) {
|
|
2805
|
+
return {
|
|
2806
|
+
available: false,
|
|
2807
|
+
status: "notProvisioned",
|
|
2808
|
+
reason: this.scenario.failureMessage ?? "Not provisioned."
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
return {
|
|
2812
|
+
available: true,
|
|
2813
|
+
capabilities: { streaming: true, tools: true, citations: true, attachments: true, cancel: true }
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* A prompt can select a scenario (`/blocked`, `/tool`, `/error`), so one story can walk
|
|
2818
|
+
* every state without remounting.
|
|
2819
|
+
*/
|
|
2820
|
+
resolveScenario(promptText) {
|
|
2821
|
+
const slash = promptText.trim().match(/^\/(\w+)/)?.[1];
|
|
2822
|
+
if (slash && slash in mockScenarios) {
|
|
2823
|
+
return { name: slash, scenario: mockScenarios[slash] };
|
|
2824
|
+
}
|
|
2825
|
+
return { name: this.scenarioName, scenario: this.scenario };
|
|
2826
|
+
}
|
|
2827
|
+
async *send(turn) {
|
|
2828
|
+
const { scenario } = this.resolveScenario(turn.text);
|
|
2829
|
+
const messageId = `mock-${Math.floor(this.rand() * 1e9).toString(36)}`;
|
|
2830
|
+
const turnId = `turn-${messageId}`;
|
|
2831
|
+
const provider = this.name;
|
|
2832
|
+
const finish = (result) => ({
|
|
2833
|
+
type: "turn_end",
|
|
2834
|
+
turnId,
|
|
2835
|
+
messageId,
|
|
2836
|
+
result: { ...result, provider }
|
|
2837
|
+
});
|
|
2838
|
+
yield { type: "turn_start", turnId, messageId, provider, model: "mock-1" };
|
|
2839
|
+
if (turn.cancelSignal?.aborted) {
|
|
2840
|
+
yield finish({ status: "cancelled", text: "", failure: { status: "cancelled", message: "Stopped." } });
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
if (scenario.status === "notProvisioned" || scenario.status === "error" && !scenario.text) {
|
|
2844
|
+
await this.scheduler(this.firstTokenMs);
|
|
2845
|
+
yield finish({
|
|
2846
|
+
status: scenario.status,
|
|
2847
|
+
text: "",
|
|
2848
|
+
failure: {
|
|
2849
|
+
status: scenario.status,
|
|
2850
|
+
message: scenario.failureMessage ?? "Failed.",
|
|
2851
|
+
code: scenario.failureCode,
|
|
2852
|
+
recoverable: scenario.recoverable,
|
|
2853
|
+
retryAfterMs: scenario.retryAfterMs
|
|
2854
|
+
}
|
|
2855
|
+
});
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
if (scenario.sources) {
|
|
2859
|
+
yield { type: "status", messageId, label: "Searching Dataverse\u2026" };
|
|
2860
|
+
await this.scheduler(this.firstTokenMs);
|
|
2861
|
+
yield { type: "sources", messageId, sources: scenario.sources };
|
|
2862
|
+
}
|
|
2863
|
+
if (scenario.thinking) {
|
|
2864
|
+
yield { type: "thinking_delta", messageId, text: scenario.thinking };
|
|
2865
|
+
}
|
|
2866
|
+
if (scenario.toolCall) {
|
|
2867
|
+
yield { type: "tool_call", messageId, call: scenario.toolCall };
|
|
2868
|
+
if (scenario.autoToolResult !== void 0) {
|
|
2869
|
+
await this.scheduler(this.chunkMs);
|
|
2870
|
+
yield {
|
|
2871
|
+
type: "tool_result",
|
|
2872
|
+
messageId,
|
|
2873
|
+
callId: scenario.toolCall.id,
|
|
2874
|
+
status: "succeeded",
|
|
2875
|
+
resultText: scenario.autoToolResult
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
const chunks = scenario.chunks ?? scenario.text.split(/(?<=\s)/);
|
|
2880
|
+
await this.scheduler(this.firstTokenMs);
|
|
2881
|
+
let emitted = "";
|
|
2882
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
2883
|
+
if (turn.cancelSignal?.aborted) {
|
|
2884
|
+
yield finish({
|
|
2885
|
+
status: "cancelled",
|
|
2886
|
+
text: emitted,
|
|
2887
|
+
failure: { status: "cancelled", message: "Stopped." }
|
|
2888
|
+
});
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
if (scenario.throwsMidStream && i === Math.floor(chunks.length / 2)) {
|
|
2892
|
+
throw new Error("mock transport exploded mid-stream");
|
|
2893
|
+
}
|
|
2894
|
+
emitted += chunk;
|
|
2895
|
+
yield { type: "text_delta", messageId, text: chunk };
|
|
2896
|
+
await this.scheduler(this.chunkMs);
|
|
2897
|
+
}
|
|
2898
|
+
if (scenario.suggestions) {
|
|
2899
|
+
yield { type: "suggestions", messageId, prompts: scenario.suggestions };
|
|
2900
|
+
}
|
|
2901
|
+
const failed = scenario.status !== "succeeded";
|
|
2902
|
+
yield finish({
|
|
2903
|
+
status: scenario.status,
|
|
2904
|
+
text: emitted,
|
|
2905
|
+
guarded: scenario.guarded,
|
|
2906
|
+
usage: {
|
|
2907
|
+
inputTokens: 128,
|
|
2908
|
+
outputTokens: chunks.length,
|
|
2909
|
+
costUsd: Number((chunks.length * 2e-6).toFixed(6)),
|
|
2910
|
+
durationMs: this.firstTokenMs + chunks.length * this.chunkMs,
|
|
2911
|
+
model: "mock-1"
|
|
2912
|
+
},
|
|
2913
|
+
failure: failed ? {
|
|
2914
|
+
status: scenario.status,
|
|
2915
|
+
message: scenario.failureMessage ?? "Failed.",
|
|
2916
|
+
code: scenario.failureCode,
|
|
2917
|
+
recoverable: scenario.recoverable,
|
|
2918
|
+
retryAfterMs: scenario.retryAfterMs
|
|
2919
|
+
} : void 0
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
|
|
2924
|
+
// src/services/NullAgentService.ts
|
|
2925
|
+
var NullAgentService = class {
|
|
2926
|
+
constructor() {
|
|
2927
|
+
this.name = "none";
|
|
2928
|
+
}
|
|
2929
|
+
async isAvailable() {
|
|
2930
|
+
return {
|
|
2931
|
+
available: false,
|
|
2932
|
+
status: "notProvisioned",
|
|
2933
|
+
reason: "No agent provider is configured for this host."
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
async *send(turn) {
|
|
2937
|
+
const messageId = `null-${Date.now()}`;
|
|
2938
|
+
const turnId = `turn-${messageId}`;
|
|
2939
|
+
void turn;
|
|
2940
|
+
yield { type: "turn_start", turnId, messageId, provider: this.name };
|
|
2941
|
+
yield {
|
|
2942
|
+
type: "turn_end",
|
|
2943
|
+
turnId,
|
|
2944
|
+
messageId,
|
|
2945
|
+
result: {
|
|
2946
|
+
status: "notProvisioned",
|
|
2947
|
+
text: "",
|
|
2948
|
+
provider: this.name,
|
|
2949
|
+
failure: {
|
|
2950
|
+
status: "notProvisioned",
|
|
2951
|
+
message: "No agent provider is configured for this host.",
|
|
2952
|
+
code: "not_provisioned",
|
|
2953
|
+
recoverable: false
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
|
|
2960
|
+
// src/services/createAgentService.ts
|
|
2961
|
+
function decide(config) {
|
|
2962
|
+
switch (config.provider) {
|
|
2963
|
+
case "custom":
|
|
2964
|
+
return config.factory ? { provider: config.factory(), reason: "explicit factory (provider: custom)" } : { provider: new NullAgentService(), reason: "provider: custom but no factory supplied" };
|
|
2965
|
+
case "mock":
|
|
2966
|
+
return { provider: new MockAgentService(), reason: "provider: mock" };
|
|
2967
|
+
case "none":
|
|
2968
|
+
return { provider: new NullAgentService(), reason: "provider: none (the default)" };
|
|
2969
|
+
default:
|
|
2970
|
+
return {
|
|
2971
|
+
provider: new NullAgentService(),
|
|
2972
|
+
reason: `unrecognised provider ${String(config.provider)} \u2014 falling back to none`
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
function createAgentService(config = { provider: "none" }) {
|
|
2977
|
+
return decide(config).provider;
|
|
2978
|
+
}
|
|
2979
|
+
export {
|
|
2980
|
+
ANNOUNCE_FULL_TEXT_MAX_CHARS,
|
|
2981
|
+
ActionCard,
|
|
2982
|
+
ActionResult,
|
|
2983
|
+
AgentAppShell,
|
|
2984
|
+
AgentAvatar,
|
|
2985
|
+
AgentKitHostProvider,
|
|
2986
|
+
AgentOpsPanel,
|
|
2987
|
+
AgentPicker,
|
|
2988
|
+
AgentStatusBadge,
|
|
2989
|
+
AnswerWithCitations,
|
|
2990
|
+
AppRail,
|
|
2991
|
+
ApprovalCard,
|
|
2992
|
+
AvailabilityBanner,
|
|
2993
|
+
CitationChip,
|
|
2994
|
+
CitationList,
|
|
2995
|
+
Composer,
|
|
2996
|
+
ConfirmInline,
|
|
2997
|
+
ConversationHeader,
|
|
2998
|
+
ConversationList,
|
|
2999
|
+
ConversationListItem,
|
|
3000
|
+
DEFAULT_GROUP_WINDOW_MS,
|
|
3001
|
+
DayDivider,
|
|
3002
|
+
EmptyConversation,
|
|
3003
|
+
GlobalSearchBar,
|
|
3004
|
+
GroundingPanel,
|
|
3005
|
+
LiveAnnouncer,
|
|
3006
|
+
MessageActions,
|
|
3007
|
+
MessageBubble,
|
|
3008
|
+
MessageList,
|
|
3009
|
+
MockAgentService,
|
|
3010
|
+
ModelBadge,
|
|
3011
|
+
NO_ANNOUNCEMENT,
|
|
3012
|
+
NullAgentService,
|
|
3013
|
+
ParameterReview,
|
|
3014
|
+
ProviderAttribution,
|
|
3015
|
+
RightPane,
|
|
3016
|
+
RunHistoryPanel,
|
|
3017
|
+
RunTimeline,
|
|
3018
|
+
STICK_TO_BOTTOM_THRESHOLD_PX,
|
|
3019
|
+
SourceCard,
|
|
3020
|
+
StreamingText,
|
|
3021
|
+
SuggestedPrompts,
|
|
3022
|
+
SystemNotice,
|
|
3023
|
+
ThrottleNotice,
|
|
3024
|
+
ToolCallCard,
|
|
3025
|
+
TypingIndicator,
|
|
3026
|
+
UsageMeter,
|
|
3027
|
+
agentDarkTheme,
|
|
3028
|
+
agentTheme,
|
|
3029
|
+
appendUserMessage,
|
|
3030
|
+
applyAgentEvent,
|
|
3031
|
+
bucketByDay,
|
|
3032
|
+
classifyTransportFailure,
|
|
3033
|
+
conversationAccessibleName,
|
|
3034
|
+
createAgentDarkTheme,
|
|
3035
|
+
createAgentService,
|
|
3036
|
+
createAgentTheme,
|
|
3037
|
+
createIdPrefixCounter,
|
|
3038
|
+
dayKeyOf,
|
|
3039
|
+
decide,
|
|
3040
|
+
deriveAnnouncement,
|
|
3041
|
+
extractCitationMarkers,
|
|
3042
|
+
formatDayLabel,
|
|
3043
|
+
fromClaudeStreamEvent,
|
|
3044
|
+
fromFoundryChatResult,
|
|
3045
|
+
fromOpenAiChatChunk,
|
|
3046
|
+
fromSseLine,
|
|
3047
|
+
groupMessages,
|
|
3048
|
+
immediateScheduler,
|
|
3049
|
+
initialConversation,
|
|
3050
|
+
isSameAuthorRun,
|
|
3051
|
+
isTerminalStatus,
|
|
3052
|
+
messageAttribution,
|
|
3053
|
+
mockScenarios,
|
|
3054
|
+
nextAgentIdPrefix,
|
|
3055
|
+
realScheduler,
|
|
3056
|
+
renumberCitations,
|
|
3057
|
+
resolveHostTheme,
|
|
3058
|
+
runStatusLabel,
|
|
3059
|
+
runStatusSeverity,
|
|
3060
|
+
shouldStickToBottom,
|
|
3061
|
+
splitTextByCitations,
|
|
3062
|
+
useConversation,
|
|
3063
|
+
useFocusRestore,
|
|
3064
|
+
useRovingTabindex,
|
|
3065
|
+
useStickyScroll,
|
|
3066
|
+
useVisuallyHiddenStyles
|
|
3067
|
+
};
|
|
3068
|
+
//# sourceMappingURL=index.mjs.map
|