@buoy-gg/ask-buoy 7.0.35 → 7.0.36
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/README.md +35 -8
- package/lib/commonjs/blocks/BlockRenderer.js +13 -1
- package/lib/commonjs/blocks/BlockRenderer.js.map +1 -1
- package/lib/commonjs/blocks/DataBlocks.js +126 -33
- package/lib/commonjs/blocks/DataBlocks.js.map +1 -1
- package/lib/commonjs/blocks/ExtraBlocks.js +12 -6
- package/lib/commonjs/blocks/ExtraBlocks.js.map +1 -1
- package/lib/commonjs/blocks/MediaBlocks.js +22 -6
- package/lib/commonjs/blocks/MediaBlocks.js.map +1 -1
- package/lib/commonjs/blocks/blockText.js +60 -0
- package/lib/commonjs/blocks/blockText.js.map +1 -0
- package/lib/commonjs/blocks/blockText.test.js +53 -0
- package/lib/commonjs/blocks/blockText.test.js.map +1 -0
- package/lib/commonjs/blocks/primitives.js +58 -2
- package/lib/commonjs/blocks/primitives.js.map +1 -1
- package/lib/commonjs/components/ApprovalCard.js +111 -7
- package/lib/commonjs/components/ApprovalCard.js.map +1 -1
- package/lib/commonjs/components/AskBuoyHeader.js +36 -4
- package/lib/commonjs/components/AskBuoyHeader.js.map +1 -1
- package/lib/commonjs/components/AskBuoyModal.js +160 -34
- package/lib/commonjs/components/AskBuoyModal.js.map +1 -1
- package/lib/commonjs/components/AskBuoySettings.js +50 -0
- package/lib/commonjs/components/AskBuoySettings.js.map +1 -1
- package/lib/commonjs/components/Composer.js +94 -14
- package/lib/commonjs/components/Composer.js.map +1 -1
- package/lib/commonjs/components/EmptyState.js +12 -5
- package/lib/commonjs/components/EmptyState.js.map +1 -1
- package/lib/commonjs/components/KeyboardPad.js +80 -0
- package/lib/commonjs/components/KeyboardPad.js.map +1 -0
- package/lib/commonjs/components/Markdown.js +148 -90
- package/lib/commonjs/components/Markdown.js.map +1 -1
- package/lib/commonjs/components/MessageList.js +339 -92
- package/lib/commonjs/components/MessageList.js.map +1 -1
- package/lib/commonjs/components/QueuedMessages.js +145 -0
- package/lib/commonjs/components/QueuedMessages.js.map +1 -0
- package/lib/commonjs/components/markdownParse.js +131 -0
- package/lib/commonjs/components/markdownParse.js.map +1 -0
- package/lib/commonjs/components/markdownParse.test.js +99 -0
- package/lib/commonjs/components/markdownParse.test.js.map +1 -0
- package/lib/commonjs/store/activityGroups.js +110 -0
- package/lib/commonjs/store/activityGroups.js.map +1 -0
- package/lib/commonjs/store/activityGroups.test.js +78 -0
- package/lib/commonjs/store/activityGroups.test.js.map +1 -0
- package/lib/commonjs/store/askBuoySettings.js +7 -2
- package/lib/commonjs/store/askBuoySettings.js.map +1 -1
- package/lib/commonjs/store/askBuoyStore.js +192 -10
- package/lib/commonjs/store/askBuoyStore.js.map +1 -1
- package/lib/commonjs/store/blockPlacement.js +64 -0
- package/lib/commonjs/store/blockPlacement.js.map +1 -0
- package/lib/commonjs/store/blockPlacement.test.js +123 -0
- package/lib/commonjs/store/blockPlacement.test.js.map +1 -0
- package/lib/commonjs/store/queue.test.js +150 -0
- package/lib/commonjs/store/queue.test.js.map +1 -0
- package/lib/commonjs/store/resetConversation.js +77 -0
- package/lib/commonjs/store/resetConversation.js.map +1 -0
- package/lib/commonjs/store/restorePending.test.js +63 -0
- package/lib/commonjs/store/restorePending.test.js.map +1 -0
- package/lib/commonjs/store/suggestions.js +54 -0
- package/lib/commonjs/store/suggestions.js.map +1 -0
- package/lib/commonjs/store/suggestions.test.js +58 -0
- package/lib/commonjs/store/suggestions.test.js.map +1 -0
- package/lib/commonjs/store/systemLine.test.js +77 -0
- package/lib/commonjs/store/systemLine.test.js.map +1 -0
- package/lib/commonjs/store/ticketPrompt.js +17 -0
- package/lib/commonjs/store/ticketPrompt.js.map +1 -0
- package/lib/commonjs/store/transcriptPersistence.js +92 -3
- package/lib/commonjs/store/transcriptPersistence.js.map +1 -1
- package/lib/commonjs/store/transcriptPersistence.test.js +148 -0
- package/lib/commonjs/store/transcriptPersistence.test.js.map +1 -1
- package/lib/commonjs/store/turnRendering.test.js +151 -0
- package/lib/commonjs/store/turnRendering.test.js.map +1 -0
- package/lib/commonjs/store/useAskBuoy.js +271 -49
- package/lib/commonjs/store/useAskBuoy.js.map +1 -1
- package/lib/commonjs/sync/askBuoySyncAdapter.js +25 -4
- package/lib/commonjs/sync/askBuoySyncAdapter.js.map +1 -1
- package/lib/module/blocks/BlockRenderer.js +13 -1
- package/lib/module/blocks/BlockRenderer.js.map +1 -1
- package/lib/module/blocks/DataBlocks.js +128 -35
- package/lib/module/blocks/DataBlocks.js.map +1 -1
- package/lib/module/blocks/ExtraBlocks.js +12 -6
- package/lib/module/blocks/ExtraBlocks.js.map +1 -1
- package/lib/module/blocks/MediaBlocks.js +22 -6
- package/lib/module/blocks/MediaBlocks.js.map +1 -1
- package/lib/module/blocks/blockText.js +55 -0
- package/lib/module/blocks/blockText.js.map +1 -0
- package/lib/module/blocks/blockText.test.js +53 -0
- package/lib/module/blocks/blockText.test.js.map +1 -0
- package/lib/module/blocks/primitives.js +58 -2
- package/lib/module/blocks/primitives.js.map +1 -1
- package/lib/module/components/ApprovalCard.js +113 -9
- package/lib/module/components/ApprovalCard.js.map +1 -1
- package/lib/module/components/AskBuoyHeader.js +37 -5
- package/lib/module/components/AskBuoyHeader.js.map +1 -1
- package/lib/module/components/AskBuoyModal.js +163 -37
- package/lib/module/components/AskBuoyModal.js.map +1 -1
- package/lib/module/components/AskBuoySettings.js +52 -2
- package/lib/module/components/AskBuoySettings.js.map +1 -1
- package/lib/module/components/Composer.js +95 -15
- package/lib/module/components/Composer.js.map +1 -1
- package/lib/module/components/EmptyState.js +12 -5
- package/lib/module/components/EmptyState.js.map +1 -1
- package/lib/module/components/KeyboardPad.js +75 -0
- package/lib/module/components/KeyboardPad.js.map +1 -0
- package/lib/module/components/Markdown.js +149 -90
- package/lib/module/components/Markdown.js.map +1 -1
- package/lib/module/components/MessageList.js +341 -94
- package/lib/module/components/MessageList.js.map +1 -1
- package/lib/module/components/QueuedMessages.js +140 -0
- package/lib/module/components/QueuedMessages.js.map +1 -0
- package/lib/module/components/markdownParse.js +125 -0
- package/lib/module/components/markdownParse.js.map +1 -0
- package/lib/module/components/markdownParse.test.js +98 -0
- package/lib/module/components/markdownParse.test.js.map +1 -0
- package/lib/module/store/activityGroups.js +103 -0
- package/lib/module/store/activityGroups.js.map +1 -0
- package/lib/module/store/activityGroups.test.js +77 -0
- package/lib/module/store/activityGroups.test.js.map +1 -0
- package/lib/module/store/askBuoySettings.js +7 -2
- package/lib/module/store/askBuoySettings.js.map +1 -1
- package/lib/module/store/askBuoyStore.js +190 -10
- package/lib/module/store/askBuoyStore.js.map +1 -1
- package/lib/module/store/blockPlacement.js +59 -0
- package/lib/module/store/blockPlacement.js.map +1 -0
- package/lib/module/store/blockPlacement.test.js +122 -0
- package/lib/module/store/blockPlacement.test.js.map +1 -0
- package/lib/module/store/queue.test.js +149 -0
- package/lib/module/store/queue.test.js.map +1 -0
- package/lib/module/store/resetConversation.js +72 -0
- package/lib/module/store/resetConversation.js.map +1 -0
- package/lib/module/store/restorePending.test.js +62 -0
- package/lib/module/store/restorePending.test.js.map +1 -0
- package/lib/module/store/suggestions.js +49 -0
- package/lib/module/store/suggestions.js.map +1 -0
- package/lib/module/store/suggestions.test.js +57 -0
- package/lib/module/store/suggestions.test.js.map +1 -0
- package/lib/module/store/systemLine.test.js +76 -0
- package/lib/module/store/systemLine.test.js.map +1 -0
- package/lib/module/store/ticketPrompt.js +13 -0
- package/lib/module/store/ticketPrompt.js.map +1 -0
- package/lib/module/store/transcriptPersistence.js +92 -3
- package/lib/module/store/transcriptPersistence.js.map +1 -1
- package/lib/module/store/transcriptPersistence.test.js +148 -0
- package/lib/module/store/transcriptPersistence.test.js.map +1 -1
- package/lib/module/store/turnRendering.test.js +150 -0
- package/lib/module/store/turnRendering.test.js.map +1 -0
- package/lib/module/store/useAskBuoy.js +275 -53
- package/lib/module/store/useAskBuoy.js.map +1 -1
- package/lib/module/sync/askBuoySyncAdapter.js +25 -4
- package/lib/module/sync/askBuoySyncAdapter.js.map +1 -1
- package/lib/typescript/blocks/BlockRenderer.d.ts.map +1 -1
- package/lib/typescript/blocks/DataBlocks.d.ts +11 -0
- package/lib/typescript/blocks/DataBlocks.d.ts.map +1 -1
- package/lib/typescript/blocks/ExtraBlocks.d.ts +18 -2
- package/lib/typescript/blocks/ExtraBlocks.d.ts.map +1 -1
- package/lib/typescript/blocks/MediaBlocks.d.ts.map +1 -1
- package/lib/typescript/blocks/blockText.d.ts +14 -0
- package/lib/typescript/blocks/blockText.d.ts.map +1 -0
- package/lib/typescript/blocks/blockText.test.d.ts +2 -0
- package/lib/typescript/blocks/blockText.test.d.ts.map +1 -0
- package/lib/typescript/blocks/primitives.d.ts +16 -1
- package/lib/typescript/blocks/primitives.d.ts.map +1 -1
- package/lib/typescript/components/ApprovalCard.d.ts.map +1 -1
- package/lib/typescript/components/AskBuoyHeader.d.ts +31 -1
- package/lib/typescript/components/AskBuoyHeader.d.ts.map +1 -1
- package/lib/typescript/components/AskBuoyModal.d.ts.map +1 -1
- package/lib/typescript/components/AskBuoySettings.d.ts.map +1 -1
- package/lib/typescript/components/Composer.d.ts +16 -1
- package/lib/typescript/components/Composer.d.ts.map +1 -1
- package/lib/typescript/components/EmptyState.d.ts +5 -1
- package/lib/typescript/components/EmptyState.d.ts.map +1 -1
- package/lib/typescript/components/KeyboardPad.d.ts +33 -0
- package/lib/typescript/components/KeyboardPad.d.ts.map +1 -0
- package/lib/typescript/components/Markdown.d.ts +0 -10
- package/lib/typescript/components/Markdown.d.ts.map +1 -1
- package/lib/typescript/components/MessageList.d.ts +7 -2
- package/lib/typescript/components/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/QueuedMessages.d.ts +10 -0
- package/lib/typescript/components/QueuedMessages.d.ts.map +1 -0
- package/lib/typescript/components/markdownParse.d.ts +49 -0
- package/lib/typescript/components/markdownParse.d.ts.map +1 -0
- package/lib/typescript/components/markdownParse.test.d.ts +2 -0
- package/lib/typescript/components/markdownParse.test.d.ts.map +1 -0
- package/lib/typescript/store/activityGroups.d.ts +29 -0
- package/lib/typescript/store/activityGroups.d.ts.map +1 -0
- package/lib/typescript/store/activityGroups.test.d.ts +2 -0
- package/lib/typescript/store/activityGroups.test.d.ts.map +1 -0
- package/lib/typescript/store/askBuoySettings.d.ts +22 -0
- package/lib/typescript/store/askBuoySettings.d.ts.map +1 -1
- package/lib/typescript/store/askBuoyStore.d.ts +142 -5
- package/lib/typescript/store/askBuoyStore.d.ts.map +1 -1
- package/lib/typescript/store/blockPlacement.d.ts +30 -0
- package/lib/typescript/store/blockPlacement.d.ts.map +1 -0
- package/lib/typescript/store/blockPlacement.test.d.ts +2 -0
- package/lib/typescript/store/blockPlacement.test.d.ts.map +1 -0
- package/lib/typescript/store/queue.test.d.ts +2 -0
- package/lib/typescript/store/queue.test.d.ts.map +1 -0
- package/lib/typescript/store/resetConversation.d.ts +38 -0
- package/lib/typescript/store/resetConversation.d.ts.map +1 -0
- package/lib/typescript/store/restorePending.test.d.ts +2 -0
- package/lib/typescript/store/restorePending.test.d.ts.map +1 -0
- package/lib/typescript/store/suggestions.d.ts +32 -0
- package/lib/typescript/store/suggestions.d.ts.map +1 -0
- package/lib/typescript/store/suggestions.test.d.ts +2 -0
- package/lib/typescript/store/suggestions.test.d.ts.map +1 -0
- package/lib/typescript/store/systemLine.test.d.ts +2 -0
- package/lib/typescript/store/systemLine.test.d.ts.map +1 -0
- package/lib/typescript/store/ticketPrompt.d.ts +11 -0
- package/lib/typescript/store/ticketPrompt.d.ts.map +1 -0
- package/lib/typescript/store/transcriptPersistence.d.ts +41 -2
- package/lib/typescript/store/transcriptPersistence.d.ts.map +1 -1
- package/lib/typescript/store/turnRendering.test.d.ts +2 -0
- package/lib/typescript/store/turnRendering.test.d.ts.map +1 -0
- package/lib/typescript/store/useAskBuoy.d.ts +23 -1
- package/lib/typescript/store/useAskBuoy.d.ts.map +1 -1
- package/lib/typescript/sync/askBuoySyncAdapter.d.ts +26 -6
- package/lib/typescript/sync/askBuoySyncAdapter.d.ts.map +1 -1
- package/package.json +9 -9
|
@@ -24,12 +24,31 @@
|
|
|
24
24
|
* The previous version showed the model-facing sentence cut off at two
|
|
25
25
|
* lines with the result's JSON key names beside it, which was unreadable
|
|
26
26
|
* AND uninformative.
|
|
27
|
+
* - What a READ step returned (the receipt — "10 cached queries", a store's
|
|
28
|
+
* key/values) lives INSIDE that step's row and shows only when the row is
|
|
29
|
+
* expanded. Rendered as a standalone card under the activity list it read
|
|
30
|
+
* as part of the answer: a question about one Pokémon produced a wall of
|
|
31
|
+
* ten cache entries that a non-technical tester had no way to relate to
|
|
32
|
+
* what they asked. The step row already says "10 items"; tapping it is the
|
|
33
|
+
* ask for the detail. Receipts of WRITES (a diff of what changed) are drawn
|
|
34
|
+
* in full — that is the thing the user must see without asking.
|
|
35
|
+
* - CARDS COME LAST, AFTER THE PROSE. A turn reads activity → explanation →
|
|
36
|
+
* the thing itself, which is the order a person would say it in. It used to
|
|
37
|
+
* be the other way round, and on a long turn the answer — a grid of 28 shop
|
|
38
|
+
* items — landed at the TOP with five paragraphs of narration underneath it,
|
|
39
|
+
* so the finished answer arrived before the sentence explaining it. Ask
|
|
40
|
+
* blocks (choice/confirm/input) stay last of all: they are the question the
|
|
41
|
+
* turn ends on.
|
|
27
42
|
*/
|
|
28
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
43
|
+
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
|
29
44
|
import { ActivityIndicator, FlatList, Pressable, StyleSheet, Text, View } from "react-native";
|
|
30
45
|
import { AlertTriangle, Check, ChevronDown, ChevronRight, Copy, night, X } from "@buoy-gg/shared-ui";
|
|
31
|
-
import {
|
|
46
|
+
import { toolTitle } from "@buoy-gg/agent-core";
|
|
32
47
|
import { BlockRenderer } from "../blocks/BlockRenderer";
|
|
48
|
+
import { blocksToText } from "../blocks/blockText";
|
|
49
|
+
import { groupCalls, turnFooter } from "../store/activityGroups";
|
|
50
|
+
import { placeBlocks } from "../store/blockPlacement";
|
|
51
|
+
import { askBuoyStore } from "../store/askBuoyStore";
|
|
33
52
|
import { Markdown } from "./Markdown";
|
|
34
53
|
import { ThinkingStrip } from "./ThinkingStrip";
|
|
35
54
|
import { TypingDots } from "./TypingDots";
|
|
@@ -70,6 +89,18 @@ function StatusMark({
|
|
|
70
89
|
color: night.color.onAccent
|
|
71
90
|
})
|
|
72
91
|
});
|
|
92
|
+
// A grey ring with a bar — drawn, not an icon, so the shape is distinct
|
|
93
|
+
// from every outcome the MODEL produced. `declined` had no case at all:
|
|
94
|
+
// the row rendered a blank slot, and later nobody could tell whether the
|
|
95
|
+
// person or the policy had stopped it.
|
|
96
|
+
case "declined":
|
|
97
|
+
case "stopped":
|
|
98
|
+
return /*#__PURE__*/_jsx(View, {
|
|
99
|
+
style: [styles.mark, styles.markNotRun],
|
|
100
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
101
|
+
style: styles.markNotRunBar
|
|
102
|
+
})
|
|
103
|
+
});
|
|
73
104
|
}
|
|
74
105
|
}
|
|
75
106
|
|
|
@@ -87,102 +118,161 @@ function statusText(call) {
|
|
|
87
118
|
text: "Failed",
|
|
88
119
|
tone: "danger"
|
|
89
120
|
};
|
|
121
|
+
if (call.status === "declined") return {
|
|
122
|
+
text: "You declined",
|
|
123
|
+
tone: "muted"
|
|
124
|
+
};
|
|
125
|
+
if (call.status === "stopped") return {
|
|
126
|
+
text: "Not run — stopped",
|
|
127
|
+
tone: "muted"
|
|
128
|
+
};
|
|
90
129
|
return {
|
|
91
130
|
text: call.summary || "Done",
|
|
92
131
|
tone: "muted"
|
|
93
132
|
};
|
|
94
133
|
}
|
|
134
|
+
|
|
135
|
+
/** Rows that never fold: anything that did not simply succeed. */
|
|
136
|
+
const isExceptional = c => c.status === "failed" || c.status === "refused" || c.status === "declined" || c.status === "stopped";
|
|
95
137
|
function ActivityRow({
|
|
96
138
|
call,
|
|
139
|
+
receipts,
|
|
97
140
|
last
|
|
98
141
|
}) {
|
|
99
142
|
const [open, setOpen] = useState(false);
|
|
100
143
|
const status = statusText(call);
|
|
101
144
|
const Chevron = open ? ChevronDown : ChevronRight;
|
|
102
145
|
const failureDetail = call.status === "failed" && call.summary && call.summary !== "Failed" ? call.summary : null;
|
|
103
|
-
return /*#__PURE__*/_jsxs(
|
|
104
|
-
testID: `buoy-ask-buoy-tool-${call.toolId}`,
|
|
105
|
-
accessibilityRole: "button",
|
|
106
|
-
accessibilityLabel: `${call.label}, ${status.text || "running"}`,
|
|
107
|
-
accessibilityState: {
|
|
108
|
-
expanded: open
|
|
109
|
-
},
|
|
110
|
-
onPress: () => setOpen(v => !v),
|
|
146
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
111
147
|
style: [styles.row, !last && styles.rowDivider],
|
|
112
|
-
children: [/*#__PURE__*/_jsxs(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
children: [/*#__PURE__*/_jsxs(Pressable, {
|
|
149
|
+
testID: `buoy-ask-buoy-tool-${call.toolId}`,
|
|
150
|
+
accessibilityRole: "button",
|
|
151
|
+
accessibilityLabel: `${call.label}, ${status.text || "running"}`,
|
|
152
|
+
accessibilityState: {
|
|
153
|
+
expanded: open
|
|
154
|
+
},
|
|
155
|
+
onPress: () => setOpen(v => !v),
|
|
156
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
157
|
+
style: styles.rowLine,
|
|
158
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
159
|
+
style: styles.markSlot,
|
|
160
|
+
children: /*#__PURE__*/_jsx(StatusMark, {
|
|
161
|
+
status: call.status
|
|
162
|
+
})
|
|
163
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
164
|
+
style: styles.rowTitle,
|
|
165
|
+
children: call.label
|
|
166
|
+
}), status.text ? /*#__PURE__*/_jsx(Text, {
|
|
167
|
+
style: [styles.rowStatus, status.tone === "danger" && styles.rowStatusDanger, status.tone === "warning" && styles.rowStatusWarning],
|
|
168
|
+
numberOfLines: 1,
|
|
169
|
+
children: status.text
|
|
170
|
+
}) : null, /*#__PURE__*/_jsx(Chevron, {
|
|
171
|
+
size: 12,
|
|
172
|
+
color: night.color.textTertiary
|
|
173
|
+
})]
|
|
174
|
+
}), open && /*#__PURE__*/_jsxs(View, {
|
|
175
|
+
style: styles.rowDetail,
|
|
176
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
177
|
+
style: styles.rowTool,
|
|
178
|
+
children: toolTitle(call.toolId).toUpperCase()
|
|
179
|
+
}), call.description ? /*#__PURE__*/_jsx(Text, {
|
|
180
|
+
style: styles.rowDescription,
|
|
181
|
+
children: call.description
|
|
182
|
+
}) : null, failureDetail ? /*#__PURE__*/_jsx(Text, {
|
|
183
|
+
style: styles.rowFailure,
|
|
184
|
+
children: failureDetail
|
|
185
|
+
}) : null]
|
|
129
186
|
})]
|
|
130
|
-
}), open && /*#__PURE__*/
|
|
131
|
-
style: styles.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
style: styles.rowDescription,
|
|
137
|
-
children: call.description
|
|
138
|
-
}) : null, failureDetail ? /*#__PURE__*/_jsx(Text, {
|
|
139
|
-
style: styles.rowFailure,
|
|
140
|
-
children: failureDetail
|
|
141
|
-
}) : null]
|
|
187
|
+
}), open && receipts.length > 0 && /*#__PURE__*/_jsx(View, {
|
|
188
|
+
style: styles.rowReceipts,
|
|
189
|
+
testID: `buoy-ask-buoy-tool-receipt-${call.toolId}`,
|
|
190
|
+
children: receipts.map(block => /*#__PURE__*/_jsx(BlockRenderer, {
|
|
191
|
+
block: block
|
|
192
|
+
}, block.id))
|
|
142
193
|
})]
|
|
143
194
|
});
|
|
144
195
|
}
|
|
145
|
-
const COLLAPSE_AFTER = 4;
|
|
146
196
|
|
|
147
197
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
198
|
+
* Consecutive successful reads fold into one row — "Looked at Network and
|
|
199
|
+
* Storage · 3 reads" — expandable to the individual rows. Everything else
|
|
200
|
+
* (running, failed, refused, declined, stopped, any write) is always its own
|
|
201
|
+
* row, and a group never crosses a write. See store/activityGroups.ts for the
|
|
202
|
+
* rule and why the old "Did 7 things" fold was replaced.
|
|
150
203
|
*/
|
|
151
|
-
function
|
|
152
|
-
|
|
204
|
+
function ReadsGroup({
|
|
205
|
+
label,
|
|
206
|
+
count,
|
|
207
|
+
calls,
|
|
208
|
+
receipts,
|
|
209
|
+
last
|
|
153
210
|
}) {
|
|
154
|
-
const [
|
|
155
|
-
const
|
|
156
|
-
const collapse = calls.length > COLLAPSE_AFTER && !expanded && !running;
|
|
157
|
-
const failed = calls.filter(c => c.status === "failed").length;
|
|
158
|
-
const refused = calls.filter(c => c.status === "refused").length;
|
|
159
|
-
const shown = collapse ? calls.filter(c => c.status === "failed" || c.status === "refused") : calls;
|
|
211
|
+
const [open, setOpen] = useState(false);
|
|
212
|
+
const Chevron = open ? ChevronDown : ChevronRight;
|
|
160
213
|
return /*#__PURE__*/_jsxs(View, {
|
|
161
|
-
style: styles.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
onPress: () => setExpanded(true),
|
|
214
|
+
style: !last && !open && styles.rowDivider,
|
|
215
|
+
children: [/*#__PURE__*/_jsx(Pressable, {
|
|
216
|
+
testID: "buoy-ask-buoy-reads-group",
|
|
165
217
|
accessibilityRole: "button",
|
|
166
|
-
|
|
167
|
-
|
|
218
|
+
accessibilityLabel: `${label}, ${count} reads`,
|
|
219
|
+
accessibilityState: {
|
|
220
|
+
expanded: open
|
|
221
|
+
},
|
|
222
|
+
onPress: () => setOpen(v => !v),
|
|
223
|
+
style: [styles.row, open && styles.rowDivider],
|
|
168
224
|
children: /*#__PURE__*/_jsxs(View, {
|
|
169
225
|
style: styles.rowLine,
|
|
170
|
-
children: [/*#__PURE__*/
|
|
226
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
227
|
+
style: styles.markSlot,
|
|
228
|
+
children: /*#__PURE__*/_jsx(StatusMark, {
|
|
229
|
+
status: "ok"
|
|
230
|
+
})
|
|
231
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
171
232
|
style: styles.rowTitle,
|
|
172
|
-
children:
|
|
173
|
-
}), /*#__PURE__*/
|
|
233
|
+
children: label
|
|
234
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
235
|
+
style: styles.rowStatus,
|
|
236
|
+
numberOfLines: 1,
|
|
237
|
+
children: [count, " reads"]
|
|
238
|
+
}), /*#__PURE__*/_jsx(Chevron, {
|
|
174
239
|
size: 12,
|
|
175
240
|
color: night.color.textTertiary
|
|
176
241
|
})]
|
|
177
242
|
})
|
|
178
|
-
}),
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
243
|
+
}), open && /*#__PURE__*/_jsx(View, {
|
|
244
|
+
style: styles.groupBody,
|
|
245
|
+
children: calls.map((c, i) => /*#__PURE__*/_jsx(ActivityRow, {
|
|
246
|
+
call: c,
|
|
247
|
+
receipts: receipts.get(c.id) ?? [],
|
|
248
|
+
last: last && i === calls.length - 1
|
|
249
|
+
}, c.id))
|
|
250
|
+
})]
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function ActivityCard({
|
|
254
|
+
calls,
|
|
255
|
+
receipts
|
|
256
|
+
}) {
|
|
257
|
+
const items = groupCalls(calls);
|
|
258
|
+
return /*#__PURE__*/_jsx(View, {
|
|
259
|
+
style: styles.card,
|
|
260
|
+
testID: "buoy-ask-buoy-activity",
|
|
261
|
+
children: items.map((item, i) => item.kind === "reads" ? /*#__PURE__*/_jsx(ReadsGroup, {
|
|
262
|
+
label: item.label,
|
|
263
|
+
count: item.count,
|
|
264
|
+
calls: item.calls,
|
|
265
|
+
receipts: receipts,
|
|
266
|
+
last: i === items.length - 1
|
|
267
|
+
}, item.id) : /*#__PURE__*/_jsx(ActivityRow, {
|
|
268
|
+
call: item.call,
|
|
269
|
+
receipts: receipts.get(item.call.id) ?? [],
|
|
270
|
+
last: i === items.length - 1
|
|
271
|
+
}, item.call.id))
|
|
182
272
|
});
|
|
183
273
|
}
|
|
184
274
|
|
|
185
|
-
/**
|
|
275
|
+
/** Write receipts and model blocks render between the activity card and the prose; Ask blocks last. */
|
|
186
276
|
function Blocks({
|
|
187
277
|
blocks
|
|
188
278
|
}) {
|
|
@@ -196,6 +286,20 @@ function Blocks({
|
|
|
196
286
|
}, block.id))
|
|
197
287
|
});
|
|
198
288
|
}
|
|
289
|
+
|
|
290
|
+
/** A line from Buoy, not the model. Dim, centred, no footer, no copy pill. */
|
|
291
|
+
function SystemLine({
|
|
292
|
+
message
|
|
293
|
+
}) {
|
|
294
|
+
return /*#__PURE__*/_jsx(View, {
|
|
295
|
+
style: styles.systemLine,
|
|
296
|
+
testID: "buoy-ask-buoy-system-line",
|
|
297
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
298
|
+
style: styles.systemText,
|
|
299
|
+
children: message.text
|
|
300
|
+
})
|
|
301
|
+
});
|
|
302
|
+
}
|
|
199
303
|
function UserMessage({
|
|
200
304
|
message
|
|
201
305
|
}) {
|
|
@@ -213,24 +317,43 @@ function UserMessage({
|
|
|
213
317
|
}
|
|
214
318
|
function AssistantMessage({
|
|
215
319
|
message,
|
|
216
|
-
onRetry
|
|
320
|
+
onRetry,
|
|
321
|
+
waitingOnUser
|
|
217
322
|
}) {
|
|
218
323
|
const {
|
|
219
324
|
showThinking
|
|
220
325
|
} = useAskBuoySettings();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
326
|
+
/**
|
|
327
|
+
* The dots run for the WHOLE turn, not just the wait for the first token.
|
|
328
|
+
*
|
|
329
|
+
* They used to hide as soon as any prose arrived and while a tool call was
|
|
330
|
+
* running. A turn that says "pulling the catalogue up — one moment" and
|
|
331
|
+
* then works for another minute is the normal shape of this tool, and it
|
|
332
|
+
* spent that minute with nothing moving at the bottom of the chat: the
|
|
333
|
+
* activity card had already collapsed to "Did 16 things", so the only clue
|
|
334
|
+
* anything was still happening was the Stop button and the header
|
|
335
|
+
* subtitle. The row spinner in the activity card says which STEP is
|
|
336
|
+
* running; this says the TURN is.
|
|
337
|
+
*
|
|
338
|
+
* The one state it stays out of is a turn waiting on the user — an
|
|
339
|
+
* approval or an Ask block. Nothing is loading there.
|
|
340
|
+
*/
|
|
341
|
+
const waiting = !!message.streaming && !waitingOnUser;
|
|
342
|
+
const {
|
|
343
|
+
nested,
|
|
344
|
+
afterText,
|
|
345
|
+
ask: askBlocks
|
|
346
|
+
} = placeBlocks(message.blocks, message.toolCalls.map(c => c.id));
|
|
225
347
|
return /*#__PURE__*/_jsxs(View, {
|
|
226
348
|
style: styles.assistantWrap,
|
|
227
349
|
children: [message.toolCalls.length > 0 && /*#__PURE__*/_jsx(ActivityCard, {
|
|
228
|
-
calls: message.toolCalls
|
|
229
|
-
|
|
230
|
-
blocks: showBlocks
|
|
350
|
+
calls: message.toolCalls,
|
|
351
|
+
receipts: nested
|
|
231
352
|
}), message.text.length > 0 && /*#__PURE__*/_jsx(Markdown, {
|
|
232
353
|
text: message.text,
|
|
233
354
|
style: styles.assistantText
|
|
355
|
+
}), /*#__PURE__*/_jsx(Blocks, {
|
|
356
|
+
blocks: afterText
|
|
234
357
|
}), /*#__PURE__*/_jsx(Blocks, {
|
|
235
358
|
blocks: askBlocks
|
|
236
359
|
}), showThinking && !message.streaming && /*#__PURE__*/_jsx(ThinkingStrip, {
|
|
@@ -238,6 +361,45 @@ function AssistantMessage({
|
|
|
238
361
|
}), waiting && /*#__PURE__*/_jsx(TypingDots, {}), message.error && /*#__PURE__*/_jsx(ErrorNote, {
|
|
239
362
|
error: message.error,
|
|
240
363
|
onRetry: onRetry
|
|
364
|
+
}), message.stopped && !message.error && /*#__PURE__*/_jsx(StoppedNote, {
|
|
365
|
+
onRetry: onRetry,
|
|
366
|
+
changed: message.toolCalls.some(c => c.status === "ok" && !!c.effect && c.effect !== "read")
|
|
367
|
+
})]
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* A turn the user stopped.
|
|
373
|
+
*
|
|
374
|
+
* Deliberately not the error box: they pressed the button. The whole point is
|
|
375
|
+
* that it is quiet — a muted line, the same Try-again affordance, and none of
|
|
376
|
+
* the red "check your connection" advice that a cancelled fetch used to
|
|
377
|
+
* produce. When a write had already landed before the tap, the line says so:
|
|
378
|
+
* Stop cannot pull a dispatched change back out of the app, and "Stopped."
|
|
379
|
+
* alone read as if nothing had happened.
|
|
380
|
+
*/
|
|
381
|
+
function StoppedNote({
|
|
382
|
+
onRetry,
|
|
383
|
+
changed
|
|
384
|
+
}) {
|
|
385
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
386
|
+
style: styles.stoppedBox,
|
|
387
|
+
testID: "buoy-ask-buoy-stopped",
|
|
388
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
389
|
+
style: styles.stoppedText,
|
|
390
|
+
children: changed ? "Stopped. Changes already made are listed above." : "Stopped."
|
|
391
|
+
}), onRetry && /*#__PURE__*/_jsx(Pressable, {
|
|
392
|
+
testID: "buoy-ask-buoy-retry",
|
|
393
|
+
accessibilityRole: "button",
|
|
394
|
+
accessibilityLabel: "Try again",
|
|
395
|
+
onPress: onRetry,
|
|
396
|
+
style: ({
|
|
397
|
+
pressed
|
|
398
|
+
}) => [styles.retry, pressed && styles.retryPressed],
|
|
399
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
400
|
+
style: styles.retryText,
|
|
401
|
+
children: "Try again"
|
|
402
|
+
})
|
|
241
403
|
})]
|
|
242
404
|
});
|
|
243
405
|
}
|
|
@@ -252,7 +414,7 @@ function ErrorNote({
|
|
|
252
414
|
error,
|
|
253
415
|
onRetry
|
|
254
416
|
}) {
|
|
255
|
-
const network = /fetch failed|network|ECONN|timed? ?out|socket
|
|
417
|
+
const network = /fetch failed|network|ECONN|timed? ?out|socket/i.test(error);
|
|
256
418
|
const headline = network ? "Couldn't reach your AI endpoint. Check the connection and try again." : "Something went wrong with that turn.";
|
|
257
419
|
return /*#__PURE__*/_jsxs(View, {
|
|
258
420
|
style: styles.errorBox,
|
|
@@ -272,13 +434,14 @@ function ErrorNote({
|
|
|
272
434
|
}), onRetry && /*#__PURE__*/_jsx(Pressable, {
|
|
273
435
|
testID: "buoy-ask-buoy-retry",
|
|
274
436
|
accessibilityRole: "button",
|
|
437
|
+
accessibilityLabel: "Try again",
|
|
275
438
|
onPress: onRetry,
|
|
276
439
|
style: ({
|
|
277
440
|
pressed
|
|
278
441
|
}) => [styles.retry, pressed && styles.retryPressed],
|
|
279
442
|
children: /*#__PURE__*/_jsx(Text, {
|
|
280
443
|
style: styles.retryText,
|
|
281
|
-
children: "
|
|
444
|
+
children: "Try again"
|
|
282
445
|
})
|
|
283
446
|
})]
|
|
284
447
|
})]
|
|
@@ -305,16 +468,24 @@ export function transcriptToText(messages, withTrace = false) {
|
|
|
305
468
|
lines.push(`You: ${m.text}`);
|
|
306
469
|
continue;
|
|
307
470
|
}
|
|
471
|
+
if (m.system) {
|
|
472
|
+
lines.push(` — ${m.text}`);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
308
475
|
if (withTrace) {
|
|
309
476
|
lines.push(...traceToText(m));
|
|
310
477
|
} else {
|
|
311
478
|
for (const c of m.toolCalls) {
|
|
312
|
-
const status = c
|
|
479
|
+
const status = isExceptional(c) ? ` (${c.status})` : "";
|
|
313
480
|
lines.push(` · ${c.label}${c.summary ? ` — ${c.summary}` : ""}${status}`);
|
|
314
481
|
}
|
|
315
482
|
}
|
|
316
483
|
if (m.text) lines.push(`Ask Buoy: ${m.text}`);
|
|
484
|
+
// The cards, too. A finding is the thing a ticket wants and it used to be
|
|
485
|
+
// the one thing the paste left out.
|
|
486
|
+
for (const t of blocksToText(m.blocks.map(p => p.block))) lines.push(t);
|
|
317
487
|
if (m.error) lines.push(` ! ${m.error}`);
|
|
488
|
+
if (m.stopped && !m.error) lines.push(" ! Stopped by the user.");
|
|
318
489
|
}
|
|
319
490
|
return lines.join("\n");
|
|
320
491
|
}
|
|
@@ -351,54 +522,79 @@ function CopyPill({
|
|
|
351
522
|
}
|
|
352
523
|
|
|
353
524
|
/**
|
|
354
|
-
* Under a finished answer:
|
|
525
|
+
* Under a finished answer: the turn's footer and one Copy.
|
|
526
|
+
*
|
|
527
|
+
* The footer — "48s · 6 actions · 1 declined" — is always on. Duration and
|
|
528
|
+
* counts used to exist only inside the opt-in thinking strip, so nothing
|
|
529
|
+
* separated one turn from the next and "how long did that take?" had no
|
|
530
|
+
* answer without a setting. See `turnFooter` for what is counted and why.
|
|
355
531
|
*
|
|
356
|
-
*
|
|
532
|
+
* Copy is on EVERY answer rather than only the newest, because the moment you
|
|
357
533
|
* decide to keep a conversation is usually a few replies after the one that
|
|
358
534
|
* went wrong, and scrolling to the bottom to find the button is friction at
|
|
359
|
-
* exactly the wrong time.
|
|
360
|
-
*
|
|
535
|
+
* exactly the wrong time. It copies the WHOLE chat, and now says so: labelled
|
|
536
|
+
* "Copy" it read as "copy this answer", and a paste into Slack brought the
|
|
537
|
+
* full transcript. The text is built on press from the live store, so this
|
|
538
|
+
* component takes no `messages` prop and a streaming answer no longer
|
|
539
|
+
* re-renders every finished one.
|
|
361
540
|
*/
|
|
362
541
|
function TranscriptActions({
|
|
363
|
-
|
|
542
|
+
message
|
|
364
543
|
}) {
|
|
365
544
|
const {
|
|
366
545
|
showThinking
|
|
367
546
|
} = useAskBuoySettings();
|
|
368
|
-
const getText = useCallback(() => transcriptToText(messages, showThinking), [
|
|
369
|
-
|
|
547
|
+
const getText = useCallback(() => transcriptToText(askBuoyStore.getState().messages, showThinking), [showThinking]);
|
|
548
|
+
const footer = turnFooter(message);
|
|
549
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
370
550
|
style: styles.transcriptActions,
|
|
371
|
-
children: /*#__PURE__*/_jsx(
|
|
551
|
+
children: [footer ? /*#__PURE__*/_jsx(Text, {
|
|
552
|
+
style: styles.turnFooter,
|
|
553
|
+
testID: "buoy-ask-buoy-turn-footer",
|
|
554
|
+
children: footer
|
|
555
|
+
}) : null, /*#__PURE__*/_jsx(CopyPill, {
|
|
372
556
|
testID: "buoy-ask-buoy-copy",
|
|
373
|
-
label: "Copy",
|
|
557
|
+
label: "Copy conversation",
|
|
374
558
|
getText: getText
|
|
375
|
-
})
|
|
559
|
+
})]
|
|
376
560
|
});
|
|
377
561
|
}
|
|
378
|
-
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Memoised: a streaming answer changes the store on every token, and every
|
|
565
|
+
* row re-rendered for it. Props are the message itself (a new object when it
|
|
566
|
+
* changes), the retry handler (only the newest row gets one) and the waiting
|
|
567
|
+
* flag — nothing that changes when a DIFFERENT message streams.
|
|
568
|
+
*/
|
|
569
|
+
const Row = /*#__PURE__*/memo(function Row({
|
|
379
570
|
message,
|
|
380
|
-
|
|
381
|
-
|
|
571
|
+
onRetry,
|
|
572
|
+
waitingOnUser
|
|
382
573
|
}) {
|
|
383
574
|
if (message.role === "user") return /*#__PURE__*/_jsx(UserMessage, {
|
|
384
575
|
message: message
|
|
385
576
|
});
|
|
577
|
+
if (message.system) return /*#__PURE__*/_jsx(SystemLine, {
|
|
578
|
+
message: message
|
|
579
|
+
});
|
|
386
580
|
const finished = !message.streaming && !message.error;
|
|
387
581
|
return /*#__PURE__*/_jsxs(View, {
|
|
388
582
|
children: [/*#__PURE__*/_jsx(AssistantMessage, {
|
|
389
583
|
message: message,
|
|
390
|
-
onRetry: onRetry
|
|
584
|
+
onRetry: onRetry,
|
|
585
|
+
waitingOnUser: waitingOnUser
|
|
391
586
|
}), finished && /*#__PURE__*/_jsx(TranscriptActions, {
|
|
392
|
-
|
|
587
|
+
message: message
|
|
393
588
|
})]
|
|
394
589
|
});
|
|
395
|
-
}
|
|
590
|
+
});
|
|
396
591
|
export function MessageList({
|
|
397
592
|
messages,
|
|
398
593
|
ListHeaderComponent,
|
|
399
594
|
ListEmptyComponent,
|
|
400
595
|
ListFooterComponent,
|
|
401
|
-
onRetry
|
|
596
|
+
onRetry,
|
|
597
|
+
waitingOnUser
|
|
402
598
|
}) {
|
|
403
599
|
const ref = useRef(null);
|
|
404
600
|
// Only auto-scroll when the user is already at the bottom. Yanking them back
|
|
@@ -460,8 +656,8 @@ export function MessageList({
|
|
|
460
656
|
item
|
|
461
657
|
}) => /*#__PURE__*/_jsx(Row, {
|
|
462
658
|
message: item,
|
|
463
|
-
|
|
464
|
-
|
|
659
|
+
onRetry: item.id === last?.id ? onRetry : undefined,
|
|
660
|
+
waitingOnUser: item.id === last?.id ? waitingOnUser : false
|
|
465
661
|
}),
|
|
466
662
|
onScroll: onScroll,
|
|
467
663
|
onScrollBeginDrag: onScrollBeginDrag,
|
|
@@ -510,6 +706,20 @@ const styles = StyleSheet.create({
|
|
|
510
706
|
fontSize: night.font.row,
|
|
511
707
|
lineHeight: 21
|
|
512
708
|
},
|
|
709
|
+
// Buoy's own lines
|
|
710
|
+
systemLine: {
|
|
711
|
+
alignSelf: "stretch",
|
|
712
|
+
paddingHorizontal: 12,
|
|
713
|
+
paddingVertical: 7,
|
|
714
|
+
borderRadius: night.radius.row,
|
|
715
|
+
backgroundColor: night.color.fill
|
|
716
|
+
},
|
|
717
|
+
systemText: {
|
|
718
|
+
color: night.color.textSecondary,
|
|
719
|
+
fontSize: night.font.micro,
|
|
720
|
+
lineHeight: 16,
|
|
721
|
+
textAlign: "center"
|
|
722
|
+
},
|
|
513
723
|
// Assistant
|
|
514
724
|
assistantWrap: {
|
|
515
725
|
alignItems: "stretch",
|
|
@@ -567,6 +777,16 @@ const styles = StyleSheet.create({
|
|
|
567
777
|
markRefused: {
|
|
568
778
|
backgroundColor: night.color.warning
|
|
569
779
|
},
|
|
780
|
+
markNotRun: {
|
|
781
|
+
borderWidth: 1.5,
|
|
782
|
+
borderColor: night.color.textTertiary
|
|
783
|
+
},
|
|
784
|
+
markNotRunBar: {
|
|
785
|
+
width: 7,
|
|
786
|
+
height: 1.5,
|
|
787
|
+
borderRadius: 1,
|
|
788
|
+
backgroundColor: night.color.textSecondary
|
|
789
|
+
},
|
|
570
790
|
rowTitle: {
|
|
571
791
|
flex: 1,
|
|
572
792
|
color: night.color.text,
|
|
@@ -605,6 +825,25 @@ const styles = StyleSheet.create({
|
|
|
605
825
|
fontSize: night.font.caption,
|
|
606
826
|
lineHeight: 18
|
|
607
827
|
},
|
|
828
|
+
rowReceipts: {
|
|
829
|
+
paddingTop: 10,
|
|
830
|
+
gap: 10
|
|
831
|
+
},
|
|
832
|
+
groupBody: {
|
|
833
|
+
paddingLeft: 14,
|
|
834
|
+
backgroundColor: night.color.fill
|
|
835
|
+
},
|
|
836
|
+
stoppedBox: {
|
|
837
|
+
alignItems: "flex-start",
|
|
838
|
+
gap: 2,
|
|
839
|
+
paddingHorizontal: 12,
|
|
840
|
+
paddingVertical: 8
|
|
841
|
+
},
|
|
842
|
+
stoppedText: {
|
|
843
|
+
color: night.color.textTertiary,
|
|
844
|
+
fontSize: night.font.caption,
|
|
845
|
+
lineHeight: 18
|
|
846
|
+
},
|
|
608
847
|
// Error
|
|
609
848
|
errorBox: {
|
|
610
849
|
flexDirection: "row",
|
|
@@ -648,8 +887,16 @@ const styles = StyleSheet.create({
|
|
|
648
887
|
},
|
|
649
888
|
transcriptActions: {
|
|
650
889
|
flexDirection: "row",
|
|
890
|
+
alignItems: "center",
|
|
891
|
+
gap: 10,
|
|
651
892
|
marginTop: 8
|
|
652
893
|
},
|
|
894
|
+
turnFooter: {
|
|
895
|
+
flex: 1,
|
|
896
|
+
color: night.color.textTertiary,
|
|
897
|
+
fontSize: night.font.micro,
|
|
898
|
+
fontVariant: ["tabular-nums"]
|
|
899
|
+
},
|
|
653
900
|
copyBtn: {
|
|
654
901
|
paddingHorizontal: 10,
|
|
655
902
|
paddingVertical: 5,
|