@wix/himalaya-cli 0.1250.0 → 0.1252.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/wix-member-login.d.ts +6 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/index.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/serve-runtime/src/wix-bridge.d.ts +144 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/serve-storage/src/sha256.d.ts +13 -0
- package/dist/cli.mjs +554 -284
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/sdk-reference.json +1 -1
- package/package.json +2 -2
- package/templates/beauty-salon-owner/dev/index.ts +668 -87
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/community-club-owner/dev/index.ts +668 -87
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/community-club-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/community-club-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/events-meetups-owner/dev/index.ts +668 -87
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/events-meetups-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/events-meetups-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/gym-fitness-owner/dev/index.ts +668 -87
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/nonprofit-cause-owner/dev/index.ts +668 -87
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/owner-lite/dev/index.ts +754 -173
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/owner-lite/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/owner-lite/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/podcast-creator-owner/dev/index.ts +668 -87
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/portfolio-creative-owner/dev/index.ts +668 -87
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/restaurant-owner/dev/index.ts +668 -87
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/restaurant-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/restaurant-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/retail-store-owner/dev/index.ts +668 -87
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/retail-store-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/retail-store-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
- package/templates/yoga-studio-owner/dev/index.ts +668 -87
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +271 -45
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-widget-model.ts +3 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-widget-view.ts +333 -18
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +7 -0
|
@@ -72,8 +72,19 @@ import {
|
|
|
72
72
|
import { runClientToolExchange, type AriaClientTools } from "./aria-client-tools.js";
|
|
73
73
|
import { proseOfParts, scanEnvelopes, stripHidden } from "./aria-widgets.js";
|
|
74
74
|
import { isWebUrl, stripMarkdown } from "./aria-widget-model.js";
|
|
75
|
-
import {
|
|
76
|
-
|
|
75
|
+
import {
|
|
76
|
+
DEFAULT_QUESTIONNAIRE_COPY,
|
|
77
|
+
NO_WIDGET,
|
|
78
|
+
activeWidgetOf,
|
|
79
|
+
asyncAdvanced,
|
|
80
|
+
questionnaireAnswers,
|
|
81
|
+
questionnaireSynced,
|
|
82
|
+
sameQuestionnaire,
|
|
83
|
+
type QuestionnaireCopy,
|
|
84
|
+
type SubtreeSlot,
|
|
85
|
+
type WidgetOptionRow,
|
|
86
|
+
type WidgetView,
|
|
87
|
+
} from "./aria-widget-view.js";
|
|
77
88
|
import { substituteSelection } from "./aria-widget-model.js";
|
|
78
89
|
import { MENTION_PAGE_SIZE, allowlisted, emptyMentionModel, mentionView,
|
|
79
90
|
type MentionItemRow, type MentionLabels, type MentionModel, type MentionProvider, type MentionView } from "./aria-mentions.js";
|
|
@@ -326,6 +337,15 @@ export interface AriaChatLabels {
|
|
|
326
337
|
selectionLimit?: (max: number) => string;
|
|
327
338
|
/** Shown under a questionnaire while a required answer is missing. */
|
|
328
339
|
answersIncomplete?: string;
|
|
340
|
+
/** A questionnaire's place, e.g. "2 of 3". Only shown with two or more
|
|
341
|
+
* questions. */
|
|
342
|
+
questionProgress?: (current: number, total: number) => string;
|
|
343
|
+
/** A questionnaire's submit, when the payload's own button has no label. */
|
|
344
|
+
questionnaireSubmit?: Resolvable<string>;
|
|
345
|
+
/** Shown when the owner taps an option whose destination is a screen of the
|
|
346
|
+
* native Wix app that this app does not have, and the payload gave no
|
|
347
|
+
* explanation of its own. */
|
|
348
|
+
notSupported?: Resolvable<string>;
|
|
329
349
|
/** Shown instead of a raw transport error when this HOST cannot reach Wix at
|
|
330
350
|
* all — it carries no Wix app binding to sign the chat-service call with.
|
|
331
351
|
* See `isWixAccountUnreachable`, and note what it is NOT: a signed-out user. */
|
|
@@ -349,6 +369,10 @@ export interface AriaChatLabels {
|
|
|
349
369
|
/** Shown when one provider's items could not be fetched. Takes the
|
|
350
370
|
* category's label. */
|
|
351
371
|
mentionItemsFailed?: (category: string) => string;
|
|
372
|
+
/** Shown when a response widget's item provider cannot be queried. */
|
|
373
|
+
widgetItemsFailed?: Resolvable<string>;
|
|
374
|
+
/** Shown when this host cannot reach Wix's item provider service. */
|
|
375
|
+
widgetItemsUnavailable?: Resolvable<string>;
|
|
352
376
|
/** Attachment copy — see `AriaChatOptions.attachments`. Chip states: */
|
|
353
377
|
attachUploading?: Resolvable<string>;
|
|
354
378
|
attachFailed?: Resolvable<string>;
|
|
@@ -509,7 +533,7 @@ export interface AriaChatOptions {
|
|
|
509
533
|
* its new BOOLEAN there on iOS and Android, and the native marshaller merges
|
|
510
534
|
* args over params — so anything reading it must cope with a non-string.
|
|
511
535
|
* `optionValue` is the key a caller should use when it means the option. */
|
|
512
|
-
export type AriaActionArg = string | { message?: string; text?: string; value?: unknown; optionValue?: string; id?: string; url?: string; intent?: string; source?: string; ok?: unknown; ref?: string } | undefined;
|
|
536
|
+
export type AriaActionArg = string | { message?: string; text?: string; value?: unknown; optionValue?: string; id?: string; url?: string; intent?: string; section?: string; step?: string; source?: string; ok?: unknown; ref?: string } | undefined;
|
|
513
537
|
|
|
514
538
|
/** The first of these fields that is actually a non-empty string.
|
|
515
539
|
*
|
|
@@ -1291,8 +1315,13 @@ export interface AriaChatCore {
|
|
|
1291
1315
|
* first. The mode is known where the payload is, so the choice belongs
|
|
1292
1316
|
* here. */
|
|
1293
1317
|
widgetPick(s: AriaChatState, arg?: AriaActionArg): AriaChatState;
|
|
1294
|
-
/**
|
|
1318
|
+
/** Type into a QUESTIONNAIRE question's free-text field. `id` names the
|
|
1319
|
+
* question; the text rides the control's own `value`. */
|
|
1295
1320
|
widgetAnswer(s: AriaChatState, arg?: AriaActionArg): AriaChatState;
|
|
1321
|
+
/** Move through a QUESTIONNAIRE one question at a time: `step` is `"next"`
|
|
1322
|
+
* or `"back"`. Next refuses while the question on screen is required and
|
|
1323
|
+
* unanswered — the same rule its greyed twin shows. */
|
|
1324
|
+
widgetStep(s: AriaChatState, arg?: AriaActionArg): AriaChatState;
|
|
1296
1325
|
/** The composer's mic: open the recognizer and show "Listening…". Returns at
|
|
1297
1326
|
* once; the transcript arrives later through `dictated`. */
|
|
1298
1327
|
dictate(s: AriaChatState): AriaChatState;
|
|
@@ -1361,6 +1390,15 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1361
1390
|
o.labels?.answersIncomplete ?? "Answer the required questions to continue.";
|
|
1362
1391
|
const limitReachedText = (max: number) =>
|
|
1363
1392
|
o.labels?.selectionLimit?.(max) ?? `You can choose up to ${max}.`;
|
|
1393
|
+
const questionnaireCopy = (): QuestionnaireCopy => ({
|
|
1394
|
+
progress: o.labels?.questionProgress ?? DEFAULT_QUESTIONNAIRE_COPY.progress,
|
|
1395
|
+
incomplete: answersIncompleteText(),
|
|
1396
|
+
submit: o.labels?.questionnaireSubmit
|
|
1397
|
+
? res(o.labels.questionnaireSubmit)
|
|
1398
|
+
: DEFAULT_QUESTIONNAIRE_COPY.submit,
|
|
1399
|
+
});
|
|
1400
|
+
const notSupportedText = () =>
|
|
1401
|
+
(o.labels?.notSupported ? res(o.labels.notSupported) : "") || "That isn't available in this app yet.";
|
|
1364
1402
|
// States the fact and stops. It deliberately gives no instruction, because
|
|
1365
1403
|
// there is none the owner could follow: they are signed in, and the thing that
|
|
1366
1404
|
// is missing is the app's own Wix binding. "Here" is doing real work — it says
|
|
@@ -1372,6 +1410,8 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1372
1410
|
o.labels?.dictationEmpty ?? "Didn't catch that. Check microphone access and try again.",
|
|
1373
1411
|
);
|
|
1374
1412
|
const dictationFailedText = () => res(o.labels?.dictationFailed ?? "Dictation isn't available right now.");
|
|
1413
|
+
const widgetItemsFailedText = () => res(o.labels?.widgetItemsFailed ?? "Couldn't load these items.");
|
|
1414
|
+
const widgetItemsUnavailableText = () => res(o.labels?.widgetItemsUnavailable ?? "Items aren't available here.");
|
|
1375
1415
|
const attachUploadingText = () => res(o.labels?.attachUploading ?? "Uploading…");
|
|
1376
1416
|
const attachFailedText = () => res(o.labels?.attachFailed ?? "Couldn't upload");
|
|
1377
1417
|
const chipLabels = () => ({ uploading: attachUploadingText(), failed: attachFailedText() });
|
|
@@ -1989,16 +2029,119 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1989
2029
|
* from what the service sent. */
|
|
1990
2030
|
const carrySelection = (next: WidgetView, prev?: WidgetView): WidgetView => {
|
|
1991
2031
|
if (!prev || prev.hidden || next.hidden) return next;
|
|
2032
|
+
// A new assistant row can ask the exact same questionnaire again. Its
|
|
2033
|
+
// shape is not enough to identify the ask; only carry edits while the
|
|
2034
|
+
// transcript row itself is unchanged.
|
|
2035
|
+
if (!next.sourceMessageId || prev.sourceMessageId !== next.sourceMessageId) return next;
|
|
1992
2036
|
if (prev.kind !== next.kind) return next;
|
|
2037
|
+
// A questionnaire carries MORE than a selection — every answer, typed text
|
|
2038
|
+
// and which question is on screen — and its `options` are only the CURRENT
|
|
2039
|
+
// question's choices, so the comparison below would call an owner on
|
|
2040
|
+
// question two a different ask and throw all of it away on the next poll.
|
|
2041
|
+
if (next.kind === "QUESTIONNAIRE") {
|
|
2042
|
+
return sameQuestionnaire(prev, next)
|
|
2043
|
+
? questionnaireSynced({ ...next, questions: prev.questions }, prev.questionIndex,
|
|
2044
|
+
questionnaireCopy(), !prev.answersTextHidden)
|
|
2045
|
+
: next;
|
|
2046
|
+
}
|
|
2047
|
+
// ITEMS_SELECTION's payload supplies a provider key, not option rows. Keep
|
|
2048
|
+
// the fetched page (and its terminal error state) as the transcript is
|
|
2049
|
+
// re-read; otherwise every poll would replace it with the empty loading
|
|
2050
|
+
// placeholder and issue the same provider request again.
|
|
2051
|
+
if (next.kind === "ITEMS_SELECTION" && prev.providerKey === next.providerKey && !prev.itemsLoading) {
|
|
2052
|
+
return reselect({ ...next, options: prev.options, itemsLoading: false, itemsLoadingHidden: true,
|
|
2053
|
+
itemsError: prev.itemsError, itemsErrorHidden: prev.itemsErrorHidden,
|
|
2054
|
+
itemsEmpty: prev.itemsEmpty }, prev.options);
|
|
2055
|
+
}
|
|
1993
2056
|
if (prev.options.length !== next.options.length) return next;
|
|
1994
2057
|
for (let i = 0; i < next.options.length; i++) {
|
|
1995
2058
|
if (prev.options[i].value !== next.options[i].value) return next;
|
|
1996
2059
|
}
|
|
1997
|
-
|
|
2060
|
+
// The line explaining an option that could not open is the answer to a
|
|
2061
|
+
// tap, and a poll is not a reason to take it back.
|
|
2062
|
+
const kept = prev.notice ? { ...next, notice: prev.notice, noticeHidden: prev.noticeHidden } : next;
|
|
2063
|
+
if (!prev.options.some((o, i) => o.selected !== next.options[i].selected)) return kept;
|
|
1998
2064
|
const options = next.options.map((o, i) => ({ ...o, selected: prev.options[i].selected }));
|
|
1999
|
-
return reselect({ ...
|
|
2065
|
+
return reselect({ ...kept, options }, options);
|
|
2066
|
+
};
|
|
2067
|
+
|
|
2068
|
+
/** Fetch the provider-backed rows after a transcript reveals an
|
|
2069
|
+
* ITEMS_SELECTION. This runs in the worker, through the same Wix API
|
|
2070
|
+
* capability used by the mention picker. A provider failure is shown inside
|
|
2071
|
+
* the widget and never turns into an empty list that looks like success. */
|
|
2072
|
+
const withWidgetItems = async (s: AriaChatState): Promise<AriaChatState> => {
|
|
2073
|
+
const w = s.widget;
|
|
2074
|
+
if (w.kind !== "ITEMS_SELECTION" || !w.itemsLoading || !w.providerKey) return s;
|
|
2075
|
+
|
|
2076
|
+
let options: WidgetOptionRow[] = [];
|
|
2077
|
+
let error = "";
|
|
2078
|
+
try {
|
|
2079
|
+
const installed = await listInstalledProviders();
|
|
2080
|
+
const provider = (installed.providers ?? []).find((candidate) =>
|
|
2081
|
+
typeof candidate?.key === "string" && candidate.key.trim() === w.providerKey);
|
|
2082
|
+
if (!provider) {
|
|
2083
|
+
error = widgetItemsFailedText();
|
|
2084
|
+
} else {
|
|
2085
|
+
const pageSize = 50;
|
|
2086
|
+
let offset = 0;
|
|
2087
|
+
let cursor = "";
|
|
2088
|
+
const seenCursors = new Set<string>();
|
|
2089
|
+
const items: ReturnType<typeof itemsOf> = [];
|
|
2090
|
+
for (;;) {
|
|
2091
|
+
const query: ItemsQuery = provider.paginationMode === "CURSOR"
|
|
2092
|
+
? { cursorPaging: { limit: pageSize, ...(cursor ? { cursor } : {}) } }
|
|
2093
|
+
: provider.paginationMode === "OFFSET" ? { paging: { limit: pageSize, offset } } : {};
|
|
2094
|
+
const response = await queryItems(w.providerKey, query);
|
|
2095
|
+
const page = itemsOf(response);
|
|
2096
|
+
items.push(...page);
|
|
2097
|
+
|
|
2098
|
+
// Providers expose their paging state with the page. Follow every
|
|
2099
|
+
// advertised page so an owner can select an item beyond the first 50.
|
|
2100
|
+
// Stop on an empty raw page or a repeated cursor to avoid looping on
|
|
2101
|
+
// malformed provider metadata.
|
|
2102
|
+
const rawCount = response?.items?.length ?? 0;
|
|
2103
|
+
const hasNext = response?.pagingMetadata?.hasNext;
|
|
2104
|
+
if (provider.paginationMode === "CURSOR") {
|
|
2105
|
+
const next = response?.pagingMetadata?.cursors?.next ?? "";
|
|
2106
|
+
if (!next || hasNext === false || rawCount === 0 || seenCursors.has(next)) break;
|
|
2107
|
+
seenCursors.add(next);
|
|
2108
|
+
cursor = next;
|
|
2109
|
+
} else if (provider.paginationMode === "OFFSET") {
|
|
2110
|
+
if (hasNext !== true || rawCount === 0) break;
|
|
2111
|
+
offset += pageSize;
|
|
2112
|
+
} else {
|
|
2113
|
+
break;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
options = items.map((item, i) => {
|
|
2117
|
+
const pictureUrl = isWixMediaHost(hostOf(item.imageUrl)) ? item.imageUrl : "";
|
|
2118
|
+
return {
|
|
2119
|
+
id: `o${i}`, value: item.id, title: item.title, subtitle: item.subtitle,
|
|
2120
|
+
subtitleHidden: !item.subtitle, text: "", textHidden: true,
|
|
2121
|
+
pictureUrl, pictureHidden: !pictureUrl, buttonLabel: "", buttonHidden: true,
|
|
2122
|
+
listText: "", listHidden: true, selected: false, respondText: "",
|
|
2123
|
+
actionUrl: "", actionScreenId: "", notSupportedText: "",
|
|
2124
|
+
};
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
} catch (e) {
|
|
2128
|
+
error = isWixAccountUnreachable(e) || String((e as Error)?.message ?? e).includes("absolute_urls_not_allowed")
|
|
2129
|
+
? widgetItemsUnavailableText() : widgetItemsFailedText();
|
|
2130
|
+
}
|
|
2131
|
+
const loaded = {
|
|
2132
|
+
...w, options, itemsLoading: false, itemsLoadingHidden: true,
|
|
2133
|
+
itemsError: error, itemsErrorHidden: !error, itemsEmpty: options.length === 0,
|
|
2134
|
+
};
|
|
2135
|
+
return { ...s, widget: reselect(loaded, options) };
|
|
2000
2136
|
};
|
|
2001
2137
|
|
|
2138
|
+
/** The questionnaire's derived fields in THIS app's words. `viewOf` derives
|
|
2139
|
+
* them in the stock copy, having none of its own. */
|
|
2140
|
+
const localized = (w: WidgetView): WidgetView =>
|
|
2141
|
+
w.kind === "QUESTIONNAIRE"
|
|
2142
|
+
? questionnaireSynced(w, w.questionIndex, questionnaireCopy(), !w.answersTextHidden)
|
|
2143
|
+
: w;
|
|
2144
|
+
|
|
2002
2145
|
const observed = (msgs: ConversationMessage[], prev?: WidgetView) => ({
|
|
2003
2146
|
_srvUsers: countTurns(msgs, "USER"),
|
|
2004
2147
|
_srvAssistants: countTurns(msgs, "ASSISTANT"),
|
|
@@ -2015,15 +2158,15 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2015
2158
|
// envelopes into live interactive cards. The transcript path already did
|
|
2016
2159
|
// both; the widget path did neither.
|
|
2017
2160
|
widget: carrySelection(
|
|
2018
|
-
activeWidgetOf(
|
|
2161
|
+
localized(activeWidgetOf(
|
|
2019
2162
|
// A draft offers no controls: the agent has not finished asking, and
|
|
2020
2163
|
// an answer tapped now would start a second turn on top of this one.
|
|
2021
2164
|
// An empty newest row is NO_WIDGET — the placeholder rule.
|
|
2022
|
-
msgs.map((m) => ({ role: m.role, content: stillWriting(m) ? "" : stripHidden(messageText(m)) })),
|
|
2165
|
+
msgs.map((m) => ({ id: m.id, role: m.role, content: stillWriting(m) ? "" : stripHidden(messageText(m)) })),
|
|
2023
2166
|
disclosedLabel,
|
|
2024
2167
|
o.subtree,
|
|
2025
2168
|
(url) => isWixMediaHost(hostOf(url)),
|
|
2026
|
-
),
|
|
2169
|
+
)),
|
|
2027
2170
|
prev,
|
|
2028
2171
|
),
|
|
2029
2172
|
});
|
|
@@ -2082,7 +2225,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2082
2225
|
seen = raw;
|
|
2083
2226
|
const failed = takeToolFailure(convId);
|
|
2084
2227
|
if (failed) {
|
|
2085
|
-
return { ...s, status: "ready", _convId: convId, messages: server, input: "", _pending: "", _abortId: "", ...observed(raw, s.widget), ok: false, ...toolFailureSlots(failed) };
|
|
2228
|
+
return withWidgetItems({ ...s, status: "ready", _convId: convId, messages: server, input: "", _pending: "", _abortId: "", ...observed(raw, s.widget), ok: false, ...toolFailureSlots(failed) });
|
|
2086
2229
|
}
|
|
2087
2230
|
// Not over while the agent is waiting on a client tool from us — and this
|
|
2088
2231
|
// loop has no clock of its own, so re-reading meanwhile would spend its
|
|
@@ -2094,10 +2237,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2094
2237
|
// the last BUBBLE stalled this loop on exactly that reply, the same way it
|
|
2095
2238
|
// stalled `tick`.
|
|
2096
2239
|
if (toolReplied(convId, raw) ?? repliedTo(raw, s._turnId) ?? countSettledAnswers(raw) > s._replyAfter) {
|
|
2097
|
-
return { ...s, status: "ready", _convId: convId, messages: bubbles, prompts: [], input: "", statusText: "", error: "", _pending: "", ...observed(raw, s.widget), ok: false };
|
|
2240
|
+
return withWidgetItems({ ...s, status: "ready", _convId: convId, messages: bubbles, prompts: [], input: "", statusText: "", error: "", _pending: "", ...observed(raw, s.widget), ok: false });
|
|
2098
2241
|
}
|
|
2099
2242
|
}
|
|
2100
|
-
return { ...s, status: "ready", _convId: convId, messages: server, input: "", statusText: slowText(), _pending: "", ...observed(seen, s.widget), ok: false };
|
|
2243
|
+
return withWidgetItems({ ...s, status: "ready", _convId: convId, messages: server, input: "", statusText: slowText(), _pending: "", ...observed(seen, s.widget), ok: false });
|
|
2101
2244
|
}
|
|
2102
2245
|
|
|
2103
2246
|
/** Send + wait, by whichever transport this consumer configured. */
|
|
@@ -2600,6 +2743,20 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2600
2743
|
* selection and the payload — and a toggle that updated two of the three is
|
|
2601
2744
|
* the shape of bug that leaves a button enabled over an empty selection. */
|
|
2602
2745
|
const reselect = (w: WidgetView, options: WidgetOptionRow[]): WidgetView => {
|
|
2746
|
+
// A questionnaire's `options` are the question ON SCREEN's choices, so a
|
|
2747
|
+
// selection is written back into that question and everything re-derived
|
|
2748
|
+
// from the answers — the Next gate and the submit gate included.
|
|
2749
|
+
if (w.kind === "QUESTIONNAIRE") {
|
|
2750
|
+
const i = w.questionIndex;
|
|
2751
|
+
const questions = w.questions.map((q, j) => {
|
|
2752
|
+
if (j !== i) return q;
|
|
2753
|
+
// A single-select takes ONE answer: choosing a choice replaces what
|
|
2754
|
+
// was typed, as typing replaces the choice (`widgetAnswer`).
|
|
2755
|
+
const picked = options.some((c) => c.selected);
|
|
2756
|
+
return { ...q, choices: options, freeText: q.type === "single-select" && picked ? "" : q.freeText };
|
|
2757
|
+
});
|
|
2758
|
+
return questionnaireSynced({ ...w, questions }, i, questionnaireCopy(), true);
|
|
2759
|
+
}
|
|
2603
2760
|
const chosen = options.filter((o) => o.selected).length;
|
|
2604
2761
|
const limitReached = w.maxSelected > 0 && chosen >= w.maxSelected;
|
|
2605
2762
|
// Only says anything once the owner is actually against the limit. A line
|
|
@@ -2658,8 +2815,8 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2658
2815
|
// `copyNote: ""` — a resumed transcript ends in a reply, so the row is
|
|
2659
2816
|
// shown at once; a note carried over would say "Copied" beside an
|
|
2660
2817
|
// answer from a different conversation that nobody copied.
|
|
2661
|
-
return leaving({ ...s, status: "ready", _convId: resumeId, _siteId: site, _pending: "", statusText: "", error: "", copyNote: "",
|
|
2662
|
-
messages: resumed, ...observed(raw, s.widget), prompts: promptsFor(resumed, promptList()) });
|
|
2818
|
+
return leaving(await withWidgetItems({ ...s, status: "ready", _convId: resumeId, _siteId: site, _pending: "", statusText: "", error: "", copyNote: "",
|
|
2819
|
+
messages: resumed, ...observed(raw, s.widget), prompts: promptsFor(resumed, promptList()) }));
|
|
2663
2820
|
} catch { /* expired or unreachable — fall through to a fresh one */ }
|
|
2664
2821
|
}
|
|
2665
2822
|
// Opening a screen runs `load` more than once — the bundle self-kicks at
|
|
@@ -2864,8 +3021,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2864
3021
|
// AFTER the transcript rebuild, so a widget that has just been retired
|
|
2865
3022
|
// by a new answer is never polled for.
|
|
2866
3023
|
const withJob = { ...next, widget: await withJobProgress(next.widget) };
|
|
3024
|
+
const withItems = await withWidgetItems(withJob);
|
|
3025
|
+
if (turnSeq !== seq || abortingEpochs.has(seq)) return undefined;
|
|
2867
3026
|
// Slow the ladder the moment the turn is over; leave it alone otherwise.
|
|
2868
|
-
return ended && wasFast ? cadence(
|
|
3027
|
+
return ended && wasFast ? cadence(withItems, o.poll.idleMs, o.poll.fastMs) : withItems;
|
|
2869
3028
|
} catch {
|
|
2870
3029
|
// A read that THREW is exactly as stale as one that resolved: if a turn
|
|
2871
3030
|
// boundary moved on while it was in flight, returning `s` merges the
|
|
@@ -2909,28 +3068,49 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2909
3068
|
},
|
|
2910
3069
|
|
|
2911
3070
|
widgetAnswer: (s, arg) => {
|
|
2912
|
-
const
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
}
|
|
2933
|
-
};
|
|
3071
|
+
const w = s.widget;
|
|
3072
|
+
if (w.kind !== "QUESTIONNAIRE") return s;
|
|
3073
|
+
const qid = (typeof arg === "string" ? arg : firstString(arg?.id)).trim();
|
|
3074
|
+
const i = w.questions.findIndex((q) => q.id === qid);
|
|
3075
|
+
// Text for a question that offers no field to type it into is not an
|
|
3076
|
+
// answer the owner gave; it cannot satisfy a required choice.
|
|
3077
|
+
if (i < 0 || w.questions[i].freeHidden) return s;
|
|
3078
|
+
// VERBATIM, not trimmed: this runs on every keystroke, and trimming here
|
|
3079
|
+
// would eat the space between two words the moment it is typed. The
|
|
3080
|
+
// answer is trimmed where it is READ (`questionnaireSynced`).
|
|
3081
|
+
const text = typeof arg === "string" ? "" : firstStringRaw(arg?.value, arg?.text);
|
|
3082
|
+
const questions = w.questions.map((q, j) => {
|
|
3083
|
+
if (j !== i) return q;
|
|
3084
|
+
// Typing into a single-select's "something else" IS the answer, so it
|
|
3085
|
+
// takes the place of any choice — one question, one answer.
|
|
3086
|
+
const replaces = q.type === "single-select" && !!text.trim();
|
|
3087
|
+
return {
|
|
3088
|
+
...q,
|
|
3089
|
+
freeText: text,
|
|
3090
|
+
choices: replaces ? q.choices.map((c) => (c.selected ? { ...c, selected: false } : c)) : q.choices,
|
|
3091
|
+
};
|
|
3092
|
+
});
|
|
3093
|
+
// Re-derived for the question the owner is LOOKING AT, whichever one the
|
|
3094
|
+
// text arrived for — a late keystroke from the previous question must not
|
|
3095
|
+
// flip the screen back to it.
|
|
3096
|
+
return { ...s, widget: questionnaireSynced({ ...w, questions }, w.questionIndex, questionnaireCopy(), true) };
|
|
3097
|
+
},
|
|
3098
|
+
|
|
3099
|
+
widgetStep: (s, arg) => {
|
|
3100
|
+
const w = s.widget;
|
|
3101
|
+
if (w.kind !== "QUESTIONNAIRE" || w.submitting) return s;
|
|
3102
|
+
const step = typeof arg === "string" ? arg.trim() : firstString(arg?.step, arg?.value, arg?.id);
|
|
3103
|
+
const current = w.questions[w.questionIndex];
|
|
3104
|
+
if (!current) return s;
|
|
3105
|
+
if (step === "back") {
|
|
3106
|
+
if (w.questionIndex === 0) return s;
|
|
3107
|
+
return { ...s, widget: questionnaireSynced(w, w.questionIndex - 1, questionnaireCopy(), true) };
|
|
3108
|
+
}
|
|
3109
|
+
if (step !== "next") return s;
|
|
3110
|
+
// The same rule the greyed Next shows. Refused here as well, because a
|
|
3111
|
+
// descriptor flag is a picture of the rule, not the rule.
|
|
3112
|
+
if (current.missing || w.questionIndex >= w.questions.length - 1) return s;
|
|
3113
|
+
return { ...s, widget: questionnaireSynced(w, w.questionIndex + 1, questionnaireCopy(), true) };
|
|
2934
3114
|
},
|
|
2935
3115
|
|
|
2936
3116
|
// --- Mentions -----------------------------------------------------------
|
|
@@ -3061,18 +3241,63 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
3061
3241
|
// a send that had already failed.
|
|
3062
3242
|
if (widgetSending) return s;
|
|
3063
3243
|
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3244
|
+
// A VISUAL_DISPLAY section's own button ("Edit" beside a row). Named by
|
|
3245
|
+
// its own key, `section`, never by `value`: a section is not an option,
|
|
3246
|
+
// and an option whose value happened to equal a row id must not be able
|
|
3247
|
+
// to answer for it. The clone's positional form can only say `value`, so
|
|
3248
|
+
// a row id there is tried only once no option has claimed it.
|
|
3249
|
+
const sectionId = typeof arg === "object" ? firstString(arg?.section) : "";
|
|
3250
|
+
const option = sectionId ? undefined : w.options.find((o) => o.value === which);
|
|
3251
|
+
const section = sectionId
|
|
3252
|
+
? w.rows.find((r) => r.id === sectionId)
|
|
3253
|
+
: (option ? undefined : w.rows.find((r) => r.id === which));
|
|
3254
|
+
if (sectionId && !section) return s;
|
|
3255
|
+
|
|
3256
|
+
// An option with a supported URL goes there even when the payload also
|
|
3257
|
+
// includes a respondText fallback. The view removes an unsupported URL
|
|
3258
|
+
// and its fallback before this action can run, so it cannot turn into a
|
|
3259
|
+
// different response. Screen-only options still use respondText when
|
|
3260
|
+
// present; otherwise show why the Wix-native screen cannot open here.
|
|
3261
|
+
if (option?.actionUrl) return openWidgetUrl(s, option.actionUrl);
|
|
3262
|
+
|
|
3263
|
+
// An option whose button goes to a screen of the native Wix app — the
|
|
3264
|
+
// catalogue's "Open store settings". This app has none of those screens,
|
|
3265
|
+
// so a screen-only option must explain the limitation rather than leave
|
|
3266
|
+
// a button that does nothing.
|
|
3267
|
+
if (option && !option.respondText) {
|
|
3268
|
+
if (option.actionScreenId) {
|
|
3269
|
+
// A screen of the native Wix app. This app has none of them, so the
|
|
3270
|
+
// honest thing is to say so — the payload's own words when it sent
|
|
3271
|
+
// some, ours otherwise — rather than leave the tap unanswered.
|
|
3272
|
+
const notice = option.notSupportedText || notSupportedText();
|
|
3273
|
+
return { ...s, widget: { ...w, notice, noticeHidden: !notice } };
|
|
3274
|
+
}
|
|
3275
|
+
return s;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
const questionnaire = w.kind === "QUESTIONNAIRE" && !option && !section && which !== "secondary";
|
|
3279
|
+
// The submit is gated on the required answers; refused here too, since a
|
|
3280
|
+
// greyed button is only a picture of the rule.
|
|
3281
|
+
if (questionnaire && (w.answersIncomplete || w.questionIndex < w.questions.length - 1)) return s;
|
|
3282
|
+
|
|
3283
|
+
const template = section
|
|
3284
|
+
? section.respondText
|
|
3285
|
+
: option
|
|
3286
|
+
? option.respondText
|
|
3287
|
+
: which === "secondary" ? w.secondaryRespondText : w.primaryRespondText;
|
|
3068
3288
|
if (!template) return s;
|
|
3069
3289
|
|
|
3070
3290
|
// What the owner actually chose. An option's own button answers with
|
|
3071
3291
|
// ITSELF even when nothing is ticked — tapping "Select" on a row IS the
|
|
3072
|
-
// selection — while a footer button speaks for the accumulated set.
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3292
|
+
// selection — while a footer button speaks for the accumulated set. A
|
|
3293
|
+
// questionnaire speaks for EVERY question, not the one on screen.
|
|
3294
|
+
const selected = section
|
|
3295
|
+
? []
|
|
3296
|
+
: questionnaire
|
|
3297
|
+
? [questionnaireAnswers(w)].filter(Boolean)
|
|
3298
|
+
: option && !option.selected
|
|
3299
|
+
? [option.title || option.value]
|
|
3300
|
+
: w.options.filter((o) => o.selected).map((o) => o.title || o.value);
|
|
3076
3301
|
const content = substituteSelection(template, selected).trim();
|
|
3077
3302
|
if (!content) return s;
|
|
3078
3303
|
if (duplicate(s, content)) return s;
|
|
@@ -3468,6 +3693,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
3468
3693
|
widgetPick: (s, arg) => gated(impl.widgetPick(s, arg)),
|
|
3469
3694
|
widgetQuery: (s, arg) => gated(impl.widgetQuery(s, arg)),
|
|
3470
3695
|
widgetAnswer: (s, arg) => gated(impl.widgetAnswer(s, arg)),
|
|
3696
|
+
widgetStep: (s, arg) => gated(impl.widgetStep(s, arg)),
|
|
3471
3697
|
widgetRespond: (s, arg) => impl.widgetRespond(s, arg).then(gated),
|
|
3472
3698
|
widgetPrimary: (s) => impl.widgetPrimary(s).then(gated),
|
|
3473
3699
|
widgetSecondary: (s) => impl.widgetSecondary(s).then(gated),
|
|
@@ -107,6 +107,8 @@ export interface WidgetButton {
|
|
|
107
107
|
* and this
|
|
108
108
|
* model must not grow a second URL check. */
|
|
109
109
|
externalUrl: string;
|
|
110
|
+
/** `mobileAction.screen.id` — a destination the widget footer cannot open. */
|
|
111
|
+
actionScreenId: string;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
/** The normalized widget. One shape for all fifteen arms.
|
|
@@ -275,6 +277,7 @@ export function buttonAt(o: Record<string, unknown> | null, key: string): Widget
|
|
|
275
277
|
// Two spellings for one concept, and both are live in the catalogue:
|
|
276
278
|
// PREMIUM_UPSELL sends `externalLink`, CHART sends `link`.
|
|
277
279
|
externalUrl: stringAt(action, "externalLink") || stringAt(action, "link"),
|
|
280
|
+
actionScreenId: stringAt(objectAt(action?.screen), "id"),
|
|
278
281
|
};
|
|
279
282
|
}
|
|
280
283
|
|