@wix/himalaya-cli 0.1306.0 → 0.1307.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.
Files changed (50) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/wix-gateway.rules.d.ts +5 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/genie-chat/src/index.d.ts +63 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-genie-chat.mjs +81 -0
  4. package/dist/cli.mjs +116 -3
  5. package/guides/sdk-reference.json +36 -0
  6. package/package.json +2 -2
  7. package/templates/beauty-salon-owner/dev/index.ts +15 -27
  8. package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +293 -12
  9. package/templates/beauty-salon-owner/tier5-src/_shared/aria-stream.ts +165 -0
  10. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  11. package/templates/community-club-owner/dev/index.ts +15 -27
  12. package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +293 -12
  13. package/templates/community-club-owner/tier5-src/_shared/aria-stream.ts +165 -0
  14. package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  15. package/templates/events-meetups-owner/dev/index.ts +15 -27
  16. package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +293 -12
  17. package/templates/events-meetups-owner/tier5-src/_shared/aria-stream.ts +165 -0
  18. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  19. package/templates/gym-fitness-owner/dev/index.ts +15 -27
  20. package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +293 -12
  21. package/templates/gym-fitness-owner/tier5-src/_shared/aria-stream.ts +165 -0
  22. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  23. package/templates/nonprofit-cause-owner/dev/index.ts +15 -27
  24. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +293 -12
  25. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-stream.ts +165 -0
  26. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  27. package/templates/owner-lite/dev/index.ts +15 -27
  28. package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +293 -12
  29. package/templates/owner-lite/tier5-src/_shared/aria-stream.ts +165 -0
  30. package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  31. package/templates/podcast-creator-owner/dev/index.ts +15 -27
  32. package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +293 -12
  33. package/templates/podcast-creator-owner/tier5-src/_shared/aria-stream.ts +165 -0
  34. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  35. package/templates/portfolio-creative-owner/dev/index.ts +15 -27
  36. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +293 -12
  37. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-stream.ts +165 -0
  38. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  39. package/templates/restaurant-owner/dev/index.ts +15 -27
  40. package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +293 -12
  41. package/templates/restaurant-owner/tier5-src/_shared/aria-stream.ts +165 -0
  42. package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  43. package/templates/retail-store-owner/dev/index.ts +15 -27
  44. package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +293 -12
  45. package/templates/retail-store-owner/tier5-src/_shared/aria-stream.ts +165 -0
  46. package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +26 -2
  47. package/templates/yoga-studio-owner/dev/index.ts +15 -27
  48. package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +293 -12
  49. package/templates/yoga-studio-owner/tier5-src/_shared/aria-stream.ts +165 -0
  50. 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
 
@@ -56606,7 +56606,8 @@ const SCREENS = runtimeSetting({
56606
56606
  "props": {
56607
56607
  "text": "$item.content",
56608
56608
  "style": "body",
56609
- "maxLines": 50
56609
+ "maxLines": 50,
56610
+ "animateReveal": true
56610
56611
  }
56611
56612
  },
56612
56613
  "modifiers": {
@@ -57114,7 +57115,7 @@ const SCREENS = runtimeSetting({
57114
57115
  }
57115
57116
  },
57116
57117
  {
57117
- "id": "himi_owner_lite_aria.stop",
57118
+ "id": "",
57118
57119
  "body": {
57119
57120
  "package": "himalaya",
57120
57121
  "type": "HStack",
@@ -57130,30 +57131,19 @@ const SCREENS = runtimeSetting({
57130
57131
  }
57131
57132
  },
57132
57133
  {
57133
- "id": "",
57134
- "body": {
57135
- "package": "himalaya",
57136
- "type": "Icon",
57137
- "props": {
57138
- "name": "close",
57139
- "size": 14,
57140
- "tint": "accentPrimary"
57141
- }
57142
- },
57143
- "modifiers": {
57144
- "accessibilityRole": "decorative"
57145
- }
57146
- },
57147
- {
57148
- "id": "",
57134
+ "id": "himi_owner_lite_aria.stop",
57149
57135
  "body": {
57150
57136
  "package": "himalaya",
57151
- "type": "Text",
57137
+ "type": "Button",
57152
57138
  "props": {
57153
- "text": "Stop",
57154
- "style": "footnote",
57155
- "color": "accentPrimary",
57156
- "maxLines": 1
57139
+ "title": "Stop",
57140
+ "style": "link",
57141
+ "size": "sm",
57142
+ "leadingIcon": "close",
57143
+ "fullWidth": false,
57144
+ "action": {
57145
+ "actionId": "tier5.himiOwnerLiteAria.stop"
57146
+ }
57157
57147
  }
57158
57148
  }
57159
57149
  }
@@ -57167,9 +57157,6 @@ const SCREENS = runtimeSetting({
57167
57157
  "top": 4,
57168
57158
  "bottom": 0
57169
57159
  },
57170
- "onTap": {
57171
- "actionId": "tier5.himiOwnerLiteAria.stop"
57172
- },
57173
57160
  "hiddenPath": "$service:himiOwnerLiteAria.stopHidden"
57174
57161
  }
57175
57162
  },
@@ -60897,7 +60884,8 @@ const SCREENS = runtimeSetting({
60897
60884
  "ui",
60898
60885
  "speech",
60899
60886
  "camera",
60900
- "presigned_upload"
60887
+ "presigned_upload",
60888
+ "stream"
60901
60889
  ],
60902
60890
  "minBinaryVersion": "0.1.0",
60903
60891
  "bundleVersion": "0.0.1"