@wix/himalaya-cli 0.1306.0 → 0.1308.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-gateway.rules.d.ts +5 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/genie-chat/src/index.d.ts +63 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-genie-chat.mjs +81 -0
- package/dist/cli.mjs +145 -5
- package/guides/sdk-reference.json +36 -0
- package/package.json +2 -2
- package/templates/beauty-salon-owner/dev/index.ts +15 -27
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/community-club-owner/dev/index.ts +15 -27
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/community-club-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/events-meetups-owner/dev/index.ts +15 -27
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/events-meetups-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/gym-fitness-owner/dev/index.ts +15 -27
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/nonprofit-cause-owner/dev/index.ts +15 -27
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/owner-lite/dev/index.ts +15 -27
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/owner-lite/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/podcast-creator-owner/dev/index.ts +15 -27
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/portfolio-creative-owner/dev/index.ts +15 -27
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/restaurant-owner/dev/index.ts +15 -27
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/restaurant-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/retail-store-owner/dev/index.ts +15 -27
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/retail-store-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
- package/templates/yoga-studio-owner/dev/index.ts +15 -27
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +293 -12
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-stream.ts +165 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// The live reply — what the Aria realtime stream says about the turn in flight.
|
|
2
|
+
//
|
|
3
|
+
// The chat-service publishes a reply's words as side-effect frames while the
|
|
4
|
+
// agent writes it (`decisionStart` / `decisionChunk` / `decisionCompleted`), plus
|
|
5
|
+
// the turn's quick replies (`receivedTags`) a moment after it ends. The
|
|
6
|
+
// transcript, which the worker also polls, only ever holds a SNAPSHOT of the
|
|
7
|
+
// reply, so the stream is what makes the words arrive as they are written.
|
|
8
|
+
//
|
|
9
|
+
// This module folds those frames into one record per turn. It is pure and
|
|
10
|
+
// dependency-free: the conversation machine owns WHEN to fold (it opens one
|
|
11
|
+
// stream per sent turn and resets this model with it), and this owns HOW.
|
|
12
|
+
//
|
|
13
|
+
// THE FOLD is the native Aria app's "accumulated" reducer, rule for rule:
|
|
14
|
+
//
|
|
15
|
+
// - One record per `decisionId`. A turn can have several decisions; their text
|
|
16
|
+
// joins with a blank line (`append`), or a later one replaces the turn's text
|
|
17
|
+
// outright (`override`).
|
|
18
|
+
// - `accumulatedResponse` is the decision's WHOLE text so far, so a frame
|
|
19
|
+
// REPLACES the record's text rather than appending to it. That is what makes
|
|
20
|
+
// the fold idempotent: a frame delivered twice, or a stream re-opened and
|
|
21
|
+
// replayed from the start, converges on the same text.
|
|
22
|
+
// - A frame whose chunk index is not ahead of the latest one seen is stale and
|
|
23
|
+
// dropped — UNLESS it is the completion, which always lands.
|
|
24
|
+
// - A completed decision is final; nothing after it changes it.
|
|
25
|
+
//
|
|
26
|
+
// What this does NOT decide is when a turn is OVER. A completed decision is a
|
|
27
|
+
// hint, not the answer: the transcript (the poll) stays the only thing that ends
|
|
28
|
+
// a turn, exactly as in the native app. Completion only lets the caller ask the
|
|
29
|
+
// transcript right away instead of waiting for its next scheduled read.
|
|
30
|
+
//
|
|
31
|
+
// This file is copied verbatim into every generated starter app, so it explains
|
|
32
|
+
// itself without pointing at anything only this repo has.
|
|
33
|
+
|
|
34
|
+
import { decodeSideEffect, type AriaSideEffect, type QuickReply } from "@himalaya/wix-genie-chat";
|
|
35
|
+
|
|
36
|
+
/** One decision of the turn, as folded so far. */
|
|
37
|
+
interface Decision {
|
|
38
|
+
id: string;
|
|
39
|
+
mode: "append" | "override";
|
|
40
|
+
latest: number;
|
|
41
|
+
completed: boolean;
|
|
42
|
+
text: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What one folded frame changed, so the caller re-renders (or asks the
|
|
46
|
+
* transcript) only when something did. */
|
|
47
|
+
export type LiveChange = "none" | "started" | "text" | "completed" | "tags" | "confirm";
|
|
48
|
+
|
|
49
|
+
export interface LiveReply {
|
|
50
|
+
/** Start over for a new turn: drops every decision and the quick replies of
|
|
51
|
+
* the previous one. `turnId` is the owner's stored message id, empty when the
|
|
52
|
+
* service named none (frames are then accepted for the whole conversation). */
|
|
53
|
+
reset(conversationId: string, turnId: string): void;
|
|
54
|
+
/** Fold one raw stream event. Frames for another conversation, or for a turn
|
|
55
|
+
* other than the current one, change nothing. */
|
|
56
|
+
fold(event: unknown): LiveChange;
|
|
57
|
+
/** The turn's text so far, joined across its decisions. Empty until the first
|
|
58
|
+
* words arrive. */
|
|
59
|
+
text(): string;
|
|
60
|
+
/** Every decision seen has completed (and there was at least one). */
|
|
61
|
+
settled(): boolean;
|
|
62
|
+
/** The quick replies offered for the current turn, in the service's order. */
|
|
63
|
+
quickReplies(): QuickReply[];
|
|
64
|
+
/** How many frames have changed something since the last call — the caller's
|
|
65
|
+
* clock-free way to tell a live stream from a stalled one. */
|
|
66
|
+
takeActivity(): number;
|
|
67
|
+
/** The conversation and turn this model is folding for. */
|
|
68
|
+
readonly conversationId: string;
|
|
69
|
+
readonly turnId: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** How a turn's decisions combine, the native app's rule: `append` joins with a
|
|
73
|
+
* blank line, `override` replaces whatever came before it. */
|
|
74
|
+
function joined(decisions: Decision[]): string {
|
|
75
|
+
let out = "";
|
|
76
|
+
for (const d of decisions) {
|
|
77
|
+
if (!d.text) continue;
|
|
78
|
+
out = d.mode === "override" || !out ? d.text : `${out}\n\n${d.text}`;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createLiveReply(): LiveReply {
|
|
84
|
+
let conv = "";
|
|
85
|
+
let turn = "";
|
|
86
|
+
let order: Decision[] = [];
|
|
87
|
+
let byId = new Map<string, Decision>();
|
|
88
|
+
let replies: QuickReply[] = [];
|
|
89
|
+
let activity = 0;
|
|
90
|
+
|
|
91
|
+
/** Does a frame naming `userMessageId` belong to the current turn? A frame
|
|
92
|
+
* naming NO turn is accepted — the native app routes those to the open turn
|
|
93
|
+
* too — but one naming another turn never is: that is a late answer to a turn
|
|
94
|
+
* the owner has since moved on from. */
|
|
95
|
+
const ours = (userMessageId: string | undefined): boolean =>
|
|
96
|
+
!userMessageId || userMessageId === "unknown" || !turn || userMessageId === turn;
|
|
97
|
+
|
|
98
|
+
const foldDecision = (e: Extract<AriaSideEffect, { kind: "decision" }>): LiveChange => {
|
|
99
|
+
if (!ours(e.userMessageId)) return "none";
|
|
100
|
+
const completed = e.phase === "completed";
|
|
101
|
+
const index = e.chunkIndex ?? -1;
|
|
102
|
+
const existing = byId.get(e.decisionId);
|
|
103
|
+
if (!existing) {
|
|
104
|
+
const d: Decision = { id: e.decisionId, mode: e.streamingMode, latest: index, completed, text: e.accumulatedResponse ?? e.chunk ?? "" };
|
|
105
|
+
byId.set(d.id, d);
|
|
106
|
+
order = [...order, d];
|
|
107
|
+
activity += 1;
|
|
108
|
+
return completed ? "completed" : d.text ? "text" : "started";
|
|
109
|
+
}
|
|
110
|
+
if (existing.completed) return "none";
|
|
111
|
+
if (!completed && e.chunkIndex !== undefined && index <= existing.latest) return "none";
|
|
112
|
+
const before = existing.text;
|
|
113
|
+
existing.latest = Math.max(existing.latest, index);
|
|
114
|
+
existing.completed = completed;
|
|
115
|
+
if (e.accumulatedResponse !== undefined) existing.text = e.accumulatedResponse;
|
|
116
|
+
else if (e.chunk !== undefined) existing.text += e.chunk;
|
|
117
|
+
activity += 1;
|
|
118
|
+
if (completed) return "completed";
|
|
119
|
+
return existing.text !== before ? "text" : "none";
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
get conversationId() { return conv; },
|
|
124
|
+
get turnId() { return turn; },
|
|
125
|
+
reset(conversationId, turnId) {
|
|
126
|
+
conv = conversationId;
|
|
127
|
+
turn = turnId;
|
|
128
|
+
order = [];
|
|
129
|
+
byId = new Map();
|
|
130
|
+
replies = [];
|
|
131
|
+
activity = 0;
|
|
132
|
+
},
|
|
133
|
+
fold(event) {
|
|
134
|
+
const e = decodeSideEffect(event);
|
|
135
|
+
if (!e || !conv) return "none";
|
|
136
|
+
if (e.kind !== "messageChanged" && e.kind !== "unknown" && e.conversationId !== conv) return "none";
|
|
137
|
+
if (e.kind === "messageChanged" && e.conversationId && e.conversationId !== conv) return "none";
|
|
138
|
+
switch (e.kind) {
|
|
139
|
+
case "decision":
|
|
140
|
+
return foldDecision(e);
|
|
141
|
+
case "tags": {
|
|
142
|
+
if (!turn || e.userMessageId !== turn) return "none";
|
|
143
|
+
replies = e.quickReplies;
|
|
144
|
+
activity += 1;
|
|
145
|
+
return "tags";
|
|
146
|
+
}
|
|
147
|
+
case "messageChanged":
|
|
148
|
+
activity += 1;
|
|
149
|
+
return "confirm";
|
|
150
|
+
default:
|
|
151
|
+
// Status lines are decoded but not painted yet; an unknown frame is
|
|
152
|
+
// somebody else's. Neither changes the reply.
|
|
153
|
+
return "none";
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
text: () => joined(order),
|
|
157
|
+
settled: () => order.length > 0 && order.every((d) => d.completed),
|
|
158
|
+
quickReplies: () => replies,
|
|
159
|
+
takeActivity() {
|
|
160
|
+
const n = activity;
|
|
161
|
+
activity = 0;
|
|
162
|
+
return n;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -34,6 +34,8 @@ import { alert } from "@himalaya/ui";
|
|
|
34
34
|
import {
|
|
35
35
|
activeSiteId,
|
|
36
36
|
createAriaChat,
|
|
37
|
+
authoredRealtimeUrl,
|
|
38
|
+
stashRealtimeUrl,
|
|
37
39
|
type AriaActionArg,
|
|
38
40
|
type AriaChatState,
|
|
39
41
|
type AriaDictationResult,
|
|
@@ -53,6 +55,10 @@ const SERVICE = "himiOwnerLiteAria";
|
|
|
53
55
|
// clone's blocking loop, which paints nothing for the length of a turn.
|
|
54
56
|
const FAST_MS = 2000;
|
|
55
57
|
const IDLE_MS = 15000;
|
|
58
|
+
// While the realtime stream is delivering a reply, the transcript is only read to
|
|
59
|
+
// CONFIRM the turn ended — the words come from the stream — so the ladder runs at
|
|
60
|
+
// this slower rate. A whole interval with no frame drops it back to FAST_MS.
|
|
61
|
+
const STREAM_MS = 6000;
|
|
56
62
|
|
|
57
63
|
function prompts(): AriaPromptChip[] {
|
|
58
64
|
return [
|
|
@@ -250,7 +256,13 @@ const core = createAriaChat({
|
|
|
250
256
|
const screenId = screenForUrl(url, allowedTargets());
|
|
251
257
|
return screenId ? { screenId } : null;
|
|
252
258
|
},
|
|
253
|
-
poll: { action: `${SERVICE}.tick`, fastMs: FAST_MS, idleMs: IDLE_MS },
|
|
259
|
+
poll: { action: `${SERVICE}.tick`, fastMs: FAST_MS, idleMs: IDLE_MS, streamMs: STREAM_MS },
|
|
260
|
+
// The realtime stream: the reply's words as they are written, plus the quick
|
|
261
|
+
// replies that follow it. Only where the screen was built with a stream url
|
|
262
|
+
// (authored onAppear load params); everywhere else — and wherever the host cannot
|
|
263
|
+
// open it — the turn is polled exactly as before. The screen must declare
|
|
264
|
+
// "stream" for the host to open it at all.
|
|
265
|
+
stream: { url: authoredRealtimeUrl },
|
|
254
266
|
// Composer dictation, on the platform recognizer: SFSpeechRecognizer,
|
|
255
267
|
// Android SpeechRecognizer, Web Speech. No audio crosses the bridge — only the
|
|
256
268
|
// final transcript. An empty language means the device's own locale, which is
|
|
@@ -319,6 +331,12 @@ const svc = defineService<AriaChatState, {
|
|
|
319
331
|
finishDictation: (s: AriaChatState) => AriaChatState;
|
|
320
332
|
// `undefined` for a result whose session is no longer the live one — see the core.
|
|
321
333
|
dictated: (s: AriaChatState, arg?: AriaDictationResult) => AriaChatState | undefined;
|
|
334
|
+
// The realtime stream's callbacks, named by the `_stream` directive the core
|
|
335
|
+
// returns from a send — never by the descriptor. `undefined` from
|
|
336
|
+
// `streamEvent` for the same reason `tick` has it.
|
|
337
|
+
streamEvent: (s: AriaChatState, event?: unknown) => Promise<AriaChatState | undefined>;
|
|
338
|
+
streamEnded: (s: AriaChatState) => AriaChatState;
|
|
339
|
+
streamFailed: (s: AriaChatState, err?: unknown) => AriaChatState;
|
|
322
340
|
mentionOpen: (s: AriaChatState) => Promise<AriaChatState | undefined>;
|
|
323
341
|
mentionClose: (s: AriaChatState) => AriaChatState;
|
|
324
342
|
mentionBack: (s: AriaChatState) => AriaChatState;
|
|
@@ -334,7 +352,7 @@ const svc = defineService<AriaChatState, {
|
|
|
334
352
|
state: core.initialState,
|
|
335
353
|
actions: {
|
|
336
354
|
// `load` is the onAppear dispatch, so it is where the copy pack arrives.
|
|
337
|
-
load: (s, arg) => { stashCopy(arg); stashTargets(arg); return core.load(s); },
|
|
355
|
+
load: (s, arg) => { stashCopy(arg); stashTargets(arg); stashRealtimeUrl(arg); return core.load(s); },
|
|
338
356
|
submit: core.submit,
|
|
339
357
|
submitPrompt: core.submitPrompt,
|
|
340
358
|
newChat: core.newChat,
|
|
@@ -391,6 +409,12 @@ const svc = defineService<AriaChatState, {
|
|
|
391
409
|
attachmentSettled: core.attachmentSettled,
|
|
392
410
|
removeAttachment: core.removeAttachment,
|
|
393
411
|
retryAttachment: core.retryAttachment,
|
|
412
|
+
// Dispatched by the HOST as the stream delivers, closes or fails. A name
|
|
413
|
+
// the entry does not export is a callback the host silently drops, and the
|
|
414
|
+
// turn would then be polled with nobody noticing the stream was ignored.
|
|
415
|
+
streamEvent: core.streamEvent,
|
|
416
|
+
streamEnded: core.streamEnded,
|
|
417
|
+
streamFailed: core.streamFailed,
|
|
394
418
|
},
|
|
395
419
|
});
|
|
396
420
|
|
|
@@ -56677,7 +56677,8 @@ const SCREENS = runtimeSetting({
|
|
|
56677
56677
|
"props": {
|
|
56678
56678
|
"text": "$item.content",
|
|
56679
56679
|
"style": "body",
|
|
56680
|
-
"maxLines": 50
|
|
56680
|
+
"maxLines": 50,
|
|
56681
|
+
"animateReveal": true
|
|
56681
56682
|
}
|
|
56682
56683
|
},
|
|
56683
56684
|
"modifiers": {
|
|
@@ -57185,7 +57186,7 @@ const SCREENS = runtimeSetting({
|
|
|
57185
57186
|
}
|
|
57186
57187
|
},
|
|
57187
57188
|
{
|
|
57188
|
-
"id": "
|
|
57189
|
+
"id": "",
|
|
57189
57190
|
"body": {
|
|
57190
57191
|
"package": "himalaya",
|
|
57191
57192
|
"type": "HStack",
|
|
@@ -57201,30 +57202,19 @@ const SCREENS = runtimeSetting({
|
|
|
57201
57202
|
}
|
|
57202
57203
|
},
|
|
57203
57204
|
{
|
|
57204
|
-
"id": "",
|
|
57205
|
-
"body": {
|
|
57206
|
-
"package": "himalaya",
|
|
57207
|
-
"type": "Icon",
|
|
57208
|
-
"props": {
|
|
57209
|
-
"name": "close",
|
|
57210
|
-
"size": 14,
|
|
57211
|
-
"tint": "accentPrimary"
|
|
57212
|
-
}
|
|
57213
|
-
},
|
|
57214
|
-
"modifiers": {
|
|
57215
|
-
"accessibilityRole": "decorative"
|
|
57216
|
-
}
|
|
57217
|
-
},
|
|
57218
|
-
{
|
|
57219
|
-
"id": "",
|
|
57205
|
+
"id": "himi_owner_lite_aria.stop",
|
|
57220
57206
|
"body": {
|
|
57221
57207
|
"package": "himalaya",
|
|
57222
|
-
"type": "
|
|
57208
|
+
"type": "Button",
|
|
57223
57209
|
"props": {
|
|
57224
|
-
"
|
|
57225
|
-
"style": "
|
|
57226
|
-
"
|
|
57227
|
-
"
|
|
57210
|
+
"title": "Stop",
|
|
57211
|
+
"style": "link",
|
|
57212
|
+
"size": "sm",
|
|
57213
|
+
"leadingIcon": "close",
|
|
57214
|
+
"fullWidth": false,
|
|
57215
|
+
"action": {
|
|
57216
|
+
"actionId": "tier5.himiOwnerLiteAria.stop"
|
|
57217
|
+
}
|
|
57228
57218
|
}
|
|
57229
57219
|
}
|
|
57230
57220
|
}
|
|
@@ -57238,9 +57228,6 @@ const SCREENS = runtimeSetting({
|
|
|
57238
57228
|
"top": 4,
|
|
57239
57229
|
"bottom": 0
|
|
57240
57230
|
},
|
|
57241
|
-
"onTap": {
|
|
57242
|
-
"actionId": "tier5.himiOwnerLiteAria.stop"
|
|
57243
|
-
},
|
|
57244
57231
|
"hiddenPath": "$service:himiOwnerLiteAria.stopHidden"
|
|
57245
57232
|
}
|
|
57246
57233
|
},
|
|
@@ -60968,7 +60955,8 @@ const SCREENS = runtimeSetting({
|
|
|
60968
60955
|
"ui",
|
|
60969
60956
|
"speech",
|
|
60970
60957
|
"camera",
|
|
60971
|
-
"presigned_upload"
|
|
60958
|
+
"presigned_upload",
|
|
60959
|
+
"stream"
|
|
60972
60960
|
],
|
|
60973
60961
|
"minBinaryVersion": "0.1.0",
|
|
60974
60962
|
"bundleVersion": "0.0.1"
|