@wix/himalaya-cli 0.969.0 → 0.971.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 +196 -162
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/tier-5-add-a-screen.md +8 -4
- package/guides/wix-business-context.md +10 -0
- package/guides/wix-site-to-himi-app.md +8 -5
- package/package.json +2 -2
- package/templates/beauty-salon-owner/config.ts +1 -1
- package/templates/beauty-salon-owner/dev/index.ts +79 -1
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/community-club-owner/config.ts +1 -1
- package/templates/community-club-owner/dev/index.ts +79 -1
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/events-meetups-owner/config.ts +1 -1
- package/templates/events-meetups-owner/dev/index.ts +79 -1
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/gym-fitness-owner/config.ts +1 -1
- package/templates/gym-fitness-owner/dev/index.ts +79 -1
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/nonprofit-cause-owner/config.ts +1 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +79 -1
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/owner-lite/config.ts +1 -1
- package/templates/owner-lite/dev/index.ts +79 -1
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/podcast-creator-owner/config.ts +1 -1
- package/templates/podcast-creator-owner/dev/index.ts +79 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/portfolio-creative-owner/config.ts +1 -1
- package/templates/portfolio-creative-owner/dev/index.ts +79 -1
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/restaurant-owner/config.ts +1 -1
- package/templates/restaurant-owner/dev/index.ts +79 -1
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/retail-store-owner/config.ts +1 -1
- package/templates/retail-store-owner/dev/index.ts +79 -1
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
- package/templates/yoga-studio-owner/config.ts +1 -1
- package/templates/yoga-studio-owner/dev/index.ts +79 -1
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +176 -6
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +15 -0
|
@@ -54892,6 +54892,83 @@ const SCREENS = runtimeSetting({
|
|
|
54892
54892
|
}
|
|
54893
54893
|
}
|
|
54894
54894
|
},
|
|
54895
|
+
{
|
|
54896
|
+
"id": "himi_owner_lite_aria.reply_actions",
|
|
54897
|
+
"body": {
|
|
54898
|
+
"package": "himalaya",
|
|
54899
|
+
"type": "HStack",
|
|
54900
|
+
"props": {
|
|
54901
|
+
"spacing": 4,
|
|
54902
|
+
"children": [
|
|
54903
|
+
{
|
|
54904
|
+
"id": "himi_owner_lite_aria.copy",
|
|
54905
|
+
"body": {
|
|
54906
|
+
"package": "himalaya",
|
|
54907
|
+
"type": "Button",
|
|
54908
|
+
"props": {
|
|
54909
|
+
"title": "Copy",
|
|
54910
|
+
"style": "plain",
|
|
54911
|
+
"size": "sm",
|
|
54912
|
+
"fullWidth": false,
|
|
54913
|
+
"leadingIcon": "copy",
|
|
54914
|
+
"action": {
|
|
54915
|
+
"actionId": "tier5.himiOwnerLiteAria.copyMessage"
|
|
54916
|
+
}
|
|
54917
|
+
}
|
|
54918
|
+
}
|
|
54919
|
+
},
|
|
54920
|
+
{
|
|
54921
|
+
"id": "himi_owner_lite_aria.regenerate",
|
|
54922
|
+
"body": {
|
|
54923
|
+
"package": "himalaya",
|
|
54924
|
+
"type": "Button",
|
|
54925
|
+
"props": {
|
|
54926
|
+
"title": "Regenerate",
|
|
54927
|
+
"style": "plain",
|
|
54928
|
+
"size": "sm",
|
|
54929
|
+
"fullWidth": false,
|
|
54930
|
+
"leadingIcon": "refresh",
|
|
54931
|
+
"action": {
|
|
54932
|
+
"actionId": "tier5.himiOwnerLiteAria.regenerate"
|
|
54933
|
+
}
|
|
54934
|
+
}
|
|
54935
|
+
}
|
|
54936
|
+
},
|
|
54937
|
+
{
|
|
54938
|
+
"id": "",
|
|
54939
|
+
"body": {
|
|
54940
|
+
"package": "himalaya",
|
|
54941
|
+
"type": "Text",
|
|
54942
|
+
"props": {
|
|
54943
|
+
"text": "$service:himiOwnerLiteAria.copyNote",
|
|
54944
|
+
"style": "footnote",
|
|
54945
|
+
"color": "textSecondary",
|
|
54946
|
+
"maxLines": 1,
|
|
54947
|
+
"collapseWhenEmpty": true
|
|
54948
|
+
}
|
|
54949
|
+
}
|
|
54950
|
+
},
|
|
54951
|
+
{
|
|
54952
|
+
"id": "",
|
|
54953
|
+
"body": {
|
|
54954
|
+
"package": "himalaya",
|
|
54955
|
+
"type": "Spacer",
|
|
54956
|
+
"props": {}
|
|
54957
|
+
}
|
|
54958
|
+
}
|
|
54959
|
+
]
|
|
54960
|
+
}
|
|
54961
|
+
},
|
|
54962
|
+
"modifiers": {
|
|
54963
|
+
"padding": {
|
|
54964
|
+
"leading": 0,
|
|
54965
|
+
"trailing": 0,
|
|
54966
|
+
"top": 0,
|
|
54967
|
+
"bottom": 4
|
|
54968
|
+
},
|
|
54969
|
+
"hiddenPath": "$service:himiOwnerLiteAria.replyActionsHidden"
|
|
54970
|
+
}
|
|
54971
|
+
},
|
|
54895
54972
|
{
|
|
54896
54973
|
"id": "himi_owner_lite_aria.prompts.bound",
|
|
54897
54974
|
"body": {
|
|
@@ -58271,7 +58348,8 @@ const SCREENS = runtimeSetting({
|
|
|
58271
58348
|
"contentType": "application/javascript+ts-bundle",
|
|
58272
58349
|
"declaredCapabilities": [
|
|
58273
58350
|
"wix_api",
|
|
58274
|
-
"open_url"
|
|
58351
|
+
"open_url",
|
|
58352
|
+
"clipboard"
|
|
58275
58353
|
],
|
|
58276
58354
|
"minBinaryVersion": "0.1.0",
|
|
58277
58355
|
"bundleVersion": "0.0.1"
|
|
@@ -89,6 +89,19 @@ export interface AriaChatState {
|
|
|
89
89
|
error: string;
|
|
90
90
|
/** Bound seed for the composer field. */
|
|
91
91
|
input: string;
|
|
92
|
+
/** Transient result of the last Copy: the confirmation, or the failure.
|
|
93
|
+
*
|
|
94
|
+
* It exists because `writeText` resolves or throws and NOTHING else in the
|
|
95
|
+
* state changes either way — so without this the owner cannot tell a copy
|
|
96
|
+
* that took from one the platform refused. Saying "Copied" when nothing
|
|
97
|
+
* reached the clipboard is a known way for a copy control to lie; this is
|
|
98
|
+
* the field that makes the difference observable. */
|
|
99
|
+
copyNote: string;
|
|
100
|
+
/** Collapses the Copy / Regenerate row. Derived, like `stopHidden`: shown
|
|
101
|
+
* only when the newest bubble is a REPLY (not the owner's message, not the
|
|
102
|
+
* local greeting) and no turn is out. A pure function of other fields, so it
|
|
103
|
+
* is computed once in `gated` rather than maintained by every reducer. */
|
|
104
|
+
replyActionsHidden: boolean;
|
|
92
105
|
messages: AriaBubble[];
|
|
93
106
|
/** Non-empty ONLY on the greeting-only empty state. */
|
|
94
107
|
prompts: AriaPromptChip[];
|
|
@@ -199,6 +212,12 @@ export interface AriaChatLabels {
|
|
|
199
212
|
/** Status line after the owner stopped a reply. Sits in the NEUTRAL slot:
|
|
200
213
|
* they asked for it, so nothing went wrong. */
|
|
201
214
|
stopped?: Resolvable<string>;
|
|
215
|
+
/** Shown after a clipboard write that RESOLVED. */
|
|
216
|
+
copied?: Resolvable<string>;
|
|
217
|
+
/** Shown after a clipboard write that threw, or when the host supplied no
|
|
218
|
+
* clipboard at all. Deliberately distinct from `copied`: the point of the
|
|
219
|
+
* pair is that the owner can tell which of the two happened. */
|
|
220
|
+
copyFailed?: Resolvable<string>;
|
|
202
221
|
/** Shown under a picker once the owner has chosen as many options as the
|
|
203
222
|
* service allows. Takes the limit, because "pick up to 3" is the whole of
|
|
204
223
|
* the information. */
|
|
@@ -242,6 +261,13 @@ export interface AriaChatOptions {
|
|
|
242
261
|
/** Opens an external URL. Apps whose descriptors carry their own open-url
|
|
243
262
|
* action (the clone) leave this unset and never invoke `openLink`. */
|
|
244
263
|
openLink?: (url: string) => Promise<boolean>;
|
|
264
|
+
|
|
265
|
+
/** Put `text` on the system clipboard. Injected rather than imported so the
|
|
266
|
+
* shared core stays free of the SDK, exactly as `openLink` is.
|
|
267
|
+
*
|
|
268
|
+
* It RESOLVES or THROWS — there is no boolean. A host that does not supply
|
|
269
|
+
* it has no clipboard, and `copyMessage` says so rather than pretending. */
|
|
270
|
+
copyText?: (text: string) => Promise<void>;
|
|
245
271
|
/** Maps an Aria action-link URL to an in-app screen. A hit navigates via a
|
|
246
272
|
* `_nav` directive — the host applies the same linkable-target check an
|
|
247
273
|
* authored `himi.open` gets — and a miss falls through to `openLink`. */
|
|
@@ -573,6 +599,16 @@ export function toBubbles(msgs: ConversationMessage[], assistantName: string, yo
|
|
|
573
599
|
.filter((b) => b.content && b.content.trim());
|
|
574
600
|
}
|
|
575
601
|
|
|
602
|
+
/** The reply the Copy / Regenerate row acts on: the LAST bubble, when it is an
|
|
603
|
+
* answer. Not "the newest assistant bubble": a widget-only answer leaves no
|
|
604
|
+
* bubble, so the transcript then ends on the owner's question, and searching
|
|
605
|
+
* back past it would offer the previous turn's answer as if it were this one.
|
|
606
|
+
* The greeting is excluded because it is a local bubble, not a reply. */
|
|
607
|
+
function lastReply(s: { messages: AriaBubble[] }): AriaBubble | undefined {
|
|
608
|
+
const last = s.messages[s.messages.length - 1];
|
|
609
|
+
return last && last.notUser && last.id !== GREETING_ID ? last : undefined;
|
|
610
|
+
}
|
|
611
|
+
|
|
576
612
|
/** Chips are visible only before the owner has said anything. */
|
|
577
613
|
function promptsFor(msgs: AriaBubble[], prompts: AriaPromptChip[]): AriaPromptChip[] {
|
|
578
614
|
return msgs.some((b) => b.role === "USER") ? [] : prompts;
|
|
@@ -845,6 +881,24 @@ export interface AriaChatCore {
|
|
|
845
881
|
* that moved during the abort makes this snapshot stale, and committing it
|
|
846
882
|
* would end a turn the owner has since started. */
|
|
847
883
|
stop(s: AriaChatState): Promise<AriaChatState | undefined>;
|
|
884
|
+
|
|
885
|
+
/** Put one reply's prose on the clipboard. Defaults to the newest assistant
|
|
886
|
+
* bubble, which is the only one the descriptor offers the control on.
|
|
887
|
+
*
|
|
888
|
+
* Copies `bubble.content`, which is ALREADY the normalized prose — the
|
|
889
|
+
* envelope scan and the markdown stripper both ran on the way in
|
|
890
|
+
* (`bubblesFrom`), so the owner gets what they can read rather than the
|
|
891
|
+
* wire text. */
|
|
892
|
+
/** `undefined` when the turn moved on while the write was out — see `stop`. */
|
|
893
|
+
copyMessage(s: AriaChatState, arg?: AriaActionArg): Promise<AriaChatState | undefined>;
|
|
894
|
+
|
|
895
|
+
/** Ask for another answer.
|
|
896
|
+
*
|
|
897
|
+
* NOT a protocol feature: it sends ordinary user text, the same literal the
|
|
898
|
+
* native Aria app sends, through the one send path (`send`). Not through
|
|
899
|
+
* `submit`: with no argument that reads the COMPOSER, so a regenerate would
|
|
900
|
+
* send whatever the owner had half-typed instead of the retry. */
|
|
901
|
+
regenerate(s: AriaChatState): Promise<AriaChatState>;
|
|
848
902
|
/** Tapping the active widget's primary / secondary footer button.
|
|
849
903
|
*
|
|
850
904
|
* The url is read from STATE rather than bound into the action's params.
|
|
@@ -880,6 +934,10 @@ export interface AriaChatCore {
|
|
|
880
934
|
}
|
|
881
935
|
|
|
882
936
|
const POLL_ID = "aria-turn";
|
|
937
|
+
/** What Regenerate sends. Ordinary user text, matching the native Aria app —
|
|
938
|
+
* the chat-service has no retry verb, and inventing one here would be a
|
|
939
|
+
* protocol we then had to keep. */
|
|
940
|
+
const REGENERATE_TEXT = "Let's try that again.";
|
|
883
941
|
/** Id of the local greeting bubble. Shared by the minting and the
|
|
884
942
|
* "is it still the empty state?" test so the two cannot drift apart. */
|
|
885
943
|
const GREETING_ID = "greeting";
|
|
@@ -915,7 +973,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
915
973
|
const localHistory = o.localHistory !== false;
|
|
916
974
|
|
|
917
975
|
const initialState: AriaChatState = {
|
|
918
|
-
status: "loading", statusText: "", error: "", input: "",
|
|
976
|
+
status: "loading", statusText: "", error: "", input: "", copyNote: "", replyActionsHidden: true,
|
|
919
977
|
messages: [], prompts: [], _convId: "", _siteId: "", _pending: "",
|
|
920
978
|
_pendingAfter: 0, _replyAfter: 0, _srvUsers: 0, _srvAssistants: 0,
|
|
921
979
|
_abortId: "", _turnId: "", stopHidden: true, ok: false,
|
|
@@ -966,6 +1024,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
966
1024
|
* those. `submitting` stays on the state because the descriptor needs
|
|
967
1025
|
* something to disable a button with; it is the shadow, not the guard. */
|
|
968
1026
|
let widgetSending = false;
|
|
1027
|
+
/** A regenerate is on its way. Refuses a double-tap in the window before
|
|
1028
|
+
* `send` has written `_pending` — the same window `widgetSending` next door
|
|
1029
|
+
* closes for two taps of one option. */
|
|
1030
|
+
let regenerating = false;
|
|
969
1031
|
|
|
970
1032
|
/** A turn the server STORED but never got asked to answer.
|
|
971
1033
|
*
|
|
@@ -1188,7 +1250,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1188
1250
|
// state this action RETURNS, so on the `_poll` path they paint immediately.
|
|
1189
1251
|
const optimistic: AriaBubble[] = [...s.messages, plainBubble(`u${s.messages.length}`, "USER", you(), content)];
|
|
1190
1252
|
const base: AriaChatState = {
|
|
1191
|
-
|
|
1253
|
+
// `copyNote: ""` — a Copy confirmation belongs to the moment, and a new
|
|
1254
|
+
// turn ends it. Left in place it would sit under the next reply saying
|
|
1255
|
+
// "Copied" about text the owner copied several turns ago.
|
|
1256
|
+
...s, prompts: [], input: "", error: "", copyNote: "",
|
|
1192
1257
|
// The PREVIOUS turn's handles must not survive into this one: aborting by
|
|
1193
1258
|
// a stale id would abandon a reply that has already been read, and waiting
|
|
1194
1259
|
// on a stale turn id would look for an answer that already exists. Both
|
|
@@ -1410,9 +1475,36 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1410
1475
|
* function of two other fields, and eight reducers each remembering to set it
|
|
1411
1476
|
* is eight chances for the control to be shown when there is nothing to stop
|
|
1412
1477
|
* — or, worse, hidden while a reply the owner wants to abandon runs on. */
|
|
1478
|
+
let committed: AriaChatState | null = null;
|
|
1479
|
+
let commits = 0;
|
|
1480
|
+
/** A result as the runtime stores it: the one-shot directives it strips on
|
|
1481
|
+
* commit (`_stream`, `_nav`, `_poll`, `_error` — see `applyNext` in the web
|
|
1482
|
+
* host's runtime bootstrap) removed. */
|
|
1483
|
+
const settled = (s: AriaChatState): AriaChatState => {
|
|
1484
|
+
const r = s as AriaChatState & Record<string, unknown>;
|
|
1485
|
+
if (r._stream === undefined && r._nav === undefined && r._poll === undefined && r._error === undefined) return s;
|
|
1486
|
+
const { _stream: _a, _nav: _b, _poll: _c, _error: _d, ...rest } = r;
|
|
1487
|
+
return rest as AriaChatState;
|
|
1488
|
+
};
|
|
1413
1489
|
const gated = (s: AriaChatState): AriaChatState => {
|
|
1414
1490
|
const hidden = !(turnInFlight(s) && !!s._abortId);
|
|
1415
|
-
|
|
1491
|
+
// The reply actions follow the same rule for the same reason: a derived
|
|
1492
|
+
// flag, recomputed on every action's way out, so no reducer can forget it.
|
|
1493
|
+
// `_pending` rather than `turnInFlight` — see `regenerate` for why `status`
|
|
1494
|
+
// is not trusted to have been cleared.
|
|
1495
|
+
const replyActionsHidden = !(lastReply(s) && !s._pending);
|
|
1496
|
+
const out = s.stopHidden === hidden && s.replyActionsHidden === replyActionsHidden
|
|
1497
|
+
? s
|
|
1498
|
+
: { ...s, stopHidden: hidden, replyActionsHidden };
|
|
1499
|
+
// Every state the runtime commits passes through here (an `undefined`
|
|
1500
|
+
// result commits nothing and skips it), so this is the newest committed
|
|
1501
|
+
// state — what `copyMessage` puts its note on when other actions landed
|
|
1502
|
+
// while its write was out. Kept as the runtime STORES it, i.e. without the
|
|
1503
|
+
// transient directives it strips on commit: re-returning a `_nav` or a
|
|
1504
|
+
// `_poll` from here would navigate, or re-arm the timer, a second time.
|
|
1505
|
+
committed = settled(out);
|
|
1506
|
+
commits += 1;
|
|
1507
|
+
return out;
|
|
1416
1508
|
};
|
|
1417
1509
|
|
|
1418
1510
|
/** Ask the service how an async job is getting on.
|
|
@@ -1550,7 +1642,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1550
1642
|
const raw = messagesOf(gm);
|
|
1551
1643
|
const bubbles = toBubbles(raw, assistant(), you());
|
|
1552
1644
|
const resumed = bubbles.length ? bubbles : greetingOnly();
|
|
1553
|
-
|
|
1645
|
+
// `copyNote: ""` — a resumed transcript ends in a reply, so the row is
|
|
1646
|
+
// shown at once; a note carried over would say "Copied" beside an
|
|
1647
|
+
// answer from a different conversation that nobody copied.
|
|
1648
|
+
return { ...s, status: "ready", _convId: resumeId, _siteId: site, _pending: "", statusText: "", error: "", copyNote: "",
|
|
1554
1649
|
messages: resumed, ...observed(raw, s.widget), prompts: promptsFor(resumed, promptList()) };
|
|
1555
1650
|
} catch { /* expired or unreachable — fall through to a fresh one */ }
|
|
1556
1651
|
}
|
|
@@ -1649,11 +1744,11 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1649
1744
|
// previous one, which reads as ➕ having done nothing.
|
|
1650
1745
|
turnSeq += 1;
|
|
1651
1746
|
return slow({ ...s, status: "ready", _convId: conversationIdOf(res), _siteId: site, input: "", statusText: "", error: "",
|
|
1652
|
-
_pending: "", _abortId: "", _turnId: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
|
|
1747
|
+
_pending: "", _abortId: "", _turnId: "", copyNote: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
|
|
1653
1748
|
} catch (e) {
|
|
1654
1749
|
turnSeq += 1; // see above — a failed create commits state too
|
|
1655
1750
|
return slow({ ...s, status: "ready", _convId: "", _siteId: site, input: "", ...failureSlots(e, unavailableText),
|
|
1656
|
-
_pending: "", _abortId: "", _turnId: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
|
|
1751
|
+
_pending: "", _abortId: "", _turnId: "", copyNote: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
|
|
1657
1752
|
}
|
|
1658
1753
|
},
|
|
1659
1754
|
|
|
@@ -1972,6 +2067,79 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1972
2067
|
return slow(withDraft(
|
|
1973
2068
|
{ ...s, status: "ready", statusText: stoppedText(), error: "", _pending: "", _abortId: "" }, typedAt));
|
|
1974
2069
|
},
|
|
2070
|
+
|
|
2071
|
+
copyMessage: async (s, arg) => {
|
|
2072
|
+
// Which bubble. The descriptor offers Copy on the newest assistant reply,
|
|
2073
|
+
// so the id is optional and that bubble is the default — but an id IS
|
|
2074
|
+
// accepted, because a row-scoped control passing `$item.id` is the
|
|
2075
|
+
// established shape on this screen (the link rows do exactly that).
|
|
2076
|
+
const wanted = typeof arg === "object" && arg !== null ? String((arg as { id?: string }).id ?? "") : "";
|
|
2077
|
+
const target = wanted
|
|
2078
|
+
? s.messages.find((m) => m.id === wanted)
|
|
2079
|
+
// The LAST bubble, and only if it is a reply — the same test `gated`
|
|
2080
|
+
// shows the row on. Searching back for the newest reply instead would,
|
|
2081
|
+
// after a widget-only answer (which leaves no bubble), skip over the
|
|
2082
|
+
// owner's question and copy the answer to the turn BEFORE it. The
|
|
2083
|
+
// greeting is excluded because it is a local bubble, not a reply.
|
|
2084
|
+
: lastReply(s);
|
|
2085
|
+
// `content` is ALREADY the prose: `bubblesFrom` scans the widget envelope
|
|
2086
|
+
// out first and then strips markdown, so this is what the owner can read
|
|
2087
|
+
// rather than the wire text.
|
|
2088
|
+
const text = (target?.content ?? "").trim();
|
|
2089
|
+
const failed = () => res(o.labels?.copyFailed ?? "Couldn't copy");
|
|
2090
|
+
if (!text) return { ...s, copyNote: failed() };
|
|
2091
|
+
// No clipboard on this host: say so, rather than report a success that
|
|
2092
|
+
// never happened. Same rule as the throw below.
|
|
2093
|
+
if (!o.copyText) return { ...s, copyNote: failed() };
|
|
2094
|
+
// Read BEFORE the await, compared after: `s` is a pre-await snapshot,
|
|
2095
|
+
// and the runtime commits the whole returned state. Two cases.
|
|
2096
|
+
// - A send or + moved the turn boundary: the note is about a reply that
|
|
2097
|
+
// is gone, so stand down and commit nothing (as `stop` does).
|
|
2098
|
+
// - Anything else landed — a keystroke, a widget selection, a query —
|
|
2099
|
+
// so `{ ...s }` would silently undo it. Put the note on the newest
|
|
2100
|
+
// COMMITTED state instead of on the snapshot.
|
|
2101
|
+
const seq = turnSeq;
|
|
2102
|
+
const at = commits;
|
|
2103
|
+
let note: string;
|
|
2104
|
+
try {
|
|
2105
|
+
await o.copyText(text);
|
|
2106
|
+
note = res(o.labels?.copied ?? "Copied");
|
|
2107
|
+
} catch {
|
|
2108
|
+
// The web Async Clipboard API rejects without a user gesture, and
|
|
2109
|
+
// Android refuses while the app is unfocused. Both land here, and both
|
|
2110
|
+
// are a FAILURE the owner is told about.
|
|
2111
|
+
note = failed();
|
|
2112
|
+
}
|
|
2113
|
+
if (turnSeq !== seq) return undefined;
|
|
2114
|
+
// No separate guard for a resume: `load` resumes only when the screen
|
|
2115
|
+
// holds no live conversation, and a Copy row exists only under a reply
|
|
2116
|
+
// in one — so a resume cannot land while a Copy is out. (The nav input
|
|
2117
|
+
// and site are fixed for a worker's life; a site switch boots a new one.)
|
|
2118
|
+
return { ...(commits !== at && committed ? committed : s), copyNote: note };
|
|
2119
|
+
},
|
|
2120
|
+
|
|
2121
|
+
regenerate: async (s) => {
|
|
2122
|
+
// Refuse a double-tap, and refuse while a reply is still coming — there
|
|
2123
|
+
// is nothing to regenerate until the last one has landed.
|
|
2124
|
+
// `_pending` rather than `turnInFlight`, and the difference is load-bearing.
|
|
2125
|
+
// `turnInFlight` is `status === "sending" || !!_pending`, and `status` can
|
|
2126
|
+
// still read "sending" after a turn has actually closed — a tick whose read
|
|
2127
|
+
// straddled a turn boundary returns `undefined` and writes nothing back, so
|
|
2128
|
+
// the status it would have cleared stays put. Measured: with `turnInFlight`
|
|
2129
|
+
// here, the SECOND regenerate of a conversation was refused even though the
|
|
2130
|
+
// first reply had landed. `_pending` is set by `send` and cleared when the
|
|
2131
|
+
// reply arrives, so it tracks "a turn is out" exactly; the latch above
|
|
2132
|
+
// covers the double-tap that `_pending` is too slow for.
|
|
2133
|
+
if (regenerating || !!s._pending) return s;
|
|
2134
|
+
regenerating = true;
|
|
2135
|
+
try {
|
|
2136
|
+
// `send` directly: it is the one send path, and `submit` with no
|
|
2137
|
+
// argument would read the composer instead of sending the retry.
|
|
2138
|
+
return await send(s, REGENERATE_TEXT);
|
|
2139
|
+
} finally {
|
|
2140
|
+
regenerating = false;
|
|
2141
|
+
}
|
|
2142
|
+
},
|
|
1975
2143
|
};
|
|
1976
2144
|
|
|
1977
2145
|
// Every action's result goes through the Stop gate on its way out.
|
|
@@ -1985,6 +2153,8 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1985
2153
|
tick: (s) => impl.tick(s).then((r) => (r ? gated(r) : r)),
|
|
1986
2154
|
openLink: (s, arg) => impl.openLink(s, arg).then(gated),
|
|
1987
2155
|
stop: (s) => impl.stop(s).then((r) => (r ? gated(r) : r)),
|
|
2156
|
+
copyMessage: (s, arg) => impl.copyMessage(s, arg).then((r) => (r ? gated(r) : r)),
|
|
2157
|
+
regenerate: (s) => impl.regenerate(s).then(gated),
|
|
1988
2158
|
// Synchronous, like `typing`: a selection edit makes no network call, so
|
|
1989
2159
|
// gating it is a plain call rather than a `.then`.
|
|
1990
2160
|
widgetToggle: (s, arg) => gated(impl.widgetToggle(s, arg)),
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { defineService } from "@himalaya/state";
|
|
25
25
|
import { open } from "@himalaya/open-url";
|
|
26
|
+
import { writeText } from "@himalaya/clipboard";
|
|
26
27
|
import {
|
|
27
28
|
activeSiteId,
|
|
28
29
|
createAriaChat,
|
|
@@ -148,6 +149,11 @@ const core = createAriaChat({
|
|
|
148
149
|
typing: () => copyParam("ownerAria.typing", "Aria is typing…"),
|
|
149
150
|
slow: () => copyParam("ownerAria.slow", "Aria is taking a while…"),
|
|
150
151
|
stopped: () => copyParam("ownerAria.stopped", "Stopped."),
|
|
152
|
+
// The note beside Copy. Through copyParam for the same reason as the rest:
|
|
153
|
+
// the buttons are localized by the pack, and a note left on the core's
|
|
154
|
+
// English default would be the one line under them that is not.
|
|
155
|
+
copied: () => copyParam("ownerAria.copied", "Copied"),
|
|
156
|
+
copyFailed: () => copyParam("ownerAria.copyFailed", "Couldn't copy"),
|
|
151
157
|
// Shown when the HOST carries no Wix app binding, so no Wix call can leave
|
|
152
158
|
// the browser (the web preview). Through copyParam like every other string
|
|
153
159
|
// here — a default baked into the shared core cannot be localized by this
|
|
@@ -167,6 +173,9 @@ const core = createAriaChat({
|
|
|
167
173
|
localHistory: false,
|
|
168
174
|
siteId: activeSiteId,
|
|
169
175
|
openLink: async (url) => (await open(url, "system_browser")).opened,
|
|
176
|
+
// Resolves or throws — `copyMessage` keys its confirmation on which, so a
|
|
177
|
+
// write the platform refused is never reported as a copy.
|
|
178
|
+
copyText: (text) => writeText(text),
|
|
170
179
|
resolveEntity: (url) => {
|
|
171
180
|
const screenId = screenForUrl(url, allowedTargets());
|
|
172
181
|
return screenId ? { screenId } : null;
|
|
@@ -191,6 +200,10 @@ const svc = defineService<AriaChatState, {
|
|
|
191
200
|
// new chat) holds a pre-boundary snapshot, and committing it would end the
|
|
192
201
|
// turn the owner has since started.
|
|
193
202
|
stop: (s: AriaChatState) => Promise<AriaChatState | undefined>;
|
|
203
|
+
// `undefined` for the same reason again: a write still out when the turn
|
|
204
|
+
// moved on must not commit its pre-await snapshot over the new one.
|
|
205
|
+
copyMessage: (s: AriaChatState, arg?: AriaActionArg) => Promise<AriaChatState | undefined>;
|
|
206
|
+
regenerate: (s: AriaChatState) => Promise<AriaChatState>;
|
|
194
207
|
widgetPrimary: (s: AriaChatState) => Promise<AriaChatState>;
|
|
195
208
|
widgetSecondary: (s: AriaChatState) => Promise<AriaChatState>;
|
|
196
209
|
widgetToggle: (s: AriaChatState, arg?: AriaActionArg) => AriaChatState;
|
|
@@ -218,6 +231,8 @@ const svc = defineService<AriaChatState, {
|
|
|
218
231
|
// false — so this is only ever dispatched while a reply is outstanding and
|
|
219
232
|
// the worker is holding the handle that can abort it.
|
|
220
233
|
stop: core.stop,
|
|
234
|
+
copyMessage: core.copyMessage,
|
|
235
|
+
regenerate: core.regenerate,
|
|
221
236
|
// The active widget's footer buttons. Named here as well as in the core:
|
|
222
237
|
// the descriptor dispatches `tier5.<service>.<action>`, and an action the
|
|
223
238
|
// entry does not export is a control that renders and does nothing.
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database"],
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard"],
|
|
41
41
|
designTokensPath: "./tokens.json",
|
|
42
42
|
navigation: {
|
|
43
43
|
"navKind": "responsive",
|
|
@@ -54892,6 +54892,83 @@ const SCREENS = runtimeSetting({
|
|
|
54892
54892
|
}
|
|
54893
54893
|
}
|
|
54894
54894
|
},
|
|
54895
|
+
{
|
|
54896
|
+
"id": "himi_owner_lite_aria.reply_actions",
|
|
54897
|
+
"body": {
|
|
54898
|
+
"package": "himalaya",
|
|
54899
|
+
"type": "HStack",
|
|
54900
|
+
"props": {
|
|
54901
|
+
"spacing": 4,
|
|
54902
|
+
"children": [
|
|
54903
|
+
{
|
|
54904
|
+
"id": "himi_owner_lite_aria.copy",
|
|
54905
|
+
"body": {
|
|
54906
|
+
"package": "himalaya",
|
|
54907
|
+
"type": "Button",
|
|
54908
|
+
"props": {
|
|
54909
|
+
"title": "Copy",
|
|
54910
|
+
"style": "plain",
|
|
54911
|
+
"size": "sm",
|
|
54912
|
+
"fullWidth": false,
|
|
54913
|
+
"leadingIcon": "copy",
|
|
54914
|
+
"action": {
|
|
54915
|
+
"actionId": "tier5.himiOwnerLiteAria.copyMessage"
|
|
54916
|
+
}
|
|
54917
|
+
}
|
|
54918
|
+
}
|
|
54919
|
+
},
|
|
54920
|
+
{
|
|
54921
|
+
"id": "himi_owner_lite_aria.regenerate",
|
|
54922
|
+
"body": {
|
|
54923
|
+
"package": "himalaya",
|
|
54924
|
+
"type": "Button",
|
|
54925
|
+
"props": {
|
|
54926
|
+
"title": "Regenerate",
|
|
54927
|
+
"style": "plain",
|
|
54928
|
+
"size": "sm",
|
|
54929
|
+
"fullWidth": false,
|
|
54930
|
+
"leadingIcon": "refresh",
|
|
54931
|
+
"action": {
|
|
54932
|
+
"actionId": "tier5.himiOwnerLiteAria.regenerate"
|
|
54933
|
+
}
|
|
54934
|
+
}
|
|
54935
|
+
}
|
|
54936
|
+
},
|
|
54937
|
+
{
|
|
54938
|
+
"id": "",
|
|
54939
|
+
"body": {
|
|
54940
|
+
"package": "himalaya",
|
|
54941
|
+
"type": "Text",
|
|
54942
|
+
"props": {
|
|
54943
|
+
"text": "$service:himiOwnerLiteAria.copyNote",
|
|
54944
|
+
"style": "footnote",
|
|
54945
|
+
"color": "textSecondary",
|
|
54946
|
+
"maxLines": 1,
|
|
54947
|
+
"collapseWhenEmpty": true
|
|
54948
|
+
}
|
|
54949
|
+
}
|
|
54950
|
+
},
|
|
54951
|
+
{
|
|
54952
|
+
"id": "",
|
|
54953
|
+
"body": {
|
|
54954
|
+
"package": "himalaya",
|
|
54955
|
+
"type": "Spacer",
|
|
54956
|
+
"props": {}
|
|
54957
|
+
}
|
|
54958
|
+
}
|
|
54959
|
+
]
|
|
54960
|
+
}
|
|
54961
|
+
},
|
|
54962
|
+
"modifiers": {
|
|
54963
|
+
"padding": {
|
|
54964
|
+
"leading": 0,
|
|
54965
|
+
"trailing": 0,
|
|
54966
|
+
"top": 0,
|
|
54967
|
+
"bottom": 4
|
|
54968
|
+
},
|
|
54969
|
+
"hiddenPath": "$service:himiOwnerLiteAria.replyActionsHidden"
|
|
54970
|
+
}
|
|
54971
|
+
},
|
|
54895
54972
|
{
|
|
54896
54973
|
"id": "himi_owner_lite_aria.prompts.bound",
|
|
54897
54974
|
"body": {
|
|
@@ -58271,7 +58348,8 @@ const SCREENS = runtimeSetting({
|
|
|
58271
58348
|
"contentType": "application/javascript+ts-bundle",
|
|
58272
58349
|
"declaredCapabilities": [
|
|
58273
58350
|
"wix_api",
|
|
58274
|
-
"open_url"
|
|
58351
|
+
"open_url",
|
|
58352
|
+
"clipboard"
|
|
58275
58353
|
],
|
|
58276
58354
|
"minBinaryVersion": "0.1.0",
|
|
58277
58355
|
"bundleVersion": "0.0.1"
|