@appilots/web-sdk 0.2.0 → 0.3.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 +19 -0
- package/dist/{chunk-T5TUFZ6T.js → chunk-H4ITA2QP.js} +255 -16
- package/dist/{chunk-VHRE4RUO.mjs → chunk-RR3T5CPP.mjs} +250 -17
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +70 -58
- package/dist/index.mjs +1 -1
- package/dist/react/index.d.mts +19 -5
- package/dist/react/index.d.ts +19 -5
- package/dist/react/index.js +516 -101
- package/dist/react/index.mjs +505 -98
- package/dist/{AppilotsWebRuntime-DZF26UyU.d.mts → webSpeechHost-CrTKADeb.d.mts} +127 -1
- package/dist/{AppilotsWebRuntime-DZF26UyU.d.ts → webSpeechHost-CrTKADeb.d.ts} +127 -1
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AppilotsWebRuntime, APPILOTS_CHAT_ATTR, parseMarkdownBlocks, parseInlineMarkdown } from '../chunk-
|
|
2
|
-
export { AppilotsWebRuntime } from '../chunk-
|
|
3
|
-
import { createContext, useMemo, useEffect, useRef, useContext, useSyncExternalStore, useCallback, useState,
|
|
4
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
1
|
+
import { AppilotsWebRuntime, createWebSpeechHost, isSpeechHost, APPILOTS_CHAT_ATTR, confirmExpiresAt, describeAction, startStillWorkingTimer, appilotsDebugWarn, parseMarkdownBlocks, parseInlineMarkdown } from '../chunk-RR3T5CPP.mjs';
|
|
2
|
+
export { AppilotsWebRuntime, createWebSpeechHost, isWebSpeechSupported } from '../chunk-RR3T5CPP.mjs';
|
|
3
|
+
import { createContext, useMemo, useEffect, useRef, useContext, useSyncExternalStore, useCallback, useState, useId, useLayoutEffect } from 'react';
|
|
4
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
|
|
7
7
|
var RuntimeContext = createContext(null);
|
|
@@ -68,6 +68,9 @@ function useAppilotsChat(options = {}) {
|
|
|
68
68
|
const cancelMessage = useCallback(() => chat.cancelMessage(), [chat]);
|
|
69
69
|
const clearMessages = useCallback(() => chat.clearMessages(), [chat]);
|
|
70
70
|
const clearError = useCallback(() => chat.clearError(), [chat]);
|
|
71
|
+
const pauseMission = useCallback(() => chat.pauseMission(), [chat]);
|
|
72
|
+
const resumeMission = useCallback(() => chat.resumeMission(), [chat]);
|
|
73
|
+
const cancelMission = useCallback(() => chat.cancelMission(), [chat]);
|
|
71
74
|
return {
|
|
72
75
|
messages: state.messages,
|
|
73
76
|
isLoading: state.isLoading,
|
|
@@ -75,6 +78,10 @@ function useAppilotsChat(options = {}) {
|
|
|
75
78
|
error: state.error,
|
|
76
79
|
pendingActions: state.pendingActions,
|
|
77
80
|
escalation: state.escalation,
|
|
81
|
+
mission: state.mission ?? null,
|
|
82
|
+
pauseMission,
|
|
83
|
+
resumeMission,
|
|
84
|
+
cancelMission,
|
|
78
85
|
requestHuman,
|
|
79
86
|
sendMessage,
|
|
80
87
|
cancelMessage,
|
|
@@ -101,6 +108,78 @@ function useAppilotsActions() {
|
|
|
101
108
|
rejectAction
|
|
102
109
|
};
|
|
103
110
|
}
|
|
111
|
+
function useActionStatus(locale) {
|
|
112
|
+
const { actions } = useAppilotsRuntime();
|
|
113
|
+
const state = useSyncExternalStore(actions.subscribeState, actions.getState, actions.getState);
|
|
114
|
+
const current = state.actions.find((a) => a.status === "executing" && a.type !== "confirm");
|
|
115
|
+
return current ? describeAction(current, locale).label : "";
|
|
116
|
+
}
|
|
117
|
+
function useVoiceInput({ host, locale, onText }) {
|
|
118
|
+
const [state, setState] = useState("idle");
|
|
119
|
+
const run = useRef(0);
|
|
120
|
+
const onTextRef = useRef(onText);
|
|
121
|
+
onTextRef.current = onText;
|
|
122
|
+
const stateRef = useRef(state);
|
|
123
|
+
stateRef.current = state;
|
|
124
|
+
const cancel = useCallback(() => {
|
|
125
|
+
if (stateRef.current === "idle") return;
|
|
126
|
+
run.current++;
|
|
127
|
+
setState("idle");
|
|
128
|
+
void host?.cancel().catch((err) => appilotsDebugWarn("useVoiceInput: cancel failed", err));
|
|
129
|
+
}, [host]);
|
|
130
|
+
const toggle = useCallback(() => {
|
|
131
|
+
if (!host) return;
|
|
132
|
+
const id = ++run.current;
|
|
133
|
+
const current = stateRef.current;
|
|
134
|
+
if (current === "idle" || current === "error") {
|
|
135
|
+
setState("starting");
|
|
136
|
+
host.start({ locale }).then(
|
|
137
|
+
() => id === run.current && setState("listening"),
|
|
138
|
+
(err) => {
|
|
139
|
+
appilotsDebugWarn("useVoiceInput: start failed", err);
|
|
140
|
+
if (id === run.current) setState("error");
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
} else if (current === "starting" || current === "listening") {
|
|
144
|
+
setState("stopping");
|
|
145
|
+
host.stop().then(
|
|
146
|
+
(text) => {
|
|
147
|
+
if (id !== run.current) return;
|
|
148
|
+
onTextRef.current(text ?? "");
|
|
149
|
+
setState("idle");
|
|
150
|
+
},
|
|
151
|
+
(err) => {
|
|
152
|
+
appilotsDebugWarn("useVoiceInput: stop failed", err);
|
|
153
|
+
if (id === run.current) setState("error");
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}, [host, locale]);
|
|
158
|
+
const active = state === "starting" || state === "listening";
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!host || !active) return;
|
|
161
|
+
const id = run.current;
|
|
162
|
+
const offPartial = host.onPartial?.((text) => id === run.current && onTextRef.current(text));
|
|
163
|
+
const offEnd = host.onEnd?.((error) => {
|
|
164
|
+
if (id !== run.current) return;
|
|
165
|
+
run.current++;
|
|
166
|
+
setState(error ? "error" : "idle");
|
|
167
|
+
});
|
|
168
|
+
return () => {
|
|
169
|
+
offPartial?.();
|
|
170
|
+
offEnd?.();
|
|
171
|
+
};
|
|
172
|
+
}, [host, active]);
|
|
173
|
+
useEffect(
|
|
174
|
+
() => () => {
|
|
175
|
+
const s = stateRef.current;
|
|
176
|
+
if (s === "starting" || s === "listening") void host?.cancel().catch(() => {
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
[host]
|
|
180
|
+
);
|
|
181
|
+
return { state, toggle, cancel };
|
|
182
|
+
}
|
|
104
183
|
|
|
105
184
|
// src/react/confirmCard.ts
|
|
106
185
|
var GENERIC_MESSAGE = "Confirm this action?";
|
|
@@ -116,13 +195,18 @@ function deriveConfirmCardCopy(payload, defaults = {
|
|
|
116
195
|
};
|
|
117
196
|
const actionDescription = str("actionDescription");
|
|
118
197
|
const message = str("message") ?? str("title") ?? (actionDescription ? defaults.describe(actionDescription) : void 0) ?? defaults.message;
|
|
198
|
+
const confirmLabel = str("confirmLabel") ?? defaults.confirmLabel;
|
|
199
|
+
const cancelLabel = str("cancelLabel") ?? defaults.cancelLabel;
|
|
200
|
+
const name = (label) => actionDescription ? `${label}: ${actionDescription}` : label;
|
|
119
201
|
return {
|
|
120
202
|
message,
|
|
121
|
-
confirmLabel
|
|
122
|
-
cancelLabel
|
|
203
|
+
confirmLabel,
|
|
204
|
+
cancelLabel,
|
|
205
|
+
confirmName: name(confirmLabel),
|
|
206
|
+
cancelName: name(cancelLabel)
|
|
123
207
|
};
|
|
124
208
|
}
|
|
125
|
-
function inline(text) {
|
|
209
|
+
function inline(text, newTab) {
|
|
126
210
|
return parseInlineMarkdown(text).map((segment, index) => {
|
|
127
211
|
switch (segment.kind) {
|
|
128
212
|
case "bold":
|
|
@@ -132,28 +216,38 @@ function inline(text) {
|
|
|
132
216
|
case "code":
|
|
133
217
|
return /* @__PURE__ */ jsx("code", { children: segment.value }, index);
|
|
134
218
|
case "link":
|
|
135
|
-
return /* @__PURE__ */
|
|
219
|
+
return /* @__PURE__ */ jsxs("a", { href: segment.href, target: "_blank", rel: "noopener noreferrer", children: [
|
|
220
|
+
segment.value,
|
|
221
|
+
/* @__PURE__ */ jsxs("span", { className: "ap-sr-only", children: [
|
|
222
|
+
" ",
|
|
223
|
+
newTab
|
|
224
|
+
] })
|
|
225
|
+
] }, index);
|
|
136
226
|
default:
|
|
137
227
|
return segment.value;
|
|
138
228
|
}
|
|
139
229
|
});
|
|
140
230
|
}
|
|
141
|
-
function ChatMarkdown({
|
|
231
|
+
function ChatMarkdown({
|
|
232
|
+
content,
|
|
233
|
+
hiddenValue,
|
|
234
|
+
newTab
|
|
235
|
+
}) {
|
|
142
236
|
return /* @__PURE__ */ jsx("div", { className: "ap-markdown", children: parseMarkdownBlocks(content.replaceAll("<redacted>", hiddenValue)).map((block, index) => {
|
|
143
237
|
switch (block.kind) {
|
|
144
238
|
case "heading": {
|
|
145
|
-
const Heading = `h${block.level}`;
|
|
146
|
-
return /* @__PURE__ */ jsx(Heading, { children: inline(block.text) }, index);
|
|
239
|
+
const Heading = `h${block.level + 2}`;
|
|
240
|
+
return /* @__PURE__ */ jsx(Heading, { children: inline(block.text, newTab) }, index);
|
|
147
241
|
}
|
|
148
242
|
case "codeblock":
|
|
149
243
|
return /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: block.code }) }, index);
|
|
150
244
|
case "bullet":
|
|
151
245
|
case "ordered": {
|
|
152
246
|
const List = block.kind === "bullet" ? "ul" : "ol";
|
|
153
|
-
return /* @__PURE__ */ jsx(List, { children: block.items.map((item, i) => /* @__PURE__ */ jsx("li", { children: inline(item) }, i)) }, index);
|
|
247
|
+
return /* @__PURE__ */ jsx(List, { children: block.items.map((item, i) => /* @__PURE__ */ jsx("li", { children: inline(item, newTab) }, i)) }, index);
|
|
154
248
|
}
|
|
155
249
|
default:
|
|
156
|
-
return /* @__PURE__ */ jsx("p", { children: inline(block.text) }, index);
|
|
250
|
+
return /* @__PURE__ */ jsx("p", { children: inline(block.text, newTab) }, index);
|
|
157
251
|
}
|
|
158
252
|
}) });
|
|
159
253
|
}
|
|
@@ -182,17 +276,31 @@ var chatStrings = {
|
|
|
182
276
|
stop: "Stop response",
|
|
183
277
|
inputHint: "Enter to send \xB7 Shift + Enter for a new line",
|
|
184
278
|
latest: "Latest messages",
|
|
279
|
+
messages: "Messages",
|
|
280
|
+
you: "You",
|
|
281
|
+
newTab: "(opens in a new tab)",
|
|
185
282
|
thinking: "Thinking\u2026",
|
|
186
283
|
statusAnalyzing: "Analyzing\u2026",
|
|
187
284
|
statusWaitingApp: "Waiting for the app\u2026",
|
|
188
285
|
statusAdjusting: "Adjusting\u2026",
|
|
286
|
+
stillWorking: (seconds) => `Still working\u2026 ${seconds} s`,
|
|
189
287
|
errorPrefix: "Something went wrong",
|
|
190
288
|
dismiss: "Dismiss error",
|
|
191
289
|
approval: "Your approval is needed",
|
|
192
290
|
confirm: "Confirm",
|
|
193
291
|
cancel: "Cancel",
|
|
194
292
|
confirmMessage: "Confirm this action?",
|
|
195
|
-
confirmDescription: (label) => `Confirm "${label}"
|
|
293
|
+
confirmDescription: (label) => `Confirm "${label}"?`,
|
|
294
|
+
confirmExpired: "This confirmation expired. Ask the assistant again to continue.",
|
|
295
|
+
missionControls: "Task controls",
|
|
296
|
+
missionPause: "Pause",
|
|
297
|
+
missionResume: "Resume task",
|
|
298
|
+
missionCancel: "Cancel task",
|
|
299
|
+
missionPaused: "Paused",
|
|
300
|
+
voice: "Dictate by voice",
|
|
301
|
+
voiceListening: "Listening. Press the microphone again to stop.",
|
|
302
|
+
voiceStopped: "Stopped listening. Review the text before sending.",
|
|
303
|
+
voiceError: "Could not use the microphone."
|
|
196
304
|
},
|
|
197
305
|
"pt-BR": {
|
|
198
306
|
openChat: "Abrir chat",
|
|
@@ -216,88 +324,118 @@ var chatStrings = {
|
|
|
216
324
|
stop: "Parar resposta",
|
|
217
325
|
inputHint: "Enter para enviar \xB7 Shift + Enter para nova linha",
|
|
218
326
|
latest: "Mensagens recentes",
|
|
327
|
+
messages: "Mensagens",
|
|
328
|
+
you: "Voc\xEA",
|
|
329
|
+
newTab: "(abre em nova aba)",
|
|
219
330
|
thinking: "Pensando\u2026",
|
|
220
331
|
statusAnalyzing: "Analisando\u2026",
|
|
221
332
|
statusWaitingApp: "Aguardando o aplicativo\u2026",
|
|
222
333
|
statusAdjusting: "Ajustando\u2026",
|
|
334
|
+
stillWorking: (seconds) => `Ainda trabalhando\u2026 ${seconds} s`,
|
|
223
335
|
errorPrefix: "Algo deu errado",
|
|
224
336
|
dismiss: "Fechar erro",
|
|
225
337
|
approval: "Precisamos da sua confirma\xE7\xE3o",
|
|
226
338
|
confirm: "Confirmar",
|
|
227
339
|
cancel: "Cancelar",
|
|
228
340
|
confirmMessage: "Confirma esta a\xE7\xE3o?",
|
|
229
|
-
confirmDescription: (label) => `Confirma "${label}"
|
|
341
|
+
confirmDescription: (label) => `Confirma "${label}"?`,
|
|
342
|
+
confirmExpired: "Esta confirma\xE7\xE3o expirou. Pe\xE7a de novo ao assistente para continuar.",
|
|
343
|
+
missionControls: "Controles da tarefa",
|
|
344
|
+
missionPause: "Pausar",
|
|
345
|
+
missionResume: "Retomar tarefa",
|
|
346
|
+
missionCancel: "Cancelar tarefa",
|
|
347
|
+
missionPaused: "Pausada",
|
|
348
|
+
voice: "Ditar por voz",
|
|
349
|
+
voiceListening: "Ouvindo. Pressione o microfone de novo para parar.",
|
|
350
|
+
voiceStopped: "Parou de ouvir. Revise o texto antes de enviar.",
|
|
351
|
+
voiceError: "N\xE3o foi poss\xEDvel usar o microfone."
|
|
230
352
|
}
|
|
231
353
|
};
|
|
232
354
|
|
|
233
355
|
// src/react/chatStyles.ts
|
|
234
356
|
var chatStyles = `
|
|
235
|
-
.appilots-chat { --ap-accent: #2563eb; display: flex; flex-direction: column; height: 100%; min-height:
|
|
357
|
+
.appilots-chat { --ap-accent: #2563eb; display: flex; flex-direction: column; height: 100%; min-height: 17.5rem; min-width: 0; color: #172033; background: #f6f8fb; border: 1px solid #e2e7ef; border-radius: 20px; overflow: hidden auto; font: 0.875rem/1.5 system-ui, -apple-system, sans-serif; box-shadow: 0 8px 32px #17203308; }
|
|
358
|
+
/* At large text sizes the header and footer outgrow the panel: they keep their size and the panel scrolls, instead of shrinking under their own text. */
|
|
359
|
+
.appilots-chat > * { flex-shrink: 0; }
|
|
236
360
|
.appilots-chat *, .appilots-chat *::before, .appilots-chat *::after { box-sizing: border-box; }
|
|
237
|
-
.appilots-chat button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; margin: 0; padding: 8px 12px; border: 1px solid #dde3ed; border-radius: 10px; background: #fff; color: inherit; cursor: pointer; font: inherit; line-height: 1.3; }
|
|
361
|
+
.appilots-chat button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px; margin: 0; padding: 8px 12px; border: 1px solid #dde3ed; border-radius: 10px; background: #fff; color: inherit; cursor: pointer; font: inherit; line-height: 1.3; }
|
|
238
362
|
.appilots-chat button:hover:not(:disabled) { background: #edf2f9; }
|
|
239
363
|
.appilots-chat button:disabled { opacity: .5; cursor: not-allowed; }
|
|
240
364
|
.appilots-chat button:focus-visible, .appilots-chat a:focus-visible, .appilots-chat textarea:focus-visible { outline: 2px solid var(--ap-accent); outline-offset: 3px; }
|
|
241
|
-
.appilots-chat .ap-header { display: flex; align-items: center; gap: 10px; padding: 18px; background: #fff; border-bottom: 1px solid #e8ecf2; }
|
|
365
|
+
.appilots-chat .ap-header { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 18px; background: #fff; border-bottom: 1px solid #e8ecf2; }
|
|
242
366
|
.appilots-chat .ap-avatar { display: grid; place-items: center; flex-shrink: 0; width: 38px; height: 38px; border-radius: 13px; background: #eaf0ff; color: var(--ap-accent); }
|
|
243
|
-
.appilots-chat .ap-identity { flex: 1; min-width: 0; }
|
|
244
|
-
.appilots-chat .ap-title { display: block; font-size:
|
|
245
|
-
.appilots-chat .ap-caption { font-size:
|
|
246
|
-
.appilots-chat .ap-icon { padding: 8px; width:
|
|
247
|
-
.appilots-chat .ap-human-button { border: 0; border-radius: 0; border-bottom: 1px solid #e8ecf2; padding: 10px 16px; color: #52647e; font-size:
|
|
248
|
-
.appilots-chat .ap-escalation { padding: 12px 18px; color: #16604b; background: #eaf7f1; font-size:
|
|
249
|
-
.appilots-chat .ap-log { flex: 1; min-height:
|
|
367
|
+
.appilots-chat .ap-identity { flex: 1 1 5rem; min-width: 0; }
|
|
368
|
+
.appilots-chat .ap-title { display: block; margin: 0; font-size: 0.9375rem; font-weight: 700; line-height: 1.4; overflow-wrap: anywhere; }
|
|
369
|
+
.appilots-chat .ap-caption { font-size: 0.75rem; color: #5f6b7d; }
|
|
370
|
+
.appilots-chat .ap-icon { padding: 8px; min-width: 44px; min-height: 44px; border: 0; background: transparent; flex-shrink: 0; }
|
|
371
|
+
.appilots-chat .ap-human-button { border: 0; border-radius: 0; border-bottom: 1px solid #e8ecf2; padding: 10px 16px; color: #52647e; font-size: 0.75rem; }
|
|
372
|
+
.appilots-chat .ap-escalation { padding: 12px 18px; color: #16604b; background: #eaf7f1; font-size: 0.75rem; border-bottom: 1px solid #d5ede2; }
|
|
373
|
+
.appilots-chat .ap-log { flex: 1; min-height: 6rem; overflow-y: auto; overscroll-behavior: contain; padding: 22px 18px; display: flex; flex-direction: column; gap: 20px; scrollbar-width: thin; }
|
|
250
374
|
.appilots-chat .ap-empty { margin: auto 0; padding: 24px 4px; text-align: center; }
|
|
251
375
|
.appilots-chat .ap-empty .ap-avatar { margin: 0 auto 18px; width: 52px; height: 52px; border-radius: 18px; }
|
|
252
|
-
.appilots-chat .ap-empty
|
|
253
|
-
.appilots-chat .ap-empty p { margin: 0 auto 24px; max-width:
|
|
376
|
+
.appilots-chat .ap-empty h3 { margin: 0 0 8px; font-size: 1.3125rem; font-weight: 650; letter-spacing: -.4px; }
|
|
377
|
+
.appilots-chat .ap-empty p { margin: 0 auto 24px; max-width: 16.25rem; color: #5f6b7d; }
|
|
254
378
|
.appilots-chat .ap-suggestions { display: flex; flex-direction: column; align-items: stretch; gap: 8px; }
|
|
255
|
-
.appilots-chat .ap-suggestions button { padding: 12px 14px; text-align: left; justify-content: space-between; font-size:
|
|
379
|
+
.appilots-chat .ap-suggestions button { padding: 12px 14px; text-align: left; justify-content: space-between; font-size: 0.8125rem; }
|
|
256
380
|
.appilots-chat .ap-message { max-width: 94%; align-self: flex-start; min-width: 0; }
|
|
257
381
|
.appilots-chat .ap-message-user { align-self: flex-end; max-width: 86%; }
|
|
258
|
-
.appilots-chat .ap-sender { display: block; margin: 0 0 5px 3px; color: #
|
|
382
|
+
.appilots-chat .ap-sender { display: block; margin: 0 0 5px 3px; color: #5f6b7d; font-size: 0.6875rem; font-weight: 600; }
|
|
259
383
|
.appilots-chat .ap-bubble { padding: 12px 15px; background: #fff; border: 1px solid #e7ebf2; border-radius: 4px 17px 17px; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
260
384
|
.appilots-chat .ap-message-user .ap-bubble { background: var(--ap-accent); color: #fff; border-color: transparent; border-radius: 17px 4px 17px 17px; }
|
|
261
385
|
.appilots-chat .ap-message-human_agent .ap-bubble { border-color: #cde7dc; }
|
|
262
|
-
.appilots-chat .ap-message time { display: block; margin: 5px 3px 0; color: #
|
|
386
|
+
.appilots-chat .ap-message time { display: block; margin: 5px 3px 0; color: #5f6b7d; font-size: 0.625rem; }
|
|
263
387
|
.appilots-chat .ap-message-user time { text-align: right; }
|
|
264
|
-
.appilots-chat .ap-system { align-self: center; text-align: center; color: #
|
|
388
|
+
.appilots-chat .ap-system { align-self: center; text-align: center; color: #5f6b7d; font-size: 0.75rem; white-space: pre-wrap; }
|
|
265
389
|
.appilots-chat .ap-system button { display: flex; margin: 8px auto 0; }
|
|
266
390
|
.appilots-chat .ap-markdown { white-space: normal; }
|
|
267
391
|
.appilots-chat .ap-markdown p { margin: 0 0 10px; white-space: pre-wrap; }
|
|
268
|
-
.appilots-chat .ap-markdown
|
|
392
|
+
.appilots-chat .ap-markdown h3, .appilots-chat .ap-markdown h4, .appilots-chat .ap-markdown h5 { font-size: 1rem; line-height: 1.4; margin: 12px 0 8px; }
|
|
269
393
|
.appilots-chat .ap-markdown ul, .appilots-chat .ap-markdown ol { margin: 8px 0; padding-left: 22px; }
|
|
270
394
|
.appilots-chat .ap-markdown li { margin: 4px 0; white-space: pre-wrap; }
|
|
271
|
-
.appilots-chat .ap-markdown code { font:
|
|
395
|
+
.appilots-chat .ap-markdown code { font: 0.75rem/1.6 ui-monospace, monospace; background: #edf1f7; border-radius: 4px; padding: 2px 4px; }
|
|
272
396
|
.appilots-chat .ap-markdown pre { margin: 10px 0; padding: 12px; background: #edf1f7; border-radius: 8px; overflow-x: auto; white-space: pre; }
|
|
273
397
|
.appilots-chat .ap-markdown pre code { padding: 0; }
|
|
274
398
|
.appilots-chat .ap-markdown a { color: #1d4ed8; text-decoration: underline; }
|
|
275
399
|
.appilots-chat .ap-markdown > :first-child { margin-top: 0; }
|
|
276
400
|
.appilots-chat .ap-markdown > :last-child { margin-bottom: 0; }
|
|
277
401
|
.appilots-chat .ap-confirm { border: 1px solid #eddbb4; background: #fffdf7; border-radius: 14px; padding: 14px; }
|
|
278
|
-
.appilots-chat .ap-confirm-title { color: #8c641d; font-size:
|
|
402
|
+
.appilots-chat .ap-confirm-title { color: #8c641d; font-size: 0.6875rem; font-weight: 600; margin-bottom: 6px; }
|
|
403
|
+
.appilots-chat .ap-confirm-expired { margin: 10px 0 0; color: #9a3412; font-size: 0.8125rem; font-weight: 600; }
|
|
404
|
+
.appilots-chat .ap-confirm-actions button { min-height: 44px; min-width: 44px; }
|
|
279
405
|
.appilots-chat .ap-confirm-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
|
280
406
|
.appilots-chat .ap-primary, .appilots-chat .ap-primary:hover:not(:disabled) { background: var(--ap-accent); border-color: transparent; color: #fff; }
|
|
281
|
-
.appilots-chat .ap-
|
|
407
|
+
.appilots-chat .ap-sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
408
|
+
.appilots-chat .ap-loading { display: flex; align-items: center; gap: 8px; color: #5f6b7d; font-size: 0.75rem; }
|
|
282
409
|
.appilots-chat .ap-loading::before { content: ''; width: 6px; height: 6px; background: var(--ap-accent); border-radius: 50%; }
|
|
283
|
-
.appilots-chat .ap-
|
|
284
|
-
.appilots-chat .ap-
|
|
410
|
+
.appilots-chat .ap-mission { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 14px 8px; }
|
|
411
|
+
.appilots-chat .ap-mission button { font-size: 0.75rem; min-height: 44px; }
|
|
412
|
+
.appilots-chat .ap-error { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 14px; margin: 0 14px 8px; background: #fef2f2; color: #b42318; border-radius: 10px; font-size: 0.75rem; }
|
|
413
|
+
.appilots-chat .ap-latest { align-self: center; font-size: 0.75rem; margin: 0 0 8px; }
|
|
285
414
|
.appilots-chat .ap-footer { background: #fff; border-top: 1px solid #e8ecf2; padding: 14px; }
|
|
286
|
-
.appilots-chat .ap-composer { display: flex; align-items: flex-end; gap: 8px; padding: 8px; border: 1px solid #
|
|
415
|
+
.appilots-chat .ap-composer { display: flex; align-items: flex-end; gap: 8px; padding: 8px; border: 1px solid #838ea0; border-radius: 16px; background: #fff; }
|
|
287
416
|
.appilots-chat .ap-composer:focus-within { border-color: var(--ap-accent); box-shadow: 0 0 0 2px #2563eb0b; }
|
|
288
|
-
.appilots-chat textarea { display: block; flex: 1; min-width: 0; min-height:
|
|
417
|
+
.appilots-chat textarea { display: block; flex: 1; min-width: 0; min-height: 2.5rem; max-height: 7.5rem; margin: 0; padding: 9px 4px; border: 0; border-radius: 4px; background: transparent; color: inherit; font: inherit; line-height: 1.375rem; resize: none; }
|
|
289
418
|
.appilots-chat textarea:focus-visible { outline: none; }
|
|
290
|
-
.appilots-chat .ap-send { width:
|
|
291
|
-
.appilots-chat .ap-hint { display: block; text-align: center; font-size:
|
|
419
|
+
.appilots-chat .ap-send { min-width: 44px; min-height: 44px; padding: 0; border-radius: 12px; flex-shrink: 0; }
|
|
420
|
+
.appilots-chat .ap-hint { display: block; text-align: center; font-size: 0.625rem; color: #5f6b7d; padding-top: 8px; }
|
|
421
|
+
@media (prefers-reduced-motion: reduce) {
|
|
422
|
+
.appilots-chat *, .appilots-chat *::before, .appilots-chat *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
|
|
423
|
+
}
|
|
424
|
+
.appilots-chat .ap-log:focus-visible { outline: 2px solid var(--ap-accent); outline-offset: -2px; }
|
|
425
|
+
.appilots-chat .ap-mic[aria-pressed="true"] { color: #fff; background: #b42318; border-radius: 12px; }
|
|
426
|
+
.appilots-chat .ap-mic[aria-pressed="true"]:hover:not(:disabled) { background: #9a1c13; }
|
|
292
427
|
`;
|
|
293
|
-
function Icon({
|
|
428
|
+
function Icon({
|
|
429
|
+
name
|
|
430
|
+
}) {
|
|
294
431
|
const paths = {
|
|
295
432
|
chat: "M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6A8.4 8.4 0 0 1 12.5 3H13a8.5 8.5 0 0 1 8 8v.5Z",
|
|
296
433
|
human: "M3 14v-3a9 9 0 0 1 18 0v3M3 12h3v8H3zM18 12h3v8h-3zM18 20c0 2-3 2-6 2",
|
|
297
434
|
new: "M12 5v14M5 12h14",
|
|
298
435
|
send: "M12 19V5M5 12l7-7 7 7",
|
|
299
436
|
stop: "M6 6h12v12H6z",
|
|
300
|
-
close: "M6 6l12 12M18 6 6 18"
|
|
437
|
+
close: "M6 6l12 12M18 6 6 18",
|
|
438
|
+
mic: "M12 3a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V6a3 3 0 0 0-3-3ZM5 11a7 7 0 0 0 14 0M12 18v3"
|
|
301
439
|
};
|
|
302
440
|
return /* @__PURE__ */ jsx(
|
|
303
441
|
"svg",
|
|
@@ -315,13 +453,24 @@ function Icon({ name }) {
|
|
|
315
453
|
}
|
|
316
454
|
);
|
|
317
455
|
}
|
|
456
|
+
function useStillWorkingSeconds(active) {
|
|
457
|
+
const [seconds, setSeconds] = useState(0);
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
setSeconds(0);
|
|
460
|
+
if (!active) return;
|
|
461
|
+
return startStillWorkingTimer(setSeconds);
|
|
462
|
+
}, [active]);
|
|
463
|
+
return seconds;
|
|
464
|
+
}
|
|
318
465
|
function AppilotsChat({
|
|
319
466
|
title,
|
|
320
467
|
placeholder,
|
|
321
468
|
locale = "en",
|
|
322
469
|
suggestedPrompts = [],
|
|
323
470
|
style,
|
|
324
|
-
onClose
|
|
471
|
+
onClose,
|
|
472
|
+
speech,
|
|
473
|
+
hidden
|
|
325
474
|
}) {
|
|
326
475
|
const strings = chatStrings[locale];
|
|
327
476
|
const chatTitle = title ?? strings.title;
|
|
@@ -331,6 +480,10 @@ function AppilotsChat({
|
|
|
331
480
|
loadingStatusKey,
|
|
332
481
|
error,
|
|
333
482
|
escalation,
|
|
483
|
+
mission,
|
|
484
|
+
pauseMission,
|
|
485
|
+
resumeMission,
|
|
486
|
+
cancelMission,
|
|
334
487
|
sendMessage,
|
|
335
488
|
cancelMessage,
|
|
336
489
|
clearMessages,
|
|
@@ -352,7 +505,45 @@ function AppilotsChat({
|
|
|
352
505
|
const inputRef = useRef(null);
|
|
353
506
|
const logRef = useRef(null);
|
|
354
507
|
const followMessages = useRef(true);
|
|
508
|
+
const hintId = useId();
|
|
355
509
|
const confirms = pendingActions.filter((action) => action.type === "confirm");
|
|
510
|
+
const actionStatus = useActionStatus(locale);
|
|
511
|
+
const working = isLoading && confirms.length === 0;
|
|
512
|
+
const stillWorkingSeconds = useStillWorkingSeconds(working);
|
|
513
|
+
const statusText = stillWorkingSeconds ? strings.stillWorking(stillWorkingSeconds) : strings[loadingStatusKey];
|
|
514
|
+
const activeMission = mission && !["completed", "cancelled"].includes(mission.status) ? mission : null;
|
|
515
|
+
const streaming = isLoading && !!messages.at(-1)?.id.startsWith("streaming_");
|
|
516
|
+
const focusInput = () => inputRef.current?.focus();
|
|
517
|
+
const [speechHost, setSpeechHost] = useState(null);
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
setSpeechHost(
|
|
520
|
+
speech === "browser" ? createWebSpeechHost() : isSpeechHost(speech) ? speech : null
|
|
521
|
+
);
|
|
522
|
+
}, [speech]);
|
|
523
|
+
const draftBeforeVoice = useRef("");
|
|
524
|
+
const voice = useVoiceInput({
|
|
525
|
+
host: speechHost,
|
|
526
|
+
locale,
|
|
527
|
+
onText: (text) => setDraft([draftBeforeVoice.current.trimEnd(), text.trim()].filter(Boolean).join(" "))
|
|
528
|
+
});
|
|
529
|
+
const capturing = voice.state === "starting" || voice.state === "listening";
|
|
530
|
+
const { cancel: cancelCapture } = voice;
|
|
531
|
+
useEffect(() => {
|
|
532
|
+
if (hidden) cancelCapture();
|
|
533
|
+
}, [hidden, cancelCapture]);
|
|
534
|
+
const heardOnce = useRef(false);
|
|
535
|
+
if (voice.state === "listening") heardOnce.current = true;
|
|
536
|
+
const voiceStatus = voice.state === "listening" ? strings.voiceListening : voice.state === "error" ? strings.voiceError : voice.state === "idle" && heardOnce.current ? strings.voiceStopped : "";
|
|
537
|
+
const toggleVoice = () => {
|
|
538
|
+
if (!capturing) draftBeforeVoice.current = draft;
|
|
539
|
+
voice.toggle();
|
|
540
|
+
};
|
|
541
|
+
const cancelVoice = () => {
|
|
542
|
+
if (!capturing) return false;
|
|
543
|
+
voice.cancel();
|
|
544
|
+
setDraft(draftBeforeVoice.current);
|
|
545
|
+
return true;
|
|
546
|
+
};
|
|
356
547
|
const scrollToLatest = () => {
|
|
357
548
|
followMessages.current = true;
|
|
358
549
|
if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
|
|
@@ -367,11 +558,12 @@ function AppilotsChat({
|
|
|
367
558
|
const input = inputRef.current;
|
|
368
559
|
if (!input) return;
|
|
369
560
|
input.style.height = "auto";
|
|
370
|
-
input.style.height = `${
|
|
561
|
+
input.style.height = `${input.scrollHeight}px`;
|
|
371
562
|
}, [draft]);
|
|
372
563
|
const send = () => {
|
|
373
564
|
const content = draft.trim();
|
|
374
|
-
if (!content || isLoading || requestingHuman) return;
|
|
565
|
+
if (!content || isLoading || requestingHuman || activeMission) return;
|
|
566
|
+
if (voice.state !== "idle" && voice.state !== "error") voice.cancel();
|
|
375
567
|
scrollToLatest();
|
|
376
568
|
clearError();
|
|
377
569
|
setDraft("");
|
|
@@ -387,6 +579,7 @@ function AppilotsChat({
|
|
|
387
579
|
await requestHuman(reason);
|
|
388
580
|
} finally {
|
|
389
581
|
setRequestingHuman(false);
|
|
582
|
+
focusInput();
|
|
390
583
|
}
|
|
391
584
|
};
|
|
392
585
|
return (
|
|
@@ -404,7 +597,7 @@ function AppilotsChat({
|
|
|
404
597
|
/* @__PURE__ */ jsxs("header", { className: "ap-header", children: [
|
|
405
598
|
/* @__PURE__ */ jsx("div", { className: "ap-avatar", children: /* @__PURE__ */ jsx(Icon, { name: escalation ? "human" : "chat" }) }),
|
|
406
599
|
/* @__PURE__ */ jsxs("div", { className: "ap-identity", children: [
|
|
407
|
-
/* @__PURE__ */ jsx("
|
|
600
|
+
/* @__PURE__ */ jsx("h2", { className: "ap-title", children: chatTitle }),
|
|
408
601
|
/* @__PURE__ */ jsx("span", { className: "ap-caption", children: escalation?.operatorName ?? (escalation ? strings.humanLabel : strings.subtitle) })
|
|
409
602
|
] }),
|
|
410
603
|
/* @__PURE__ */ jsx(
|
|
@@ -417,6 +610,7 @@ function AppilotsChat({
|
|
|
417
610
|
disabled: requestingHuman || !messages.length && !escalation && !error,
|
|
418
611
|
onClick: () => {
|
|
419
612
|
clearMessages();
|
|
613
|
+
voice.cancel();
|
|
420
614
|
setDraft("");
|
|
421
615
|
scrollToLatest();
|
|
422
616
|
inputRef.current?.focus();
|
|
@@ -461,8 +655,10 @@ function AppilotsChat({
|
|
|
461
655
|
className: "ap-log",
|
|
462
656
|
ref: logRef,
|
|
463
657
|
role: "log",
|
|
464
|
-
"aria-label":
|
|
465
|
-
"aria-live": "polite",
|
|
658
|
+
"aria-label": strings.messages,
|
|
659
|
+
"aria-live": streaming ? "off" : "polite",
|
|
660
|
+
"aria-busy": streaming,
|
|
661
|
+
tabIndex: 0,
|
|
466
662
|
"data-testid": "appilots-chat-log",
|
|
467
663
|
onScroll: () => {
|
|
468
664
|
const log = logRef.current;
|
|
@@ -473,7 +669,7 @@ function AppilotsChat({
|
|
|
473
669
|
children: [
|
|
474
670
|
messages.length === 0 && /* @__PURE__ */ jsxs("div", { className: "ap-empty", children: [
|
|
475
671
|
/* @__PURE__ */ jsx("div", { className: "ap-avatar", children: /* @__PURE__ */ jsx(Icon, { name: "chat" }) }),
|
|
476
|
-
/* @__PURE__ */ jsx("
|
|
672
|
+
/* @__PURE__ */ jsx("h3", { children: strings.welcome }),
|
|
477
673
|
/* @__PURE__ */ jsx("p", { children: strings.welcomeBody }),
|
|
478
674
|
/* @__PURE__ */ jsx("div", { className: "ap-suggestions", children: suggestedPrompts.map((prompt) => /* @__PURE__ */ jsxs(
|
|
479
675
|
"button",
|
|
@@ -512,8 +708,18 @@ function AppilotsChat({
|
|
|
512
708
|
className: `ap-message ap-message-${message.role}`,
|
|
513
709
|
"data-testid": `appilots-message-${message.role}`,
|
|
514
710
|
children: [
|
|
515
|
-
message.role
|
|
516
|
-
|
|
711
|
+
message.role === "user" ? /* @__PURE__ */ jsxs("span", { className: "ap-sr-only", children: [
|
|
712
|
+
strings.you,
|
|
713
|
+
": "
|
|
714
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "ap-sender", children: message.role === "human_agent" ? escalation?.operatorName ?? strings.humanLabel : chatTitle }),
|
|
715
|
+
/* @__PURE__ */ jsx("div", { className: "ap-bubble", children: message.role === "user" ? message.content : /* @__PURE__ */ jsx(
|
|
716
|
+
ChatMarkdown,
|
|
717
|
+
{
|
|
718
|
+
content: message.content,
|
|
719
|
+
hiddenValue: strings.hiddenValue,
|
|
720
|
+
newTab: strings.newTab
|
|
721
|
+
}
|
|
722
|
+
) }),
|
|
517
723
|
/* @__PURE__ */ jsx("time", { dateTime: new Date(message.timestamp).toISOString(), children: new Date(message.timestamp).toLocaleTimeString(locale, {
|
|
518
724
|
hour: "2-digit",
|
|
519
725
|
minute: "2-digit"
|
|
@@ -523,50 +729,88 @@ function AppilotsChat({
|
|
|
523
729
|
message.id
|
|
524
730
|
)
|
|
525
731
|
),
|
|
526
|
-
confirms.map((action) =>
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
732
|
+
confirms.map((action) => /* @__PURE__ */ jsx(
|
|
733
|
+
ConfirmCard,
|
|
734
|
+
{
|
|
735
|
+
action,
|
|
736
|
+
strings,
|
|
737
|
+
composerRef: inputRef,
|
|
738
|
+
onApprove: () => void approveAction(action.id),
|
|
739
|
+
onReject: () => void rejectAction(action.id)
|
|
740
|
+
},
|
|
741
|
+
action.id
|
|
742
|
+
)),
|
|
743
|
+
(isLoading || activeMission) && // While loading this is visual only: the log is a live region, and a
|
|
744
|
+
// second one nested in it double-speaks. The status below is what
|
|
745
|
+
// readers hear. An idle open task has nothing else speaking.
|
|
746
|
+
/* @__PURE__ */ jsx(
|
|
747
|
+
"div",
|
|
748
|
+
{
|
|
749
|
+
className: "ap-loading",
|
|
750
|
+
"aria-hidden": isLoading ? "true" : void 0,
|
|
751
|
+
role: isLoading ? void 0 : "status",
|
|
752
|
+
"data-testid": isLoading ? "appilots-loading" : "appilots-mission-progress",
|
|
753
|
+
children: activeMission ? `${activeMission.progressText}${activeMission.status === "paused" ? ` \xB7 ${strings.missionPaused}` : ""}` : statusText
|
|
754
|
+
}
|
|
755
|
+
)
|
|
756
|
+
]
|
|
757
|
+
}
|
|
758
|
+
),
|
|
759
|
+
/* @__PURE__ */ jsx("div", { className: "ap-sr-only", role: "status", "data-testid": "appilots-chat-status", children: working ? statusText : "" }),
|
|
760
|
+
/* @__PURE__ */ jsx("div", { className: "ap-sr-only", role: "status", "data-testid": "appilots-action-status", children: actionStatus }),
|
|
761
|
+
showLatest && /* @__PURE__ */ jsxs(
|
|
762
|
+
"button",
|
|
763
|
+
{
|
|
764
|
+
type: "button",
|
|
765
|
+
className: "ap-latest",
|
|
766
|
+
onClick: () => {
|
|
767
|
+
scrollToLatest();
|
|
768
|
+
focusInput();
|
|
769
|
+
},
|
|
770
|
+
children: [
|
|
771
|
+
strings.latest,
|
|
772
|
+
" \u2193"
|
|
773
|
+
]
|
|
774
|
+
}
|
|
775
|
+
),
|
|
776
|
+
activeMission && /* @__PURE__ */ jsxs(
|
|
777
|
+
"div",
|
|
778
|
+
{
|
|
779
|
+
className: "ap-mission",
|
|
780
|
+
role: "group",
|
|
781
|
+
"aria-label": strings.missionControls,
|
|
782
|
+
"data-testid": "appilots-mission-controls",
|
|
783
|
+
children: [
|
|
784
|
+
activeMission.status === "running" ? /* @__PURE__ */ jsx(
|
|
785
|
+
"button",
|
|
786
|
+
{
|
|
787
|
+
type: "button",
|
|
788
|
+
"data-testid": "appilots-mission-pause",
|
|
789
|
+
onClick: () => void pauseMission(),
|
|
790
|
+
children: strings.missionPause
|
|
791
|
+
}
|
|
792
|
+
) : activeMission.status !== "awaiting_approval" ? /* @__PURE__ */ jsx(
|
|
793
|
+
"button",
|
|
794
|
+
{
|
|
795
|
+
type: "button",
|
|
796
|
+
"data-testid": "appilots-mission-resume",
|
|
797
|
+
disabled: isLoading,
|
|
798
|
+
onClick: () => void resumeMission(),
|
|
799
|
+
children: strings.missionResume
|
|
800
|
+
}
|
|
801
|
+
) : null,
|
|
802
|
+
/* @__PURE__ */ jsx(
|
|
803
|
+
"button",
|
|
804
|
+
{
|
|
805
|
+
type: "button",
|
|
806
|
+
"data-testid": "appilots-mission-cancel",
|
|
807
|
+
onClick: () => void cancelMission(),
|
|
808
|
+
children: strings.missionCancel
|
|
809
|
+
}
|
|
810
|
+
)
|
|
563
811
|
]
|
|
564
812
|
}
|
|
565
813
|
),
|
|
566
|
-
showLatest && /* @__PURE__ */ jsxs("button", { type: "button", className: "ap-latest", onClick: scrollToLatest, children: [
|
|
567
|
-
strings.latest,
|
|
568
|
-
" \u2193"
|
|
569
|
-
] }),
|
|
570
814
|
error && /* @__PURE__ */ jsxs("div", { className: "ap-error", role: "alert", children: [
|
|
571
815
|
/* @__PURE__ */ jsx("span", { children: error }),
|
|
572
816
|
/* @__PURE__ */ jsx(
|
|
@@ -575,7 +819,10 @@ function AppilotsChat({
|
|
|
575
819
|
type: "button",
|
|
576
820
|
className: "ap-icon",
|
|
577
821
|
"aria-label": strings.dismiss,
|
|
578
|
-
onClick:
|
|
822
|
+
onClick: () => {
|
|
823
|
+
clearError();
|
|
824
|
+
focusInput();
|
|
825
|
+
},
|
|
579
826
|
children: /* @__PURE__ */ jsx(Icon, { name: "close" })
|
|
580
827
|
}
|
|
581
828
|
)
|
|
@@ -598,9 +845,15 @@ function AppilotsChat({
|
|
|
598
845
|
value: draft,
|
|
599
846
|
placeholder: placeholder ?? strings.placeholder,
|
|
600
847
|
"aria-label": placeholder ?? strings.placeholder,
|
|
848
|
+
"aria-describedby": hintId,
|
|
601
849
|
"data-testid": "appilots-chat-input",
|
|
850
|
+
readOnly: capturing || voice.state === "stopping",
|
|
602
851
|
onChange: (event) => setDraft(event.target.value),
|
|
603
852
|
onKeyDown: (event) => {
|
|
853
|
+
if (event.key === "Escape" && cancelVoice()) {
|
|
854
|
+
event.preventDefault();
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
604
857
|
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing && event.keyCode !== 229) {
|
|
605
858
|
event.preventDefault();
|
|
606
859
|
send();
|
|
@@ -608,6 +861,22 @@ function AppilotsChat({
|
|
|
608
861
|
}
|
|
609
862
|
}
|
|
610
863
|
),
|
|
864
|
+
speechHost && /* @__PURE__ */ jsx(
|
|
865
|
+
"button",
|
|
866
|
+
{
|
|
867
|
+
type: "button",
|
|
868
|
+
className: "ap-send ap-mic",
|
|
869
|
+
"data-testid": "appilots-mic-button",
|
|
870
|
+
"aria-label": strings.voice,
|
|
871
|
+
title: strings.voice,
|
|
872
|
+
"aria-pressed": capturing,
|
|
873
|
+
onClick: toggleVoice,
|
|
874
|
+
onKeyDown: (event) => {
|
|
875
|
+
if (event.key === "Escape" && cancelVoice()) event.preventDefault();
|
|
876
|
+
},
|
|
877
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "mic" })
|
|
878
|
+
}
|
|
879
|
+
),
|
|
611
880
|
isLoading ? /* @__PURE__ */ jsx(
|
|
612
881
|
"button",
|
|
613
882
|
{
|
|
@@ -615,7 +884,11 @@ function AppilotsChat({
|
|
|
615
884
|
className: "ap-send ap-primary",
|
|
616
885
|
"aria-label": strings.stop,
|
|
617
886
|
title: strings.stop,
|
|
618
|
-
|
|
887
|
+
"data-testid": "appilots-chat-stop",
|
|
888
|
+
onClick: () => {
|
|
889
|
+
cancelMessage();
|
|
890
|
+
focusInput();
|
|
891
|
+
},
|
|
619
892
|
children: /* @__PURE__ */ jsx(Icon, { name: "stop" })
|
|
620
893
|
}
|
|
621
894
|
) : /* @__PURE__ */ jsx(
|
|
@@ -626,31 +899,134 @@ function AppilotsChat({
|
|
|
626
899
|
"data-testid": "appilots-chat-send",
|
|
627
900
|
"aria-label": strings.send,
|
|
628
901
|
title: strings.send,
|
|
629
|
-
disabled: !draft.trim() || requestingHuman,
|
|
902
|
+
disabled: !draft.trim() || requestingHuman || !!activeMission,
|
|
630
903
|
children: /* @__PURE__ */ jsx(Icon, { name: "send" })
|
|
631
904
|
}
|
|
632
905
|
)
|
|
633
906
|
]
|
|
634
907
|
}
|
|
635
908
|
),
|
|
636
|
-
/* @__PURE__ */ jsx("span", { className: "ap-hint", children: strings.inputHint })
|
|
909
|
+
/* @__PURE__ */ jsx("span", { className: "ap-hint", id: hintId, children: strings.inputHint }),
|
|
910
|
+
speechHost && /* @__PURE__ */ jsx(
|
|
911
|
+
"span",
|
|
912
|
+
{
|
|
913
|
+
className: voiceStatus ? "ap-hint" : "ap-sr-only",
|
|
914
|
+
role: "status",
|
|
915
|
+
"data-testid": "appilots-voice-status",
|
|
916
|
+
children: voiceStatus
|
|
917
|
+
}
|
|
918
|
+
)
|
|
637
919
|
] })
|
|
638
920
|
]
|
|
639
921
|
}
|
|
640
922
|
)
|
|
641
923
|
);
|
|
642
924
|
}
|
|
925
|
+
function ConfirmCard({
|
|
926
|
+
action,
|
|
927
|
+
strings,
|
|
928
|
+
composerRef,
|
|
929
|
+
onApprove,
|
|
930
|
+
onReject
|
|
931
|
+
}) {
|
|
932
|
+
const id = useId();
|
|
933
|
+
const cardRef = useRef(null);
|
|
934
|
+
const cancelRef = useRef(null);
|
|
935
|
+
const expiresAt = confirmExpiresAt(action);
|
|
936
|
+
const [expired, setExpired] = useState(() => expiresAt !== void 0 && expiresAt <= Date.now());
|
|
937
|
+
const { message, confirmLabel, cancelLabel, confirmName, cancelName } = deriveConfirmCardCopy(
|
|
938
|
+
action.payload ?? {},
|
|
939
|
+
{
|
|
940
|
+
message: strings.confirmMessage,
|
|
941
|
+
confirmLabel: strings.confirm,
|
|
942
|
+
cancelLabel: strings.cancel,
|
|
943
|
+
describe: strings.confirmDescription
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
useLayoutEffect(() => {
|
|
947
|
+
cancelRef.current?.focus();
|
|
948
|
+
const card = cardRef.current;
|
|
949
|
+
return () => {
|
|
950
|
+
if (card?.contains(document.activeElement)) composerRef.current?.focus();
|
|
951
|
+
};
|
|
952
|
+
}, [composerRef]);
|
|
953
|
+
useEffect(() => {
|
|
954
|
+
if (expiresAt === void 0 || expired) return;
|
|
955
|
+
const timer = setTimeout(() => setExpired(true), expiresAt - Date.now());
|
|
956
|
+
return () => clearTimeout(timer);
|
|
957
|
+
}, [expiresAt, expired]);
|
|
958
|
+
return /* @__PURE__ */ jsxs(
|
|
959
|
+
"div",
|
|
960
|
+
{
|
|
961
|
+
ref: cardRef,
|
|
962
|
+
className: "ap-confirm",
|
|
963
|
+
"data-testid": "appilots-confirm",
|
|
964
|
+
role: "alertdialog",
|
|
965
|
+
"aria-labelledby": `${id}-title`,
|
|
966
|
+
"aria-describedby": `${id}-message`,
|
|
967
|
+
onKeyDown: (event) => {
|
|
968
|
+
if (event.key !== "Escape" || event.nativeEvent.isComposing) return;
|
|
969
|
+
event.preventDefault();
|
|
970
|
+
event.stopPropagation();
|
|
971
|
+
onReject();
|
|
972
|
+
},
|
|
973
|
+
children: [
|
|
974
|
+
/* @__PURE__ */ jsx("div", { className: "ap-confirm-title", id: `${id}-title`, children: strings.approval }),
|
|
975
|
+
/* @__PURE__ */ jsx("div", { id: `${id}-message`, children: /* @__PURE__ */ jsx(ChatMarkdown, { content: message, hiddenValue: strings.hiddenValue, newTab: strings.newTab }) }),
|
|
976
|
+
expired && /* @__PURE__ */ jsx("p", { className: "ap-confirm-expired", role: "alert", "data-testid": "appilots-confirm-expired", children: strings.confirmExpired }),
|
|
977
|
+
/* @__PURE__ */ jsxs("div", { className: "ap-confirm-actions", children: [
|
|
978
|
+
/* @__PURE__ */ jsx(
|
|
979
|
+
"button",
|
|
980
|
+
{
|
|
981
|
+
type: "button",
|
|
982
|
+
className: "ap-primary",
|
|
983
|
+
"data-testid": "appilots-confirm-accept",
|
|
984
|
+
"aria-label": confirmName,
|
|
985
|
+
disabled: expired,
|
|
986
|
+
onClick: onApprove,
|
|
987
|
+
children: confirmLabel
|
|
988
|
+
}
|
|
989
|
+
),
|
|
990
|
+
/* @__PURE__ */ jsx(
|
|
991
|
+
"button",
|
|
992
|
+
{
|
|
993
|
+
ref: cancelRef,
|
|
994
|
+
type: "button",
|
|
995
|
+
"data-testid": "appilots-confirm-reject",
|
|
996
|
+
"aria-label": cancelName,
|
|
997
|
+
onClick: onReject,
|
|
998
|
+
children: cancelLabel
|
|
999
|
+
}
|
|
1000
|
+
)
|
|
1001
|
+
] })
|
|
1002
|
+
]
|
|
1003
|
+
}
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
643
1006
|
var styles = `
|
|
644
1007
|
.appilots-chat-fab { position: fixed; bottom: max(16px, env(safe-area-inset-bottom)); right: max(16px, env(safe-area-inset-right)); display: flex; flex-direction: column; align-items: flex-end; gap: 12px; }
|
|
645
1008
|
.appilots-chat-fab[data-position="bottom-left"] { right: auto; left: max(16px, env(safe-area-inset-left)); align-items: flex-start; }
|
|
646
|
-
.appilots-chat-fab .ap-fab-panel { width: min(
|
|
1009
|
+
.appilots-chat-fab .ap-fab-panel { width: min(24.375rem, calc(100vw - max(16px, env(safe-area-inset-left)) - max(16px, env(safe-area-inset-right)))); height: min(40rem, calc(100dvh - 68px - max(16px, env(safe-area-inset-top)) - max(16px, env(safe-area-inset-bottom)))); }
|
|
647
1010
|
.appilots-chat-fab .ap-fab-panel[hidden] { display: none; }
|
|
648
1011
|
.appilots-chat-fab .appilots-chat { min-height: 0; box-shadow: 0 16px 64px #17203326; }
|
|
649
1012
|
.appilots-chat-fab .ap-fab-button { box-sizing: border-box; display: grid; place-items: center; width: 56px; height: 56px; flex-shrink: 0; padding: 0; margin: 0; border: 0; border-radius: 50%; color: #fff; background: var(--ap-fab-accent); box-shadow: 0 6px 24px #17203333; cursor: pointer; }
|
|
650
1013
|
.appilots-chat-fab .ap-fab-button svg { width: 24px; height: 24px; }
|
|
651
1014
|
.appilots-chat-fab .ap-fab-button:hover { filter: brightness(.94); }
|
|
1015
|
+
.appilots-chat-fab .ap-fab-stop { display: flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 16px; border: 1px solid #d5dbe5; border-radius: 22px; color: #172033; background: #fff; font: 600 14px/1.2 system-ui, -apple-system, sans-serif; box-shadow: 0 4px 16px #17203322; cursor: pointer; }
|
|
1016
|
+
.appilots-chat-fab .ap-fab-stop:focus-visible { outline: 3px solid var(--ap-fab-accent); outline-offset: 2px; }
|
|
652
1017
|
.appilots-chat-fab .ap-fab-button:focus-visible { outline: 3px solid var(--ap-fab-accent); outline-offset: 4px; }
|
|
1018
|
+
.appilots-chat-fab .ap-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
1019
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1020
|
+
.appilots-chat-fab, .appilots-chat-fab * { animation: none !important; transition: none !important; }
|
|
1021
|
+
}
|
|
653
1022
|
`;
|
|
1023
|
+
function ClosedPanelActionStatus({
|
|
1024
|
+
open,
|
|
1025
|
+
locale
|
|
1026
|
+
}) {
|
|
1027
|
+
const actionStatus = useActionStatus(locale);
|
|
1028
|
+
return /* @__PURE__ */ jsx("div", { className: "ap-sr-only", role: "status", "data-testid": "appilots-fab-action-status", children: open ? "" : actionStatus });
|
|
1029
|
+
}
|
|
654
1030
|
function AppilotsChatFab({
|
|
655
1031
|
position = "bottom-right",
|
|
656
1032
|
defaultOpen = false,
|
|
@@ -701,6 +1077,8 @@ function AppilotsChatFab({
|
|
|
701
1077
|
className: "ap-fab-panel",
|
|
702
1078
|
ref: panelRef,
|
|
703
1079
|
hidden: !open,
|
|
1080
|
+
role: "dialog",
|
|
1081
|
+
"aria-label": chatProps.title ?? strings.title,
|
|
704
1082
|
"data-testid": "appilots-chat-panel",
|
|
705
1083
|
children: /* @__PURE__ */ jsx(
|
|
706
1084
|
AppilotsChat,
|
|
@@ -708,11 +1086,14 @@ function AppilotsChatFab({
|
|
|
708
1086
|
...chatProps,
|
|
709
1087
|
locale,
|
|
710
1088
|
onClose: close,
|
|
1089
|
+
hidden: !open,
|
|
711
1090
|
style: { "--ap-accent": accentColor, ...style }
|
|
712
1091
|
}
|
|
713
1092
|
)
|
|
714
1093
|
}
|
|
715
1094
|
),
|
|
1095
|
+
/* @__PURE__ */ jsx(ClosedPanelActionStatus, { open, locale }),
|
|
1096
|
+
!open && /* @__PURE__ */ jsx(ClosedPanelTurn, { strings }),
|
|
716
1097
|
/* @__PURE__ */ jsx(
|
|
717
1098
|
"button",
|
|
718
1099
|
{
|
|
@@ -721,6 +1102,7 @@ function AppilotsChatFab({
|
|
|
721
1102
|
ref: triggerRef,
|
|
722
1103
|
"aria-label": open ? strings.closeChat : strings.openChat,
|
|
723
1104
|
title: open ? strings.closeChat : strings.openChat,
|
|
1105
|
+
"aria-haspopup": "dialog",
|
|
724
1106
|
"aria-expanded": open,
|
|
725
1107
|
"aria-controls": panelId,
|
|
726
1108
|
"data-testid": "appilots-chat-fab",
|
|
@@ -734,5 +1116,30 @@ function AppilotsChatFab({
|
|
|
734
1116
|
host
|
|
735
1117
|
);
|
|
736
1118
|
}
|
|
1119
|
+
function ClosedPanelTurn({ strings }) {
|
|
1120
|
+
const { chat } = useAppilotsRuntime();
|
|
1121
|
+
const { isLoading, loadingStatusKey } = useSyncExternalStore(
|
|
1122
|
+
chat.subscribeState,
|
|
1123
|
+
chat.getState,
|
|
1124
|
+
chat.getState
|
|
1125
|
+
);
|
|
1126
|
+
const stillWorkingSeconds = useStillWorkingSeconds(isLoading);
|
|
1127
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1128
|
+
isLoading && /* @__PURE__ */ jsxs(
|
|
1129
|
+
"button",
|
|
1130
|
+
{
|
|
1131
|
+
type: "button",
|
|
1132
|
+
className: "ap-fab-stop",
|
|
1133
|
+
"data-testid": "appilots-fab-stop",
|
|
1134
|
+
onClick: () => chat.cancelMessage(),
|
|
1135
|
+
children: [
|
|
1136
|
+
/* @__PURE__ */ jsx(Icon, { name: "stop" }),
|
|
1137
|
+
strings.stop
|
|
1138
|
+
]
|
|
1139
|
+
}
|
|
1140
|
+
),
|
|
1141
|
+
/* @__PURE__ */ jsx("div", { className: "ap-sr-only", role: "status", "data-testid": "appilots-fab-status", children: isLoading ? stillWorkingSeconds ? strings.stillWorking(stillWorkingSeconds) : strings[loadingStatusKey] : "" })
|
|
1142
|
+
] });
|
|
1143
|
+
}
|
|
737
1144
|
|
|
738
1145
|
export { AppilotsChat, AppilotsChatFab, AppilotsWebProvider, useAppilotsActions, useAppilotsChat, useAppilotsRuntime };
|