@lotics/ui 4.3.0 → 4.5.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/AGENTS.md +98 -28
- package/examples/tpl_assistant.tsx +6 -6
- package/examples/tpl_briefing.tsx +121 -0
- package/examples/tpl_compare.tsx +133 -0
- package/examples/tpl_crosscheck.tsx +120 -0
- package/examples/tpl_dieline.tsx +218 -55
- package/examples/tpl_draft.tsx +7 -9
- package/examples/tpl_extract.tsx +91 -92
- package/examples/tpl_lookup.tsx +288 -0
- package/examples/tpl_match.tsx +123 -0
- package/examples/tpl_triage.tsx +112 -0
- package/package.json +13 -3
- package/src/agent_progress.tsx +35 -23
- package/src/agent_run.tsx +60 -80
- package/src/change_review.tsx +190 -110
- package/src/choice_list.tsx +63 -0
- package/src/clarify.tsx +19 -39
- package/src/confidence.tsx +30 -8
- package/src/discrepancy.tsx +114 -0
- package/src/finding.tsx +104 -0
- package/src/match_row.tsx +133 -0
- package/src/prompt_field.tsx +47 -89
- package/src/record_review.tsx +149 -0
- package/src/scored_option.tsx +139 -0
- package/src/sources.tsx +38 -21
- package/src/spec_list.tsx +81 -0
- package/src/suggestion.tsx +35 -45
- package/src/triage_row.tsx +99 -0
- package/src/assisted_field.tsx +0 -93
package/src/change_review.tsx
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { StyleSheet, View } from "react-native";
|
|
2
|
-
import { colors
|
|
2
|
+
import { colors } from "./colors";
|
|
3
3
|
import { Text } from "./text";
|
|
4
4
|
import { Icon } from "./icon";
|
|
5
5
|
import { Button } from "./button";
|
|
6
|
-
import { IconButton } from "./icon_button";
|
|
7
|
-
import { LinkButton } from "./link_button";
|
|
8
6
|
|
|
9
7
|
export type ChangeReviewItemStatus = "pending" | "accepted" | "rejected";
|
|
10
8
|
|
|
11
9
|
export interface ChangeReviewItem {
|
|
12
10
|
/** What changed — a field/parameter name. */
|
|
13
11
|
label: string;
|
|
14
|
-
/** Prior value. Omit for a newly-added value (renders as just the
|
|
12
|
+
/** Prior value. Omit for a newly-added value (renders as just the `+` line). */
|
|
15
13
|
before?: string;
|
|
16
14
|
/** Proposed value. */
|
|
17
15
|
after: string;
|
|
@@ -29,114 +27,179 @@ export interface ChangeReviewProps {
|
|
|
29
27
|
applyLabel?: string;
|
|
30
28
|
/** Once resolved the card settles to a quiet outcome line (no actions). */
|
|
31
29
|
status?: "open" | "applied" | "discarded";
|
|
32
|
-
/** Providing either flips the card into 1-BY-1 mode: each
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
/** Providing either flips the card into 1-BY-1 mode: each edit is its own
|
|
31
|
+
* card with Keep / Drop; deciding COLLAPSES it to a preview the host can
|
|
32
|
+
* `onUndoItem`. "Accept all" + "Apply kept" sit in a separate footer. The
|
|
33
|
+
* decision is recorded — nothing writes until Apply. Omit both for whole-set. */
|
|
36
34
|
onAcceptItem?: (index: number) => void;
|
|
37
35
|
onRejectItem?: (index: number) => void;
|
|
36
|
+
/** Revert a decided item back to pending (the Undo on a collapsed card). */
|
|
37
|
+
onUndoItem?: (index: number) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A git-style stacked diff: the old value on a red `−` line (struck), the new
|
|
41
|
+
* value on a green `+` line. An addition (no `before`) is just the `+` line. */
|
|
42
|
+
function DiffLines({ before, after }: { before?: string; after: string }) {
|
|
43
|
+
return (
|
|
44
|
+
<View style={styles.diff}>
|
|
45
|
+
{before != null ? (
|
|
46
|
+
<View style={styles.diffLine}>
|
|
47
|
+
<Text size="sm" weight="medium" tabular style={styles.marker(colors.red[600])}>
|
|
48
|
+
−
|
|
49
|
+
</Text>
|
|
50
|
+
<Text size="sm" tabular style={[styles.strike, styles.value(colors.red[600])]}>
|
|
51
|
+
{before}
|
|
52
|
+
</Text>
|
|
53
|
+
</View>
|
|
54
|
+
) : null}
|
|
55
|
+
<View style={styles.diffLine}>
|
|
56
|
+
<Text size="sm" weight="medium" tabular style={styles.marker(colors.emerald[700])}>
|
|
57
|
+
+
|
|
58
|
+
</Text>
|
|
59
|
+
<Text size="sm" weight="medium" tabular style={styles.value(colors.emerald[700])}>
|
|
60
|
+
{after}
|
|
61
|
+
</Text>
|
|
62
|
+
</View>
|
|
63
|
+
</View>
|
|
64
|
+
);
|
|
38
65
|
}
|
|
39
66
|
|
|
40
67
|
/**
|
|
41
|
-
* An agent-proposed edit to existing state,
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* a NEW value, this
|
|
68
|
+
* An agent-proposed edit to existing state, reviewed before it lands — never
|
|
69
|
+
* auto-applied. Two modes. Whole-set: a list of before→after diffs + apply /
|
|
70
|
+
* discard the lot. 1-BY-1 (when the host passes `onAcceptItem`/`onRejectItem`):
|
|
71
|
+
* each edit is its own card with Keep / Drop; deciding COLLAPSES the card to a
|
|
72
|
+
* preview with Undo, and a separate footer carries Accept-all + the single Apply
|
|
73
|
+
* that commits the kept set. Where `Suggestion` proposes a NEW value, this
|
|
74
|
+
* reviews CHANGES to existing ones.
|
|
47
75
|
*/
|
|
48
76
|
export function ChangeReview(props: ChangeReviewProps) {
|
|
49
77
|
const status = props.status ?? "open";
|
|
50
78
|
const perItem = props.onAcceptItem != null || props.onRejectItem != null;
|
|
51
|
-
const
|
|
79
|
+
const total = props.changes.length;
|
|
80
|
+
const keptCount = props.changes.filter((c) => c.status === "accepted").length;
|
|
52
81
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
const acceptAll = () =>
|
|
83
|
+
props.changes.forEach((c, i) => {
|
|
84
|
+
if ((c.status ?? "pending") === "pending") props.onAcceptItem?.(i);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const summaryNote = props.summary ? (
|
|
88
|
+
<View style={styles.note}>
|
|
89
|
+
<Text size="sm" color="muted">
|
|
90
|
+
{props.summary}
|
|
91
|
+
</Text>
|
|
92
|
+
</View>
|
|
93
|
+
) : null;
|
|
94
|
+
|
|
95
|
+
// ── 1-BY-1: each edit a card; deciding collapses it to a preview ──────────
|
|
96
|
+
if (perItem) {
|
|
97
|
+
return (
|
|
98
|
+
<View style={[styles.card, status !== "open" ? styles.resolved : null]}>
|
|
99
|
+
<View style={styles.head}>
|
|
100
|
+
<Text size="xs" color="muted" weight="medium" style={{ flex: 1 }}>
|
|
101
|
+
Suggested edits
|
|
60
102
|
</Text>
|
|
103
|
+
{status === "open" ? (
|
|
104
|
+
<Text size="xs" color="muted" tabular>
|
|
105
|
+
{keptCount} of {total} kept
|
|
106
|
+
</Text>
|
|
107
|
+
) : null}
|
|
61
108
|
</View>
|
|
62
|
-
|
|
109
|
+
{summaryNote}
|
|
63
110
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
111
|
+
{status !== "open" ? (
|
|
112
|
+
<Text size="xs" color="muted" weight="medium">
|
|
113
|
+
{status === "applied" ? "Applied" : "Discarded"}
|
|
114
|
+
</Text>
|
|
115
|
+
) : (
|
|
116
|
+
<>
|
|
117
|
+
<View style={styles.list}>
|
|
118
|
+
{props.changes.map((c, i) => {
|
|
119
|
+
const st = c.status ?? "pending";
|
|
120
|
+
if (st === "pending") {
|
|
121
|
+
return (
|
|
122
|
+
<View key={`${c.label}-${i}`} style={styles.block}>
|
|
123
|
+
<Text size="xs" color="muted" weight="medium">
|
|
124
|
+
{c.label}
|
|
125
|
+
</Text>
|
|
126
|
+
<DiffLines before={c.before} after={c.after} />
|
|
127
|
+
<View style={styles.cardActions}>
|
|
128
|
+
<Button title="Drop" color="muted" shape="rounded" onPress={() => props.onRejectItem?.(i)} />
|
|
129
|
+
<Button title="Keep" color="secondary" shape="rounded" onPress={() => props.onAcceptItem?.(i)} />
|
|
130
|
+
</View>
|
|
131
|
+
</View>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const kept = st === "accepted";
|
|
135
|
+
return (
|
|
136
|
+
<View key={`${c.label}-${i}`} style={styles.collapsed}>
|
|
137
|
+
<Text size="sm" weight="medium" numberOfLines={1}>
|
|
138
|
+
{c.label}
|
|
78
139
|
</Text>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
140
|
+
<Text
|
|
141
|
+
size="sm"
|
|
142
|
+
color={kept ? "default" : "muted"}
|
|
143
|
+
numberOfLines={1}
|
|
144
|
+
style={[{ flex: 1 }, kept ? null : styles.strike]}
|
|
145
|
+
>
|
|
146
|
+
{kept ? c.after : c.before ?? c.after}
|
|
147
|
+
</Text>
|
|
148
|
+
<View style={styles.statusTag}>
|
|
149
|
+
<Icon name={kept ? "check" : "x"} size={14} color={kept ? colors.emerald[600] : colors.zinc[400]} />
|
|
150
|
+
<Text size="sm" weight="medium" color="muted">
|
|
151
|
+
{kept ? "Kept" : "Dropped"}
|
|
152
|
+
</Text>
|
|
153
|
+
</View>
|
|
154
|
+
{props.onUndoItem ? (
|
|
155
|
+
<Button title="Undo" color="muted" shape="rounded" onPress={() => props.onUndoItem?.(i)} />
|
|
156
|
+
) : null}
|
|
157
|
+
</View>
|
|
158
|
+
);
|
|
159
|
+
})}
|
|
160
|
+
</View>
|
|
161
|
+
|
|
162
|
+
<View style={styles.footer}>
|
|
163
|
+
<Button title="Accept all" color="muted" shape="rounded" onPress={acceptAll} />
|
|
164
|
+
<View style={{ flex: 1 }} />
|
|
165
|
+
{props.onDiscard ? <Button title="Discard" color="muted" shape="rounded" onPress={props.onDiscard} /> : null}
|
|
166
|
+
{props.onApply ? (
|
|
167
|
+
<Button title={props.applyLabel ?? "Apply kept"} color="primary" shape="rounded" disabled={keptCount === 0} onPress={props.onApply} />
|
|
105
168
|
) : null}
|
|
106
169
|
</View>
|
|
107
|
-
|
|
108
|
-
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
109
172
|
</View>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
110
175
|
|
|
176
|
+
// ── WHOLE-SET: review the lot, apply / discard together ───────────────────
|
|
177
|
+
return (
|
|
178
|
+
<View style={[styles.card, status !== "open" ? styles.resolved : null]}>
|
|
179
|
+
<Text size="xs" color="muted" weight="medium">
|
|
180
|
+
Suggested edit
|
|
181
|
+
</Text>
|
|
182
|
+
{summaryNote}
|
|
183
|
+
<View style={styles.block}>
|
|
184
|
+
{props.changes.map((c, i) => (
|
|
185
|
+
<View key={`${c.label}-${i}`} style={i > 0 ? styles.changeSpacer : undefined}>
|
|
186
|
+
<Text size="xs" color="muted" weight="medium">
|
|
187
|
+
{c.label}
|
|
188
|
+
</Text>
|
|
189
|
+
<DiffLines before={c.before} after={c.after} />
|
|
190
|
+
</View>
|
|
191
|
+
))}
|
|
192
|
+
</View>
|
|
111
193
|
{status !== "open" ? (
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
size={14}
|
|
116
|
-
color={status === "applied" ? solid("emerald") : colors.zinc[400]}
|
|
117
|
-
/>
|
|
118
|
-
<Text size="xs" color="muted">
|
|
119
|
-
{status === "applied" ? "Applied" : "Discarded"}
|
|
120
|
-
</Text>
|
|
121
|
-
</View>
|
|
194
|
+
<Text size="xs" color="muted" weight="medium">
|
|
195
|
+
{status === "applied" ? "Applied" : "Discarded"}
|
|
196
|
+
</Text>
|
|
122
197
|
) : (
|
|
123
198
|
<View style={styles.footer}>
|
|
124
|
-
<View style={{ flex: 1 }}
|
|
125
|
-
|
|
126
|
-
<Text size="xs" color="muted">
|
|
127
|
-
{accepted} of {props.changes.length} accepted
|
|
128
|
-
</Text>
|
|
129
|
-
) : null}
|
|
130
|
-
</View>
|
|
131
|
-
{props.onDiscard ? <LinkButton title={perItem ? "Reject all" : "Discard"} onPress={props.onDiscard} /> : null}
|
|
199
|
+
<View style={{ flex: 1 }} />
|
|
200
|
+
{props.onDiscard ? <Button title="Discard" color="muted" shape="rounded" onPress={props.onDiscard} /> : null}
|
|
132
201
|
{props.onApply ? (
|
|
133
|
-
<Button
|
|
134
|
-
title={props.applyLabel ?? (perItem ? "Apply accepted" : "Apply")}
|
|
135
|
-
color="primary"
|
|
136
|
-
shape="rounded"
|
|
137
|
-
disabled={perItem && accepted === 0}
|
|
138
|
-
onPress={props.onApply}
|
|
139
|
-
/>
|
|
202
|
+
<Button title={props.applyLabel ?? "Apply"} color="primary" shape="rounded" onPress={props.onApply} />
|
|
140
203
|
) : null}
|
|
141
204
|
</View>
|
|
142
205
|
)}
|
|
@@ -144,23 +207,40 @@ export function ChangeReview(props: ChangeReviewProps) {
|
|
|
144
207
|
);
|
|
145
208
|
}
|
|
146
209
|
|
|
147
|
-
const styles =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
210
|
+
const styles = {
|
|
211
|
+
...StyleSheet.create({
|
|
212
|
+
card: {
|
|
213
|
+
borderWidth: 1,
|
|
214
|
+
borderColor: colors.border,
|
|
215
|
+
backgroundColor: colors.white,
|
|
216
|
+
borderRadius: 12,
|
|
217
|
+
padding: 16,
|
|
218
|
+
gap: 14,
|
|
219
|
+
},
|
|
220
|
+
resolved: { backgroundColor: colors.zinc[50] },
|
|
221
|
+
head: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
222
|
+
note: { borderLeftWidth: 2, borderLeftColor: colors.zinc[300], paddingLeft: 12 },
|
|
223
|
+
list: { gap: 8 },
|
|
224
|
+
block: { backgroundColor: colors.zinc[50], borderRadius: 10, padding: 14, gap: 10 },
|
|
225
|
+
cardActions: { flexDirection: "row", alignItems: "center", justifyContent: "flex-end", gap: 8 },
|
|
226
|
+
collapsed: {
|
|
227
|
+
flexDirection: "row",
|
|
228
|
+
alignItems: "center",
|
|
229
|
+
gap: 10,
|
|
230
|
+
backgroundColor: colors.zinc[50],
|
|
231
|
+
borderRadius: 10,
|
|
232
|
+
paddingLeft: 14,
|
|
233
|
+
paddingRight: 8,
|
|
234
|
+
paddingVertical: 6,
|
|
235
|
+
minHeight: 48,
|
|
236
|
+
},
|
|
237
|
+
statusTag: { flexDirection: "row", alignItems: "center", gap: 4 },
|
|
238
|
+
changeSpacer: { marginTop: 10, borderTopWidth: 1, borderTopColor: colors.zinc[200], paddingTop: 10 },
|
|
239
|
+
diff: { gap: 3 },
|
|
240
|
+
diffLine: { flexDirection: "row", alignItems: "baseline", gap: 8 },
|
|
241
|
+
strike: { textDecorationLine: "line-through" },
|
|
242
|
+
footer: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
243
|
+
}),
|
|
244
|
+
marker: (color: string) => ({ color, width: 12 }),
|
|
245
|
+
value: (color: string) => ({ color }),
|
|
246
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { StyleSheet, View } from "react-native";
|
|
2
|
+
import { colors } from "./colors";
|
|
3
|
+
import { Text } from "./text";
|
|
4
|
+
import { Icon } from "./icon";
|
|
5
|
+
import { CardSelectItem } from "./card_select_item";
|
|
6
|
+
|
|
7
|
+
export interface ChoiceOption {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string;
|
|
10
|
+
/** An optional second line under the label. */
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ChoiceListProps {
|
|
15
|
+
options: ChoiceOption[];
|
|
16
|
+
/** The chosen value. */
|
|
17
|
+
value?: string;
|
|
18
|
+
onSelect: (value: string) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A vertical set of selectable answer options — bordered cards with the global
|
|
23
|
+
* focus/hover/selected ring (`CardSelectItem`), single-select and freely
|
|
24
|
+
* switchable (pick a different option any time). The chosen card shows a check.
|
|
25
|
+
* The agent's quick-reply surface (`Clarify` uses it) and any "pick one"
|
|
26
|
+
* question.
|
|
27
|
+
*/
|
|
28
|
+
export function ChoiceList(props: ChoiceListProps) {
|
|
29
|
+
const { options, value, onSelect } = props;
|
|
30
|
+
return (
|
|
31
|
+
<View style={{ gap: 8 }}>
|
|
32
|
+
{options.map((o) => {
|
|
33
|
+
const selected = value === o.value;
|
|
34
|
+
return (
|
|
35
|
+
<CardSelectItem
|
|
36
|
+
key={o.value}
|
|
37
|
+
selected={selected}
|
|
38
|
+
onPress={() => onSelect(o.value)}
|
|
39
|
+
accessibilityLabel={o.label}
|
|
40
|
+
style={styles.item}
|
|
41
|
+
>
|
|
42
|
+
<View style={styles.text}>
|
|
43
|
+
<Text size="sm" weight="medium">
|
|
44
|
+
{o.label}
|
|
45
|
+
</Text>
|
|
46
|
+
{o.description ? (
|
|
47
|
+
<Text size="xs" color="muted">
|
|
48
|
+
{o.description}
|
|
49
|
+
</Text>
|
|
50
|
+
) : null}
|
|
51
|
+
</View>
|
|
52
|
+
{selected ? <Icon name="check" size={16} color={colors.zinc[900]} /> : null}
|
|
53
|
+
</CardSelectItem>
|
|
54
|
+
);
|
|
55
|
+
})}
|
|
56
|
+
</View>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const styles = StyleSheet.create({
|
|
61
|
+
item: { flexDirection: "row", alignItems: "center", gap: 10, paddingVertical: 11, paddingHorizontal: 14 },
|
|
62
|
+
text: { flex: 1, gap: 2 },
|
|
63
|
+
});
|
package/src/clarify.tsx
CHANGED
|
@@ -1,54 +1,37 @@
|
|
|
1
1
|
import { StyleSheet, View } from "react-native";
|
|
2
|
-
import { colors
|
|
2
|
+
import { colors } from "./colors";
|
|
3
3
|
import { Text } from "./text";
|
|
4
|
-
import {
|
|
5
|
-
import { Button } from "./button";
|
|
4
|
+
import { ChoiceList, type ChoiceOption } from "./choice_list";
|
|
6
5
|
|
|
7
|
-
export
|
|
8
|
-
label: string;
|
|
9
|
-
value: string;
|
|
10
|
-
}
|
|
6
|
+
export type ClarifyOption = ChoiceOption;
|
|
11
7
|
|
|
12
8
|
export interface ClarifyProps {
|
|
13
9
|
/** The agent's question — what it needs settled to proceed. */
|
|
14
10
|
question: string;
|
|
15
11
|
options: ClarifyOption[];
|
|
16
12
|
onAnswer: (value: string) => void;
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
13
|
+
/** The chosen option's VALUE — the picked choice shows selected; it stays
|
|
14
|
+
* switchable so the human can change their mind. */
|
|
19
15
|
answer?: string;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
/**
|
|
23
|
-
* The agent asks BACK — a question with
|
|
24
|
-
* before the run continues
|
|
25
|
-
*
|
|
26
|
-
*
|
|
19
|
+
* The agent asks BACK — a question with selectable answer options (`ChoiceList`)
|
|
20
|
+
* the human picks before the run continues, freely switchable until committed.
|
|
21
|
+
* Human-in-the-loop input: when the agent is unsure, it clarifies instead of
|
|
22
|
+
* guessing wrong. The run pauses on this and `onAnswer` resumes it. Pair with
|
|
23
|
+
* `AgentRun` / `AgentProgress`.
|
|
27
24
|
*/
|
|
28
25
|
export function Clarify(props: ClarifyProps) {
|
|
29
|
-
const answered = props.answer != null;
|
|
30
26
|
return (
|
|
31
27
|
<View style={styles.card}>
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</
|
|
38
|
-
{
|
|
39
|
-
<View style={styles.answered}>
|
|
40
|
-
<Icon name="circle-check" size={14} color={solid("emerald")} />
|
|
41
|
-
<Text size="sm" color="muted">
|
|
42
|
-
{props.answer}
|
|
43
|
-
</Text>
|
|
44
|
-
</View>
|
|
45
|
-
) : (
|
|
46
|
-
<View style={styles.options}>
|
|
47
|
-
{props.options.map((o) => (
|
|
48
|
-
<Button key={o.value} title={o.label} color="secondary" shape="rounded" onPress={() => props.onAnswer(o.value)} />
|
|
49
|
-
))}
|
|
50
|
-
</View>
|
|
51
|
-
)}
|
|
28
|
+
<Text size="xs" color="muted" weight="medium">
|
|
29
|
+
Question
|
|
30
|
+
</Text>
|
|
31
|
+
<Text size="sm" weight="medium">
|
|
32
|
+
{props.question}
|
|
33
|
+
</Text>
|
|
34
|
+
<ChoiceList options={props.options} value={props.answer} onSelect={props.onAnswer} />
|
|
52
35
|
</View>
|
|
53
36
|
);
|
|
54
37
|
}
|
|
@@ -56,13 +39,10 @@ export function Clarify(props: ClarifyProps) {
|
|
|
56
39
|
const styles = StyleSheet.create({
|
|
57
40
|
card: {
|
|
58
41
|
borderWidth: 1,
|
|
59
|
-
borderColor:
|
|
42
|
+
borderColor: colors.border,
|
|
60
43
|
backgroundColor: colors.white,
|
|
61
44
|
borderRadius: 12,
|
|
62
|
-
padding:
|
|
45
|
+
padding: 16,
|
|
63
46
|
gap: 12,
|
|
64
47
|
},
|
|
65
|
-
header: { flexDirection: "row", alignItems: "flex-start", gap: 8 },
|
|
66
|
-
options: { flexDirection: "row", flexWrap: "wrap", gap: 8 },
|
|
67
|
-
answered: { flexDirection: "row", alignItems: "center", gap: 6 },
|
|
68
48
|
});
|
package/src/confidence.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
1
|
+
import { StyleSheet, View } from "react-native";
|
|
2
|
+
import { colors, solid, type ColorName } from "./colors";
|
|
3
|
+
import { Text } from "./text";
|
|
3
4
|
|
|
4
5
|
export type ConfidenceLevel = "high" | "medium" | "low";
|
|
5
6
|
|
|
@@ -10,8 +11,9 @@ export interface ConfidenceProps {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const LABEL: Record<ConfidenceLevel, string> = { high: "High", medium: "Medium", low: "Low" };
|
|
13
|
-
|
|
14
|
-
// emerald, medium amber
|
|
14
|
+
const FILLED: Record<ConfidenceLevel, number> = { high: 3, medium: 2, low: 1 };
|
|
15
|
+
// high emerald, medium amber, low zinc (unsure — not an error). One family per
|
|
16
|
+
// level; the fill count AND the colour both carry it.
|
|
15
17
|
const COLOR: Record<ConfidenceLevel, ColorName> = { high: "emerald", medium: "amber", low: "zinc" };
|
|
16
18
|
|
|
17
19
|
export function levelFromScore(score: number): ConfidenceLevel {
|
|
@@ -21,11 +23,31 @@ export function levelFromScore(score: number): ConfidenceLevel {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
|
-
* How sure the AI is — a calibrated high/medium/low
|
|
25
|
-
* (
|
|
26
|
-
*
|
|
26
|
+
* How sure the AI is — a calibrated high / medium / low read as a 3-tick meter
|
|
27
|
+
* (emerald / amber / zinc) beside the level word. The fill count and the colour
|
|
28
|
+
* both carry the level. The human weights an AI proposal or estimate by it. Pair
|
|
29
|
+
* with `Suggestion` and `RecordReview`.
|
|
27
30
|
*/
|
|
28
31
|
export function Confidence(props: ConfidenceProps) {
|
|
29
32
|
const level = props.level ?? (props.score != null ? levelFromScore(props.score) : "medium");
|
|
30
|
-
|
|
33
|
+
const filled = FILLED[level];
|
|
34
|
+
const fill = solid(COLOR[level]);
|
|
35
|
+
return (
|
|
36
|
+
<View style={styles.row} accessibilityLabel={`${LABEL[level]} confidence`}>
|
|
37
|
+
<View style={styles.meter}>
|
|
38
|
+
{[0, 1, 2].map((i) => (
|
|
39
|
+
<View key={i} style={[styles.tick, { backgroundColor: i < filled ? fill : colors.zinc[200] }]} />
|
|
40
|
+
))}
|
|
41
|
+
</View>
|
|
42
|
+
<Text size="xs" weight="medium" style={{ color: fill }}>
|
|
43
|
+
{LABEL[level]}
|
|
44
|
+
</Text>
|
|
45
|
+
</View>
|
|
46
|
+
);
|
|
31
47
|
}
|
|
48
|
+
|
|
49
|
+
const styles = StyleSheet.create({
|
|
50
|
+
row: { flexDirection: "row", alignItems: "center", gap: 6 },
|
|
51
|
+
meter: { flexDirection: "row", alignItems: "center", gap: 2 },
|
|
52
|
+
tick: { width: 3, height: 10, borderRadius: 1 },
|
|
53
|
+
});
|