@wix/himalaya-cli 0.1039.0 → 0.1041.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/cli.mjs +31 -19
- package/dist/preview-assets.generated.bin +0 -0
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +42 -42
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/beauty-salon-owner/config.ts +2 -2
- package/templates/beauty-salon-owner/dev/index.ts +794 -295
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/branded-lite/dev/index.ts +175 -175
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/community-club/dev/index.ts +28 -28
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/community-club/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/community-club-owner/config.ts +2 -2
- package/templates/community-club-owner/dev/index.ts +794 -295
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/community-club-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/events-meetups/dev/index.ts +21 -21
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/events-meetups-owner/config.ts +2 -2
- package/templates/events-meetups-owner/dev/index.ts +766 -243
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/events-meetups-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/gym-fitness/dev/index.ts +49 -49
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/gym-fitness-owner/config.ts +2 -2
- package/templates/gym-fitness-owner/dev/index.ts +801 -320
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/nonprofit-cause/dev/index.ts +28 -28
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/nonprofit-cause-owner/config.ts +2 -2
- package/templates/nonprofit-cause-owner/dev/index.ts +773 -268
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/owner-lite/config.ts +2 -2
- package/templates/owner-lite/dev/index.ts +802 -322
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/owner-lite/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/owner-lite/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/podcast-creator/dev/index.ts +28 -28
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/podcast-creator-owner/config.ts +2 -2
- package/templates/podcast-creator-owner/dev/index.ts +794 -295
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/portfolio-creative/dev/index.ts +35 -35
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/portfolio-creative-owner/config.ts +2 -2
- package/templates/portfolio-creative-owner/dev/index.ts +794 -295
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/restaurant/dev/index.ts +21 -21
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/restaurant-owner/config.ts +2 -2
- package/templates/restaurant-owner/dev/index.ts +801 -320
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/restaurant-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/retail-store/dev/index.ts +21 -21
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/retail-store-owner/config.ts +2 -2
- package/templates/retail-store-owner/dev/index.ts +787 -270
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/retail-store-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/yoga-studio/dev/index.ts +42 -42
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/yoga-studio-owner/config.ts +2 -2
- package/templates/yoga-studio-owner/dev/index.ts +773 -268
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
|
@@ -230,6 +230,15 @@ export interface AriaChatState {
|
|
|
230
230
|
* Stop that cannot stop anything is worse than no Stop, because the owner
|
|
231
231
|
* taps it, the answer arrives anyway, and the control is what they blame. */
|
|
232
232
|
stopHidden: boolean;
|
|
233
|
+
/** The microphone is open for composer dictation. Set by `dictate`, cleared by
|
|
234
|
+
* `finishDictation` and by the result landing. */
|
|
235
|
+
listening: boolean;
|
|
236
|
+
/** Gates for the composer's mic control and its "Listening…" row, in the
|
|
237
|
+
* `hiddenPath` polarity. Derived in `gated`, like `stopHidden`: the mic is
|
|
238
|
+
* collapsed for a consumer with no `dictation` configured and while it is
|
|
239
|
+
* already listening; the listening row is collapsed whenever it is not. */
|
|
240
|
+
micHidden: boolean;
|
|
241
|
+
listeningHidden: boolean;
|
|
233
242
|
/** Intentionally never true: a `form` submit that returns `ok` makes the host
|
|
234
243
|
* POP the screen. A chat composer must stay put, so the whole surface leaves
|
|
235
244
|
* this false and the bound message list simply re-renders. */
|
|
@@ -317,6 +326,13 @@ export interface AriaChatLabels {
|
|
|
317
326
|
* all — it carries no Wix app binding to sign the chat-service call with.
|
|
318
327
|
* See `isWixAccountUnreachable`, and note what it is NOT: a signed-out user. */
|
|
319
328
|
unavailable?: Resolvable<string>;
|
|
329
|
+
/** Neutral line when dictation ended with nothing to insert. The recognizer
|
|
330
|
+
* reports silence, a denied microphone and a missing engine the SAME way
|
|
331
|
+
* (`cancelled`), so this cannot say which — it names both likely causes. */
|
|
332
|
+
dictationEmpty?: Resolvable<string>;
|
|
333
|
+
/** Error line when the host could not start dictation at all (the speech
|
|
334
|
+
* capability is not registered, or refused the call). */
|
|
335
|
+
dictationFailed?: Resolvable<string>;
|
|
320
336
|
/** The mention picker's copy. See `MentionLabels`. */
|
|
321
337
|
mentionTitle?: Resolvable<string>;
|
|
322
338
|
mentionNoProviders?: Resolvable<string>;
|
|
@@ -349,6 +365,41 @@ export interface AriaChatLabels {
|
|
|
349
365
|
toolFailed?: Resolvable<string>;
|
|
350
366
|
}
|
|
351
367
|
|
|
368
|
+
/** One dictation outcome, handed back to the service by `AriaDictationOptions.deliver`.
|
|
369
|
+
* `session` names the `dictate` tap it answers, so a result for a session that has
|
|
370
|
+
* since been replaced is dropped instead of landing in the composer. */
|
|
371
|
+
export interface AriaDictationResult {
|
|
372
|
+
session: number;
|
|
373
|
+
transcript?: string;
|
|
374
|
+
cancelled?: boolean;
|
|
375
|
+
/** The host refused or threw — as opposed to hearing nothing. */
|
|
376
|
+
failed?: boolean;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Composer dictation, over whatever speech binding the consumer has.
|
|
380
|
+
*
|
|
381
|
+
* `listen` is NOT awaited by the action that starts it: an action's state only
|
|
382
|
+
* paints when it returns, so awaiting a whole utterance would leave the mic tap
|
|
383
|
+
* looking dead until the owner stopped talking. `dictate` returns "Listening…"
|
|
384
|
+
* at once, and the result re-enters as a FRESH dispatch through `deliver` —
|
|
385
|
+
* which is the bundle's own `svc.dictated`, since only the bundle holds `svc`. */
|
|
386
|
+
export interface AriaDictationOptions {
|
|
387
|
+
/** Record one utterance and resolve with what was heard. Must REJECT when the
|
|
388
|
+
* host cannot dictate at all, so that is told apart from hearing nothing. */
|
|
389
|
+
listen(): Promise<{ transcript: string; cancelled: boolean }>;
|
|
390
|
+
/** Close the microphone. On iOS this also resolves `listen` with what had been
|
|
391
|
+
* heard so far, which is what makes "Done" keep the owner's words. */
|
|
392
|
+
stop(): Promise<void>;
|
|
393
|
+
/** Re-enter the service with the outcome — `(r) => { void svc.dictated(r); }`. */
|
|
394
|
+
deliver(result: AriaDictationResult): void;
|
|
395
|
+
/** Can this host recognize speech at all? Asked once, on the first `load`.
|
|
396
|
+
* `false` (or a rejection) collapses the mic: a control that can only ever
|
|
397
|
+
* answer "Didn't catch that" — a browser with no SpeechRecognition, a macOS
|
|
398
|
+
* build whose recognizer is a stub — sends the owner to fix a microphone
|
|
399
|
+
* permission that is not the problem. Omitted ⇒ assumed available. */
|
|
400
|
+
available?(): Promise<boolean>;
|
|
401
|
+
}
|
|
402
|
+
|
|
352
403
|
/** What the composer needs from the device to attach a photo. Supplied by the
|
|
353
404
|
* app's adapter, because both are capabilities the SCREEN has to declare — the
|
|
354
405
|
* shared core imports neither, so a consumer without attachments (the clone)
|
|
@@ -441,6 +492,9 @@ export interface AriaChatOptions {
|
|
|
441
492
|
* authoritative and account-scoped by the signed instance; losing the offline
|
|
442
493
|
* fallback is a far smaller cost than that. */
|
|
443
494
|
localHistory?: boolean;
|
|
495
|
+
/** Composer dictation. Omit it and `dictate` is inert and `micHidden`
|
|
496
|
+
* stays true, so a descriptor that never adds a mic is unaffected. */
|
|
497
|
+
dictation?: AriaDictationOptions;
|
|
444
498
|
}
|
|
445
499
|
|
|
446
500
|
/** The argument shape every action tolerates. The clone's generic per-item
|
|
@@ -1235,6 +1289,16 @@ export interface AriaChatCore {
|
|
|
1235
1289
|
widgetPick(s: AriaChatState, arg?: AriaActionArg): AriaChatState;
|
|
1236
1290
|
/** Answer one QUESTIONNAIRE question. */
|
|
1237
1291
|
widgetAnswer(s: AriaChatState, arg?: AriaActionArg): AriaChatState;
|
|
1292
|
+
/** The composer's mic: open the recognizer and show "Listening…". Returns at
|
|
1293
|
+
* once; the transcript arrives later through `dictated`. */
|
|
1294
|
+
dictate(s: AriaChatState): AriaChatState;
|
|
1295
|
+
/** "Done" on the listening row: close the microphone. The session stays live,
|
|
1296
|
+
* so what the recognizer had already heard is still inserted when it lands. */
|
|
1297
|
+
finishDictation(s: AriaChatState): AriaChatState;
|
|
1298
|
+
/** A dictation outcome (see `AriaDictationOptions.deliver`). Resolves to
|
|
1299
|
+
* `undefined` — leave the state untouched — for a session that is no longer
|
|
1300
|
+
* the live one. */
|
|
1301
|
+
dictated(s: AriaChatState, arg?: unknown): AriaChatState | undefined;
|
|
1238
1302
|
/** Open the mention picker at the category level, listing the site's
|
|
1239
1303
|
* installed providers the first time (per business). `undefined` when the
|
|
1240
1304
|
* business switched while the list was being fetched. */
|
|
@@ -1300,6 +1364,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1300
1364
|
const unavailableText = () => res(
|
|
1301
1365
|
o.labels?.unavailable ?? `${assistant()} can't reach your Wix account from here.`,
|
|
1302
1366
|
);
|
|
1367
|
+
const dictationEmptyText = () => res(
|
|
1368
|
+
o.labels?.dictationEmpty ?? "Didn't catch that. Check microphone access and try again.",
|
|
1369
|
+
);
|
|
1370
|
+
const dictationFailedText = () => res(o.labels?.dictationFailed ?? "Dictation isn't available right now.");
|
|
1303
1371
|
const attachUploadingText = () => res(o.labels?.attachUploading ?? "Uploading…");
|
|
1304
1372
|
const attachFailedText = () => res(o.labels?.attachFailed ?? "Couldn't upload");
|
|
1305
1373
|
const chipLabels = () => ({ uploading: attachUploadingText(), failed: attachFailedText() });
|
|
@@ -1434,6 +1502,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1434
1502
|
messages: [], prompts: [], _convId: "", _siteId: "", _pending: "", _pendingRefs: "", _pendingAttach: [],
|
|
1435
1503
|
_pendingAfter: 0, _pendingId: "", _replyAfter: 0, _srvUsers: 0, _srvAssistants: 0,
|
|
1436
1504
|
_abortId: "", _turnId: "", stopHidden: true, ok: false,
|
|
1505
|
+
listening: false, micHidden: !o.dictation, listeningHidden: true,
|
|
1437
1506
|
widget: NO_WIDGET, mention: mentionViewNow,
|
|
1438
1507
|
attachments: [], attachmentsHidden: true,
|
|
1439
1508
|
};
|
|
@@ -1658,6 +1727,77 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1658
1727
|
let draft = "";
|
|
1659
1728
|
let draftSeq = 0;
|
|
1660
1729
|
|
|
1730
|
+
/** Dictation sessions: a counter minted per `dictate` tap, and the one whose
|
|
1731
|
+
* result may still land (0 = none).
|
|
1732
|
+
*
|
|
1733
|
+
* A latch rather than state for the same reason `inFlightSend` is one: the
|
|
1734
|
+
* result arrives as a fresh dispatch handed the CURRENT state, and the only
|
|
1735
|
+
* way to tell it is the answer to an older tap — one the recognizer is still
|
|
1736
|
+
* finishing after the owner started another — is to remember which tap is
|
|
1737
|
+
* live here. Without it a slow recognizer's late result lands in the
|
|
1738
|
+
* composer on top of the one the owner is dictating now. */
|
|
1739
|
+
let dictationSession = 0;
|
|
1740
|
+
let dictationLive = 0;
|
|
1741
|
+
/** The live session was ended by the owner's own "Done". Its result is still
|
|
1742
|
+
* inserted, but an EMPTY one is not reported: they stopped it, and a
|
|
1743
|
+
* recognizer that does not answer a stop (Android's resolves only at its
|
|
1744
|
+
* safety cap, 30s later) would otherwise put "Didn't catch that" on screen
|
|
1745
|
+
* long after anyone was asking. */
|
|
1746
|
+
let dictationStopped = false;
|
|
1747
|
+
/** Sessions the owner ended with "Done" and then SUPERSEDED with a new tap
|
|
1748
|
+
* before their answers landed. Their words are still theirs: Done promises
|
|
1749
|
+
* to keep what was heard, and quick re-taps must not break that — on Android
|
|
1750
|
+
* the new `recognizeOnce` is exactly what settles the old recognizer, so its
|
|
1751
|
+
* answer arrives right as the new session opens. A SET, not one slot: Done →
|
|
1752
|
+
* Dictate → Done → Dictate leaves two outstanding, and a single slot dropped
|
|
1753
|
+
* the first. Each is appended as it lands, silently, without touching the
|
|
1754
|
+
* live session's `listening`, and leaves the set when it does. */
|
|
1755
|
+
const dictationFinishing = new Set<number>();
|
|
1756
|
+
/** The last dictated insertion: the draft it was built on and the draft it
|
|
1757
|
+
* produced — see `insertHeard`. Forgotten on any edit, send or fresh chat,
|
|
1758
|
+
* so it can only ever bridge answers that have not committed yet. */
|
|
1759
|
+
let lastInsert: { from: string; to: string } | null = null;
|
|
1760
|
+
/** The host's answer to `dictation.available`, once asked. `null` until then. */
|
|
1761
|
+
let dictationAvailable: boolean | null = null;
|
|
1762
|
+
const probeDictation = async (): Promise<void> => {
|
|
1763
|
+
if (!o.dictation?.available || dictationAvailable !== null) return;
|
|
1764
|
+
try { dictationAvailable = (await o.dictation.available()) === true; } catch { dictationAvailable = false; }
|
|
1765
|
+
};
|
|
1766
|
+
/** Heard words INTO the draft, never sent: the owner reads what the
|
|
1767
|
+
* recognizer made of them before it goes anywhere. Appended to what they had
|
|
1768
|
+
* already typed. Dictation is an edit to the draft, so it moves the draft
|
|
1769
|
+
* latch exactly as a keystroke does — `withDraft` must not put the old text
|
|
1770
|
+
* back. */
|
|
1771
|
+
const insertHeard = (s: AriaChatState, heard: string): string => {
|
|
1772
|
+
const current = s.input ?? "";
|
|
1773
|
+
// Two answers can land in the SAME turn — parked Done sessions settling
|
|
1774
|
+
// together — and each `dictated` is handed the state as it was before
|
|
1775
|
+
// either committed. Built on that snapshot, the second would overwrite the
|
|
1776
|
+
// first's words. So when the snapshot is still exactly what the previous
|
|
1777
|
+
// insertion started from, build on what that insertion produced instead.
|
|
1778
|
+
const base = lastInsert && current === lastInsert.from ? lastInsert.to : current;
|
|
1779
|
+
const typed = base.replace(/\s+$/, "");
|
|
1780
|
+
const input = typed ? `${typed} ${heard}` : heard;
|
|
1781
|
+
lastInsert = { from: current, to: input };
|
|
1782
|
+
draft = input;
|
|
1783
|
+
draftSeq += 1;
|
|
1784
|
+
return input;
|
|
1785
|
+
};
|
|
1786
|
+
/** A send or a fresh chat ends dictation. The words belong to the draft the
|
|
1787
|
+
* owner was writing THEN; a transcript that lands after they sent it, or in
|
|
1788
|
+
* another conversation's composer, reads as a duplicate draft ready to send
|
|
1789
|
+
* twice. So the live session is retired (its late answer will be dropped)
|
|
1790
|
+
* and a still-open microphone is closed. Returns the state fields to spread. */
|
|
1791
|
+
const retireDictation = (): { listening: false } => {
|
|
1792
|
+
dictationFinishing.clear();
|
|
1793
|
+
lastInsert = null;
|
|
1794
|
+
if (dictationLive) {
|
|
1795
|
+
dictationLive = 0;
|
|
1796
|
+
o.dictation?.stop().catch(() => { /* already closed */ });
|
|
1797
|
+
}
|
|
1798
|
+
return { listening: false };
|
|
1799
|
+
};
|
|
1800
|
+
|
|
1661
1801
|
/** The client-tool exchange the current turn is in the middle of, if any.
|
|
1662
1802
|
*
|
|
1663
1803
|
* While it runs, the agent is WAITING ON US — for the owner's consent, say —
|
|
@@ -2037,6 +2177,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2037
2177
|
// tap that answers a question the owner has already moved past — the same
|
|
2038
2178
|
// reason a scrollback widget keeps its prose and loses its controls.
|
|
2039
2179
|
widget: NO_WIDGET,
|
|
2180
|
+
...retireDictation(),
|
|
2040
2181
|
};
|
|
2041
2182
|
inFlightSend = content;
|
|
2042
2183
|
// Held outside the try so a FAILED send still reports the conversation it
|
|
@@ -2313,9 +2454,16 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2313
2454
|
// `_pending` rather than `turnInFlight` — see `regenerate` for why `status`
|
|
2314
2455
|
// is not trusted to have been cleared.
|
|
2315
2456
|
const replyActionsHidden = !(lastReply(s) && !s._pending);
|
|
2457
|
+
// The dictation pair is derived here for the same reason: two exact
|
|
2458
|
+
// negations each maintained by hand is how a screen ends up with both the
|
|
2459
|
+
// mic and "Listening…" on it, or neither.
|
|
2460
|
+
const listening = s.listening === true;
|
|
2461
|
+
const micHidden = !o.dictation || dictationAvailable === false || listening;
|
|
2462
|
+
const listeningHidden = !listening;
|
|
2316
2463
|
const actioned = s.stopHidden === hidden && s.replyActionsHidden === replyActionsHidden
|
|
2464
|
+
&& s.micHidden === micHidden && s.listeningHidden === listeningHidden
|
|
2317
2465
|
? s
|
|
2318
|
-
: { ...s, stopHidden: hidden, replyActionsHidden };
|
|
2466
|
+
: { ...s, stopHidden: hidden, replyActionsHidden, micHidden, listeningHidden };
|
|
2319
2467
|
// The attachment projection rides the same exit, for the same reason: one
|
|
2320
2468
|
// place that every action's result passes through, so no reducer can hand
|
|
2321
2469
|
// back a stale copy of the chips. Seeded first, so the very first action of
|
|
@@ -2557,6 +2705,9 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2557
2705
|
// still happened.
|
|
2558
2706
|
draft = next;
|
|
2559
2707
|
draftSeq += 1;
|
|
2708
|
+
// The owner's own edit: whatever a previous insertion produced is theirs
|
|
2709
|
+
// to change now, and must not be built on again.
|
|
2710
|
+
lastInsert = null;
|
|
2560
2711
|
return next === s.input ? s : { ...s, input: next };
|
|
2561
2712
|
},
|
|
2562
2713
|
|
|
@@ -2609,6 +2760,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2609
2760
|
// cancel a poll, so the only way to stop the 2s cadence is to re-arm it
|
|
2610
2761
|
// slow — do that whenever this interrupted something.
|
|
2611
2762
|
const wasMidTurn = !!s._pending || s.status === "sending";
|
|
2763
|
+
const retired = retireDictation();
|
|
2612
2764
|
const slow = (next: AriaChatState): AriaChatState =>
|
|
2613
2765
|
wasMidTurn && o.poll ? cadence(next, o.poll.idleMs, null) : next;
|
|
2614
2766
|
try {
|
|
@@ -2622,11 +2774,11 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2622
2774
|
// and the old observed counts back — the fresh chat snapping back to the
|
|
2623
2775
|
// previous one, which reads as ➕ having done nothing.
|
|
2624
2776
|
turnSeq += 1;
|
|
2625
|
-
return slow({ ...s, status: "ready", _convId: conversationIdOf(res), _siteId: site, input: "", statusText: "", error: "",
|
|
2777
|
+
return slow({ ...s, ...retired, status: "ready", _convId: conversationIdOf(res), _siteId: site, input: "", statusText: "", error: "",
|
|
2626
2778
|
_pending: "", _abortId: "", _turnId: "", copyNote: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
|
|
2627
2779
|
} catch (e) {
|
|
2628
2780
|
turnSeq += 1; // see above — a failed create commits state too
|
|
2629
|
-
return slow({ ...s, status: "ready", _convId: "", _siteId: site, input: "", ...failureSlots(e, unavailableText),
|
|
2781
|
+
return slow({ ...s, ...retired, status: "ready", _convId: "", _siteId: site, input: "", ...failureSlots(e, unavailableText),
|
|
2630
2782
|
_pending: "", _abortId: "", _turnId: "", copyNote: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
|
|
2631
2783
|
}
|
|
2632
2784
|
},
|
|
@@ -2960,6 +3112,77 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2960
3112
|
? openWidgetUrl(s, s.widget.secondaryUrl)
|
|
2961
3113
|
: impl.widgetRespond(s, { value: "secondary" }),
|
|
2962
3114
|
|
|
3115
|
+
dictate: (s) => {
|
|
3116
|
+
if (!o.dictation || s.listening) return s;
|
|
3117
|
+
const session = ++dictationSession;
|
|
3118
|
+
// Only a session the owner closed with Done can still be live here
|
|
3119
|
+
// (`listening` refuses a tap otherwise), and its answer is still owed.
|
|
3120
|
+
if (dictationLive && dictationStopped) dictationFinishing.add(dictationLive);
|
|
3121
|
+
dictationLive = session;
|
|
3122
|
+
dictationStopped = false;
|
|
3123
|
+
const deliver = (r: Omit<AriaDictationResult, "session">) => {
|
|
3124
|
+
try { o.dictation!.deliver({ session, ...r }); } catch { /* the bundle is gone */ }
|
|
3125
|
+
};
|
|
3126
|
+
// Deliberately not awaited — see `AriaDictationOptions`.
|
|
3127
|
+
o.dictation.listen().then(
|
|
3128
|
+
(r) => deliver({ transcript: r.transcript, cancelled: r.cancelled }),
|
|
3129
|
+
() => deliver({ failed: true }),
|
|
3130
|
+
);
|
|
3131
|
+
// A new attempt retires the previous attempt's own messages, and nothing
|
|
3132
|
+
// else: the status line may be saying "Aria is typing…", which is the
|
|
3133
|
+
// turn's, and the error slot may hold a failed send the owner still needs.
|
|
3134
|
+
return {
|
|
3135
|
+
...s,
|
|
3136
|
+
listening: true,
|
|
3137
|
+
statusText: s.statusText === dictationEmptyText() ? "" : s.statusText,
|
|
3138
|
+
error: s.error === dictationFailedText() ? "" : s.error,
|
|
3139
|
+
};
|
|
3140
|
+
},
|
|
3141
|
+
|
|
3142
|
+
finishDictation: (s) => {
|
|
3143
|
+
if (!s.listening) return s;
|
|
3144
|
+
// Close the mic without retiring the session: the recognizer answers with
|
|
3145
|
+
// what it had already heard, and dropping that would make "Done" throw the
|
|
3146
|
+
// owner's words away.
|
|
3147
|
+
dictationStopped = true;
|
|
3148
|
+
if (o.dictation) o.dictation.stop().catch(() => { /* already closed */ });
|
|
3149
|
+
return { ...s, listening: false };
|
|
3150
|
+
},
|
|
3151
|
+
|
|
3152
|
+
dictated: (s, arg) => {
|
|
3153
|
+
const r = (arg && typeof arg === "object" ? arg : {}) as Partial<AriaDictationResult>;
|
|
3154
|
+
const session = Number(r.session);
|
|
3155
|
+
// A Done session superseded by a newer tap: keep its words, and nothing
|
|
3156
|
+
// else. Silent on nothing-heard or failure — the owner has moved on to the
|
|
3157
|
+
// next utterance — and `listening` belongs to the live session.
|
|
3158
|
+
if (session && dictationFinishing.has(session)) {
|
|
3159
|
+
dictationFinishing.delete(session);
|
|
3160
|
+
const words = !r.failed && !r.cancelled && typeof r.transcript === "string" ? r.transcript.trim() : "";
|
|
3161
|
+
return words ? { ...s, input: insertHeard(s, words) } : undefined;
|
|
3162
|
+
}
|
|
3163
|
+
// Not the live session: a retired tap's recognizer finishing late (a send
|
|
3164
|
+
// or a new chat ended it), or a result for a worker instance that no
|
|
3165
|
+
// longer exists. Leave everything alone.
|
|
3166
|
+
if (!session || session !== dictationLive) return undefined;
|
|
3167
|
+
dictationLive = 0;
|
|
3168
|
+
const done: AriaChatState = { ...s, listening: false };
|
|
3169
|
+
// Into the error slot only when it is free (or already ours): a failed send
|
|
3170
|
+
// the owner still has to act on outranks a mic that did not start.
|
|
3171
|
+
if (r.failed) return s.error && s.error !== dictationFailedText() ? done : { ...done, error: dictationFailedText() };
|
|
3172
|
+
const heard = typeof r.transcript === "string" ? r.transcript.trim() : "";
|
|
3173
|
+
if (r.cancelled || !heard) {
|
|
3174
|
+
// Neutral, and only when the status line is free: "Aria is typing…"
|
|
3175
|
+
// belongs to a turn in flight and is not ours to overwrite. Silent
|
|
3176
|
+
// after the owner's own Done — see `dictationStopped`.
|
|
3177
|
+
return turnInFlight(s) || dictationStopped ? done : { ...done, statusText: dictationEmptyText() };
|
|
3178
|
+
}
|
|
3179
|
+
return {
|
|
3180
|
+
...done,
|
|
3181
|
+
input: insertHeard(s, heard),
|
|
3182
|
+
statusText: s.statusText === dictationEmptyText() ? "" : s.statusText,
|
|
3183
|
+
};
|
|
3184
|
+
},
|
|
3185
|
+
|
|
2963
3186
|
openLink: async (s, arg) => {
|
|
2964
3187
|
const url = typeof arg === "string" ? arg.trim() : (arg?.url ?? arg?.id ?? "").trim();
|
|
2965
3188
|
if (!url) return s;
|
|
@@ -3215,7 +3438,14 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
3215
3438
|
// Every action's result goes through the Stop gate on its way out.
|
|
3216
3439
|
return {
|
|
3217
3440
|
initialState,
|
|
3218
|
-
|
|
3441
|
+
// A `listening: true` that no session in THIS worker owns was persisted by
|
|
3442
|
+
// an earlier process: the recognizer it described is gone, and leaving the
|
|
3443
|
+
// flag up would hide the mic for good.
|
|
3444
|
+
load: async (s) => {
|
|
3445
|
+
const r = await impl.load(s);
|
|
3446
|
+
await probeDictation();
|
|
3447
|
+
return gated(r.listening && !dictationLive ? { ...r, listening: false } : r);
|
|
3448
|
+
},
|
|
3219
3449
|
submit: (s, arg) => impl.submit(s, arg).then(gated),
|
|
3220
3450
|
submitPrompt: (s, arg) => impl.submitPrompt(s, arg).then(gated),
|
|
3221
3451
|
newChat: (s) => impl.newChat(s).then(gated),
|
|
@@ -3235,6 +3465,9 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
3235
3465
|
widgetRespond: (s, arg) => impl.widgetRespond(s, arg).then(gated),
|
|
3236
3466
|
widgetPrimary: (s) => impl.widgetPrimary(s).then(gated),
|
|
3237
3467
|
widgetSecondary: (s) => impl.widgetSecondary(s).then(gated),
|
|
3468
|
+
dictate: (s) => gated(impl.dictate(s)),
|
|
3469
|
+
finishDictation: (s) => gated(impl.finishDictation(s)),
|
|
3470
|
+
dictated: (s, arg) => { const r = impl.dictated(s, arg); return r ? gated(r) : r; },
|
|
3238
3471
|
mentionOpen: (s) => impl.mentionOpen(s).then((r) => (r ? gated(r) : r)),
|
|
3239
3472
|
mentionClose: (s) => gated(impl.mentionClose(s)),
|
|
3240
3473
|
mentionBack: (s) => gated(impl.mentionBack(s)),
|
|
@@ -168,6 +168,8 @@ export function projectCollection(source: CollectionRow[], config: CollectionCon
|
|
|
168
168
|
items: visible.map(row => ({ ...row, collectionId: collectionId(row), collectionTitle: collectionTitle(row, config),
|
|
169
169
|
collectionMeta: [...new Set((config.metadata ?? ['total', 'statusLabel', 'price', 'priceLine', 'dateLine', 'meta']).filter(key => key !== config.subtitle).map(key => scalar(row[key])).filter(Boolean))].join(' · '),
|
|
170
170
|
collectionSelectLabel: prefs.selectedIds.includes(collectionId(row)) ? 'Selected' : 'Select',
|
|
171
|
+
// The save toggle names the action it WILL take (the service localizes it).
|
|
172
|
+
collectionSaveLabel: prefs.savedIds.includes(collectionId(row)) ? 'Remove from saved' : 'Save',
|
|
171
173
|
collectionSubtitle: scalar(row[config.subtitle ?? '']) || scalar(row.subtitle) || scalar(row.description) || scalar(row.meta),
|
|
172
174
|
collectionImage: scalar(row[config.image ?? '']), collectionNoImage: !scalar(row[config.image ?? '']), collectionHasImage: !!scalar(row[config.image ?? '']),
|
|
173
175
|
collectionSaved: prefs.savedIds.includes(collectionId(row)), collectionNotSaved: !prefs.savedIds.includes(collectionId(row)), collectionSelected: prefs.selectedIds.includes(collectionId(row)),
|
|
@@ -123,7 +123,12 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
123
123
|
pendingFilter = '';
|
|
124
124
|
}
|
|
125
125
|
const projected = projectCollection(rows, config, prefs, !!backend && config.backendQuery === true, known);
|
|
126
|
-
projected.items.forEach(row => {
|
|
126
|
+
projected.items.forEach(row => {
|
|
127
|
+
row.collectionSelectLabel = row.collectionSelected ? copyParam('collection.selected', 'Selected') : copyParam('collection.select', 'Select');
|
|
128
|
+
// the save toggle names the action it WILL take for this row. The
|
|
129
|
+
// old static "Save / unsave" made the owner work out the row's state.
|
|
130
|
+
row.collectionSaveLabel = row.collectionSaved ? copyParam('collection.unsave', 'Remove from saved') : copyParam('collection.save', 'Save');
|
|
131
|
+
});
|
|
127
132
|
if (previewId && !rows.some(row => collectionId(row) === previewId)) previewId = '';
|
|
128
133
|
const filterChoices = [{ id: 'all', label: copyParam('collection.all', 'All') }, { id: 'saved', label: copyParam('collection.saved', 'Saved') },
|
|
129
134
|
...projected.filters.map(label => ({ id: `value:${label}`, label: copyParam(`collection.filter.${label}`, label) }))];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Pure seams of the owner-lite Account worker — no @himalaya/* imports, so a
|
|
2
|
+
// plain node test can load them without the bundle build's module aliases.
|
|
3
|
+
|
|
4
|
+
export interface AccountState {
|
|
5
|
+
status: "loading" | "ready";
|
|
6
|
+
/** "Name · email", the email alone, or "" (the line collapses). */
|
|
7
|
+
ownerLine: string;
|
|
8
|
+
/** "Version 1.2.0 (34)", or "" when the host cannot say. */
|
|
9
|
+
versionLine: string;
|
|
10
|
+
/** Why the owner line is empty, when the identity read failed; "" otherwise. */
|
|
11
|
+
error: string;
|
|
12
|
+
supportUrl: string;
|
|
13
|
+
privacyUrl: string;
|
|
14
|
+
termsUrl: string;
|
|
15
|
+
/** hiddenPath flags — TRUE hides, so each is phrased negatively. */
|
|
16
|
+
noSupport: boolean;
|
|
17
|
+
noPrivacy: boolean;
|
|
18
|
+
noTerms: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Only absolute http(s) URLs are openable; anything else is treated as absent. */
|
|
22
|
+
export function safeUrl(v: unknown): string {
|
|
23
|
+
if (typeof v !== "string") return "";
|
|
24
|
+
const t = v.trim();
|
|
25
|
+
return /^https?:\/\/[^\s]+$/i.test(t) ? t : "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** "Name · email" when both are known, whichever one is when only one is, else "". */
|
|
29
|
+
export function ownerLineOf(name: string, email: string): string {
|
|
30
|
+
const n = name.trim(), e = email.trim();
|
|
31
|
+
if (n && e && n !== e) return `${n} · ${e}`;
|
|
32
|
+
return n || e;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function shapeAccount(s: Partial<AccountState>): AccountState {
|
|
36
|
+
const supportUrl = safeUrl(s.supportUrl), privacyUrl = safeUrl(s.privacyUrl), termsUrl = safeUrl(s.termsUrl);
|
|
37
|
+
return {
|
|
38
|
+
status: s.status ?? "loading",
|
|
39
|
+
ownerLine: s.ownerLine ?? "",
|
|
40
|
+
versionLine: s.versionLine ?? "",
|
|
41
|
+
error: s.error ?? "",
|
|
42
|
+
supportUrl, privacyUrl, termsUrl,
|
|
43
|
+
noSupport: !supportUrl, noPrivacy: !privacyUrl, noTerms: !termsUrl,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -18,6 +18,27 @@ export const DEMO_NOTE =
|
|
|
18
18
|
|
|
19
19
|
export const DEMO_SAVE_NOTE = "Demo mode — changes aren’t saved in this preview.";
|
|
20
20
|
|
|
21
|
+
/** The state patch for a write refused because this is the demo preview. A demo
|
|
22
|
+
* refusal is a MODE, not an error: no red error line — the neutral info banner
|
|
23
|
+
* (already on screen before the owner typed anything) switches to the save
|
|
24
|
+
* note, so the tap still visibly answers. `saveNote` is the (copy-pack) text. */
|
|
25
|
+
export function demoRefusalPatch(saveNote: string = DEMO_SAVE_NOTE): { status: "idle"; error: ""; savedMessage: ""; demoNote: string } {
|
|
26
|
+
return { status: "idle", error: "", savedMessage: "", demoNote: saveNote || DEMO_SAVE_NOTE };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** "€1,234.50" / "12,50" / " 10 " → "1234.50" / "12.50" / "10": digits and one
|
|
30
|
+
* decimal point — what an editable price field accepts and a price write needs.
|
|
31
|
+
* A separator followed by exactly 1–2 digits is the decimal; any other groups. */
|
|
32
|
+
export function editablePrice(raw: string): string {
|
|
33
|
+
const t = raw.replace(/[^\d.,]/g, "");
|
|
34
|
+
if (!t) return "";
|
|
35
|
+
const lastSep = Math.max(t.lastIndexOf("."), t.lastIndexOf(","));
|
|
36
|
+
if (lastSep >= 0 && /^\d{1,2}$/.test(t.slice(lastSep + 1))) {
|
|
37
|
+
return `${t.slice(0, lastSep).replace(/[.,]/g, "")}.${t.slice(lastSep + 1)}`;
|
|
38
|
+
}
|
|
39
|
+
return t.replace(/[.,]/g, "");
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
export const DEMO_IDS = ["demo-1", "demo-2", "demo-3"] as const;
|
|
22
43
|
|
|
23
44
|
export interface DemoProduct {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Tier-5 bundle: __HIMI_APP_NAME__ — Account.
|
|
2
|
+
//
|
|
3
|
+
// The Account screen used to be a static SDUI descriptor whose subtitle was the
|
|
4
|
+
// literal "Wix owner (user)" — a role string, not a person. A descriptor cannot
|
|
5
|
+
// know who is signed in; a worker can. This one reads:
|
|
6
|
+
// • the signed-in Wix user (`getMyData`, the same users.wix.com read
|
|
7
|
+
// wix-owner-profile uses) → "Name · email" under the title;
|
|
8
|
+
// • the app's real version through @himalaya/app-info (never a baked literal,
|
|
9
|
+
// which goes stale the moment the binary is bumped);
|
|
10
|
+
// • the help / privacy / terms links, which arrive as onAppear params baked
|
|
11
|
+
// from provisioning.json `identity` — so a branded template ships ITS links,
|
|
12
|
+
// and `openLink` can only ever open one of those three URLs.
|
|
13
|
+
//
|
|
14
|
+
// Every read is best-effort: an identity or version the host cannot answer
|
|
15
|
+
// collapses its line rather than rendering an error on a settings screen.
|
|
16
|
+
|
|
17
|
+
import { defineService } from "@himalaya/state";
|
|
18
|
+
import { getMyData } from "@himalaya/wix-users";
|
|
19
|
+
import { displayVersion } from "@himalaya/app-info";
|
|
20
|
+
import { open } from "@himalaya/open-url";
|
|
21
|
+
import { stashCopy, copyTemplate } from "../_shared/himi-copy.js";
|
|
22
|
+
import { ownerLineOf, shapeAccount, type AccountState } from "../_shared/owner-account.js";
|
|
23
|
+
|
|
24
|
+
type LinkKind = "support" | "privacy" | "terms";
|
|
25
|
+
|
|
26
|
+
const obj = (v: unknown): Record<string, unknown> => (v && typeof v === "object" ? v as Record<string, unknown> : {});
|
|
27
|
+
|
|
28
|
+
const svc = defineService<AccountState, {
|
|
29
|
+
load: (s: AccountState, arg?: unknown) => Promise<AccountState>;
|
|
30
|
+
openLink: (s: AccountState, arg?: unknown) => Promise<AccountState>;
|
|
31
|
+
}>("himiOwnerLiteAccount", {
|
|
32
|
+
state: shapeAccount({}),
|
|
33
|
+
actions: {
|
|
34
|
+
load: async (s, arg) => {
|
|
35
|
+
stashCopy(arg);
|
|
36
|
+
const p = obj(arg);
|
|
37
|
+
const links = { supportUrl: String(p.supportUrl ?? s.supportUrl), privacyUrl: String(p.privacyUrl ?? s.privacyUrl), termsUrl: String(p.termsUrl ?? s.termsUrl) };
|
|
38
|
+
let ownerLine = "";
|
|
39
|
+
// Why the identity line is missing, when it is. Kept in state (the failure is
|
|
40
|
+
// modelled, not swallowed) but not painted: a settings screen that cannot name
|
|
41
|
+
// its owner still works, so the line collapses rather than showing an error.
|
|
42
|
+
let error: string = "";
|
|
43
|
+
try {
|
|
44
|
+
const d = await getMyData();
|
|
45
|
+
// The PROFILE name only — never the userName handle, an account nickname
|
|
46
|
+
// ("apptestercoowner"): exactly the kind of placeholder this line replaces.
|
|
47
|
+
const name = [d.profile?.firstName, d.profile?.lastName].filter(Boolean).join(" ");
|
|
48
|
+
ownerLine = ownerLineOf(name, String(d.email || d.contactEmail || ""));
|
|
49
|
+
} catch (e) {
|
|
50
|
+
error = String((e as Error)?.message ?? e);
|
|
51
|
+
}
|
|
52
|
+
let versionLine = "";
|
|
53
|
+
try {
|
|
54
|
+
const v = await displayVersion();
|
|
55
|
+
versionLine = v ? copyTemplate("ownerAccount.version", "Version {version}", { version: v }) : "";
|
|
56
|
+
} catch { /* host without app_info — the row collapses */ }
|
|
57
|
+
return shapeAccount({ ...s, ...links, status: "ready", ownerLine, versionLine, error });
|
|
58
|
+
},
|
|
59
|
+
openLink: async (s, arg) => {
|
|
60
|
+
const kind = String(obj(arg).kind ?? arg ?? "") as LinkKind;
|
|
61
|
+
const url = kind === "support" ? s.supportUrl : kind === "privacy" ? s.privacyUrl : kind === "terms" ? s.termsUrl : "";
|
|
62
|
+
if (url) await open(url);
|
|
63
|
+
return s;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export { svc };
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import { defineService } from "@himalaya/state";
|
|
28
28
|
import { open } from "@himalaya/open-url";
|
|
29
29
|
import { writeText } from "@himalaya/clipboard";
|
|
30
|
+
import { availability, recognizeOnce, stop as stopSpeech } from "@himalaya/speech";
|
|
30
31
|
import { capturePhoto } from "@himalaya/camera";
|
|
31
32
|
import { put } from "@himalaya/presigned-upload";
|
|
32
33
|
import { alert } from "@himalaya/ui";
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
createAriaChat,
|
|
36
37
|
type AriaActionArg,
|
|
37
38
|
type AriaChatState,
|
|
39
|
+
type AriaDictationResult,
|
|
38
40
|
type AriaPromptChip,
|
|
39
41
|
} from "../_shared/aria-chat.js";
|
|
40
42
|
import { WIDGET_SUBTREE_SLOT } from "../_shared/aria-generic-widget.js";
|
|
@@ -201,6 +203,8 @@ const core = createAriaChat({
|
|
|
201
203
|
// English. Says no "sign in": the owner already is, and the missing piece is
|
|
202
204
|
// the app's binding, so there is no action for them to take.
|
|
203
205
|
unavailable: () => copyParam("ownerAria.unavailable", "Aria can't reach your Wix account from here."),
|
|
206
|
+
dictationEmpty: () => copyParam("ownerAria.dictationEmpty", "Didn't catch that. Check microphone access and try again."),
|
|
207
|
+
dictationFailed: () => copyParam("ownerAria.dictationFailed", "Dictation isn't available right now."),
|
|
204
208
|
// The mention picker. Through copyParam for the same reason as the rest.
|
|
205
209
|
// The CATEGORY labels are not here, deliberately: they are what the
|
|
206
210
|
// reference's `data.type` carries to the model, so they stay English.
|
|
@@ -243,6 +247,19 @@ const core = createAriaChat({
|
|
|
243
247
|
return screenId ? { screenId } : null;
|
|
244
248
|
},
|
|
245
249
|
poll: { action: `${SERVICE}.tick`, fastMs: FAST_MS, idleMs: IDLE_MS },
|
|
250
|
+
// Composer dictation, on the platform recognizer: SFSpeechRecognizer,
|
|
251
|
+
// Android SpeechRecognizer, Web Speech. No audio crosses the bridge — only the
|
|
252
|
+
// final transcript. An empty language means the device's own locale, which is
|
|
253
|
+
// the owner's, rather than a hardcoded en-US. The screen must declare "speech".
|
|
254
|
+
//
|
|
255
|
+
// `svc` is referenced lazily: it is defined below, and `deliver` only ever
|
|
256
|
+
// runs after an utterance, long after this module has finished evaluating.
|
|
257
|
+
dictation: {
|
|
258
|
+
listen: () => recognizeOnce(),
|
|
259
|
+
stop: () => stopSpeech(),
|
|
260
|
+
deliver: (r) => { void svc.dictated(r); },
|
|
261
|
+
available: async () => (await availability()).recognition !== "unavailable",
|
|
262
|
+
},
|
|
246
263
|
// Photo attachments. The two device capabilities live HERE rather than
|
|
247
264
|
// in the shared core so that only a screen which declares them imports them:
|
|
248
265
|
// `camera` for the picker (it returns the image already re-encoded as JPEG and
|
|
@@ -293,6 +310,10 @@ const svc = defineService<AriaChatState, {
|
|
|
293
310
|
widgetPick: (s: AriaChatState, arg?: AriaActionArg) => AriaChatState;
|
|
294
311
|
widgetQuery: (s: AriaChatState, arg?: AriaActionArg) => AriaChatState;
|
|
295
312
|
widgetAnswer: (s: AriaChatState, arg?: AriaActionArg) => AriaChatState;
|
|
313
|
+
dictate: (s: AriaChatState) => AriaChatState;
|
|
314
|
+
finishDictation: (s: AriaChatState) => AriaChatState;
|
|
315
|
+
// `undefined` for a result whose session is no longer the live one — see the core.
|
|
316
|
+
dictated: (s: AriaChatState, arg?: AriaDictationResult) => AriaChatState | undefined;
|
|
296
317
|
mentionOpen: (s: AriaChatState) => Promise<AriaChatState | undefined>;
|
|
297
318
|
mentionClose: (s: AriaChatState) => AriaChatState;
|
|
298
319
|
mentionBack: (s: AriaChatState) => AriaChatState;
|
|
@@ -340,6 +361,13 @@ const svc = defineService<AriaChatState, {
|
|
|
340
361
|
widgetPick: core.widgetPick,
|
|
341
362
|
widgetQuery: core.widgetQuery,
|
|
342
363
|
widgetAnswer: core.widgetAnswer,
|
|
364
|
+
// Composer dictation: the mic, the listening row's Done, and the result
|
|
365
|
+
// re-entering from `dictation.deliver` above. Exported for the same reason
|
|
366
|
+
// as the widget actions — an action the entry does not name is a control
|
|
367
|
+
// that renders and does nothing.
|
|
368
|
+
dictate: core.dictate,
|
|
369
|
+
finishDictation: core.finishDictation,
|
|
370
|
+
dictated: core.dictated,
|
|
343
371
|
// Mentions: the "@ Mention" control, the two-level picker and the
|
|
344
372
|
// chips above the composer. Every one is named by the descriptor, so every
|
|
345
373
|
// one has to be exported here — see the note on the picker family above.
|
|
@@ -111,7 +111,8 @@ const STARTER: Node = {
|
|
|
111
111
|
props: {
|
|
112
112
|
title: "Ask about your business",
|
|
113
113
|
subtitle: "Try “which products are out of stock”, “unpaid orders”, or “what's coming up”.",
|
|
114
|
-
|
|
114
|
+
// Not `search`: Ask is not search, and a magnifier says it is.
|
|
115
|
+
icon: "ai",
|
|
115
116
|
},
|
|
116
117
|
},
|
|
117
118
|
},
|
|
@@ -129,7 +130,7 @@ const enc = (n: Node): string => encodeSubtree(n as unknown as Parameters<typeof
|
|
|
129
130
|
const initialState: AskState = {
|
|
130
131
|
status: "idle",
|
|
131
132
|
intent: "",
|
|
132
|
-
headline: "
|
|
133
|
+
headline: "",
|
|
133
134
|
note: "",
|
|
134
135
|
customerNotice: "",
|
|
135
136
|
repairs: "",
|
|
@@ -267,7 +268,7 @@ function failed(state: AskState, headline: string, note: string): AskState {
|
|
|
267
268
|
body: {
|
|
268
269
|
package: "himalaya",
|
|
269
270
|
type: "EmptyState",
|
|
270
|
-
props: { title: headline, subtitle: note, icon: "
|
|
271
|
+
props: { title: headline, subtitle: note, icon: "ai" },
|
|
271
272
|
},
|
|
272
273
|
}],
|
|
273
274
|
},
|