@opengeni/react-native 1.4.5-canary.37615713321001
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/LICENSE +190 -0
- package/README.md +74 -0
- package/dist/adapters.d.ts +53 -0
- package/dist/adapters.js +3 -0
- package/dist/adapters.js.map +1 -0
- package/dist/attachments.d.ts +75 -0
- package/dist/chunk-3VSZAQ7G.js +391 -0
- package/dist/chunk-3VSZAQ7G.js.map +1 -0
- package/dist/chunk-5ZVK7WKA.js +88 -0
- package/dist/chunk-5ZVK7WKA.js.map +1 -0
- package/dist/chunk-EEDIMY3L.js +240 -0
- package/dist/chunk-EEDIMY3L.js.map +1 -0
- package/dist/chunk-M23SDI4R.js +83 -0
- package/dist/chunk-M23SDI4R.js.map +1 -0
- package/dist/chunk-PHJBSAR3.js +2006 -0
- package/dist/chunk-PHJBSAR3.js.map +1 -0
- package/dist/environment.d.ts +19 -0
- package/dist/expo.d.ts +25 -0
- package/dist/expo.js +267 -0
- package/dist/expo.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +3345 -0
- package/dist/index.js.map +1 -0
- package/dist/presentation.d.ts +262 -0
- package/dist/read-acknowledgement.d.ts +39 -0
- package/dist/realtime/call-events.d.ts +11 -0
- package/dist/realtime/call-view.d.ts +26 -0
- package/dist/realtime/expo-call.d.ts +11 -0
- package/dist/realtime/native-call.d.ts +104 -0
- package/dist/realtime/native-realtime.d.ts +35 -0
- package/dist/realtime/use-native-session-realtime.d.ts +25 -0
- package/dist/session-view.d.ts +87 -0
- package/dist/timeline/activity.d.ts +34 -0
- package/dist/timeline/composer.d.ts +86 -0
- package/dist/timeline/controls.d.ts +27 -0
- package/dist/timeline/decisions.d.ts +29 -0
- package/dist/timeline/feedback.d.ts +26 -0
- package/dist/timeline/icon-nodes.generated.d.ts +738 -0
- package/dist/timeline/icon.d.ts +11 -0
- package/dist/timeline/index.d.ts +32 -0
- package/dist/timeline/index.js +5419 -0
- package/dist/timeline/index.js.map +1 -0
- package/dist/timeline/markdown.d.ts +11 -0
- package/dist/timeline/markdown.js +415 -0
- package/dist/timeline/markdown.js.map +1 -0
- package/dist/timeline/message-attachments.d.ts +13 -0
- package/dist/timeline/message-timeline.d.ts +36 -0
- package/dist/timeline/model-picker.d.ts +17 -0
- package/dist/timeline/primitives.d.ts +85 -0
- package/dist/timeline/queue-dock.d.ts +19 -0
- package/dist/timeline/session-actions.d.ts +34 -0
- package/dist/timeline/session-list.d.ts +50 -0
- package/dist/timeline/session-screen.d.ts +35 -0
- package/dist/timeline/session-signals.d.ts +37 -0
- package/dist/timeline/sheet.d.ts +14 -0
- package/dist/timeline/theme.d.ts +58 -0
- package/dist/timeline/thinking-orb.d.ts +13 -0
- package/dist/timeline/turn.d.ts +80 -0
- package/dist/ui/attention.d.ts +14 -0
- package/dist/ui/cards.d.ts +21 -0
- package/dist/ui/composer.d.ts +21 -0
- package/dist/ui/format.d.ts +13 -0
- package/dist/ui/icons.d.ts +9 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +1757 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/steps-sheet.d.ts +10 -0
- package/dist/ui/theme.d.ts +63 -0
- package/dist/ui/timeline.d.ts +48 -0
- package/dist/ui/web-tokens.gen.d.ts +90 -0
- package/dist/use-native-session.d.ts +40 -0
- package/dist/view-model.d.ts +52 -0
- package/dist/voice-input.d.ts +102 -0
- package/dist/webrtc.d.ts +9 -0
- package/dist/webrtc.js +19 -0
- package/dist/webrtc.js.map +1 -0
- package/expo-module.config.json +7 -0
- package/ios/OpenGeniCall.podspec +22 -0
- package/ios/OpenGeniCallAppDelegateSubscriber.swift +29 -0
- package/ios/OpenGeniCallCenter.swift +189 -0
- package/ios/OpenGeniCallModule.swift +64 -0
- package/package.json +93 -0
- package/src/adapters.ts +150 -0
- package/src/attachments.ts +396 -0
- package/src/environment.tsx +214 -0
- package/src/expo.ts +295 -0
- package/src/index.ts +98 -0
- package/src/presentation.ts +285 -0
- package/src/read-acknowledgement.ts +72 -0
- package/src/realtime/call-events.ts +41 -0
- package/src/realtime/call-view.tsx +220 -0
- package/src/realtime/expo-call.ts +49 -0
- package/src/realtime/native-call.ts +266 -0
- package/src/realtime/native-realtime.ts +115 -0
- package/src/realtime/use-native-session-realtime.ts +29 -0
- package/src/session-view.tsx +2251 -0
- package/src/timeline/activity.tsx +736 -0
- package/src/timeline/composer.tsx +686 -0
- package/src/timeline/controls.tsx +151 -0
- package/src/timeline/decisions.tsx +651 -0
- package/src/timeline/feedback.tsx +251 -0
- package/src/timeline/icon-nodes.generated.ts +1830 -0
- package/src/timeline/icon.tsx +44 -0
- package/src/timeline/index.ts +67 -0
- package/src/timeline/markdown.tsx +510 -0
- package/src/timeline/message-attachments.tsx +155 -0
- package/src/timeline/message-timeline.tsx +1042 -0
- package/src/timeline/model-picker.tsx +450 -0
- package/src/timeline/primitives.tsx +561 -0
- package/src/timeline/queue-dock.tsx +331 -0
- package/src/timeline/session-actions.tsx +379 -0
- package/src/timeline/session-list.tsx +338 -0
- package/src/timeline/session-screen.tsx +488 -0
- package/src/timeline/session-signals.tsx +312 -0
- package/src/timeline/sheet.tsx +153 -0
- package/src/timeline/theme.tsx +128 -0
- package/src/timeline/thinking-orb.tsx +93 -0
- package/src/timeline/turn.tsx +569 -0
- package/src/ui/attention.tsx +220 -0
- package/src/ui/cards.tsx +267 -0
- package/src/ui/composer.tsx +296 -0
- package/src/ui/format.ts +86 -0
- package/src/ui/icons.tsx +131 -0
- package/src/ui/index.ts +18 -0
- package/src/ui/steps-sheet.tsx +71 -0
- package/src/ui/theme.ts +261 -0
- package/src/ui/timeline.tsx +837 -0
- package/src/ui/web-tokens.gen.ts +94 -0
- package/src/use-native-session.ts +219 -0
- package/src/view-model.ts +485 -0
- package/src/voice-input.ts +382 -0
- package/src/webrtc.ts +30 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { Platform, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
|
|
4
|
+
import { AgentIcon } from "./icons";
|
|
5
|
+
import type { AgentTheme } from "./theme";
|
|
6
|
+
|
|
7
|
+
export interface AgentComposerProps {
|
|
8
|
+
theme: AgentTheme;
|
|
9
|
+
value: string;
|
|
10
|
+
onChangeText(text: string): void;
|
|
11
|
+
onSend(): void;
|
|
12
|
+
onStop?: (() => void) | undefined;
|
|
13
|
+
onAttach?: (() => void) | undefined;
|
|
14
|
+
onVoice?: (() => void) | undefined;
|
|
15
|
+
running: boolean;
|
|
16
|
+
placeholder: string;
|
|
17
|
+
/** Chips shown in the bar composer (model, permissions…). */
|
|
18
|
+
accessories?: ReactNode | undefined;
|
|
19
|
+
bottomInset: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function RoundButton(props: {
|
|
23
|
+
label: string;
|
|
24
|
+
onPress?: (() => void) | undefined;
|
|
25
|
+
size: number;
|
|
26
|
+
background: string;
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
disabled?: boolean | undefined;
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<Pressable
|
|
32
|
+
accessibilityLabel={props.label}
|
|
33
|
+
accessibilityRole="button"
|
|
34
|
+
disabled={props.disabled}
|
|
35
|
+
hitSlop={6}
|
|
36
|
+
onPress={props.onPress}
|
|
37
|
+
style={({ pressed }) => ({
|
|
38
|
+
width: props.size,
|
|
39
|
+
height: props.size,
|
|
40
|
+
borderRadius: props.size / 2,
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "center",
|
|
43
|
+
backgroundColor: props.background,
|
|
44
|
+
opacity: props.disabled ? 0.35 : pressed ? 0.7 : 1,
|
|
45
|
+
})}
|
|
46
|
+
>
|
|
47
|
+
{props.children}
|
|
48
|
+
</Pressable>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function AgentComposer(props: AgentComposerProps) {
|
|
53
|
+
const { theme } = props;
|
|
54
|
+
if (theme.layout.composer === "bar") return <BarComposer {...props} />;
|
|
55
|
+
if (theme.layout.composer === "voice") return <VoiceComposer {...props} />;
|
|
56
|
+
return <PillComposer {...props} />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function SendOrStop(props: AgentComposerProps & { size: number }) {
|
|
60
|
+
const { theme, running, value } = props;
|
|
61
|
+
if (running && !value.trim()) {
|
|
62
|
+
return (
|
|
63
|
+
<RoundButton
|
|
64
|
+
background={theme.colors.accent}
|
|
65
|
+
label="Stop"
|
|
66
|
+
onPress={props.onStop}
|
|
67
|
+
size={props.size}
|
|
68
|
+
>
|
|
69
|
+
<AgentIcon color={theme.colors.onAccent} name="stop" size={props.size * 0.4} />
|
|
70
|
+
</RoundButton>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return (
|
|
74
|
+
<RoundButton
|
|
75
|
+
background={theme.colors.accent}
|
|
76
|
+
disabled={!value.trim()}
|
|
77
|
+
label="Send"
|
|
78
|
+
onPress={props.onSend}
|
|
79
|
+
size={props.size}
|
|
80
|
+
>
|
|
81
|
+
<AgentIcon
|
|
82
|
+
color={theme.colors.onAccent}
|
|
83
|
+
name="arrow-up"
|
|
84
|
+
size={props.size * 0.5}
|
|
85
|
+
strokeWidth={2.4}
|
|
86
|
+
/>
|
|
87
|
+
</RoundButton>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Input(props: AgentComposerProps & { fontSize: number }) {
|
|
92
|
+
return (
|
|
93
|
+
<TextInput
|
|
94
|
+
accessibilityLabel={props.placeholder}
|
|
95
|
+
multiline
|
|
96
|
+
onChangeText={props.onChangeText}
|
|
97
|
+
placeholder={props.placeholder}
|
|
98
|
+
placeholderTextColor={props.theme.colors.textFaint}
|
|
99
|
+
style={{
|
|
100
|
+
flex: 1,
|
|
101
|
+
color: props.theme.colors.text,
|
|
102
|
+
fontSize: props.fontSize,
|
|
103
|
+
minHeight: 40,
|
|
104
|
+
maxHeight: 160,
|
|
105
|
+
paddingTop: Platform.OS === "ios" ? 10 : 8,
|
|
106
|
+
paddingBottom: Platform.OS === "ios" ? 10 : 8,
|
|
107
|
+
}}
|
|
108
|
+
value={props.value}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Calm: floating pill; Liquid Glass on iOS 26. */
|
|
114
|
+
function PillComposer(props: AgentComposerProps) {
|
|
115
|
+
const { theme } = props;
|
|
116
|
+
const glass = Platform.OS === "ios" && isLiquidGlassAvailable();
|
|
117
|
+
const inner = (
|
|
118
|
+
<View
|
|
119
|
+
style={{
|
|
120
|
+
flexDirection: "row",
|
|
121
|
+
alignItems: "flex-end",
|
|
122
|
+
gap: 6,
|
|
123
|
+
paddingHorizontal: 6,
|
|
124
|
+
paddingVertical: 6,
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<RoundButton
|
|
128
|
+
background="transparent"
|
|
129
|
+
label="Add attachment"
|
|
130
|
+
onPress={props.onAttach}
|
|
131
|
+
size={36}
|
|
132
|
+
>
|
|
133
|
+
<AgentIcon color={theme.colors.text} name="plus" size={20} />
|
|
134
|
+
</RoundButton>
|
|
135
|
+
<Input {...props} fontSize={theme.type.body} />
|
|
136
|
+
{props.value.trim() || props.running ? (
|
|
137
|
+
<SendOrStop {...props} size={36} />
|
|
138
|
+
) : (
|
|
139
|
+
<RoundButton background="transparent" label="Dictate" onPress={props.onVoice} size={36}>
|
|
140
|
+
<AgentIcon color={theme.colors.text} name="mic" size={19} />
|
|
141
|
+
</RoundButton>
|
|
142
|
+
)}
|
|
143
|
+
</View>
|
|
144
|
+
);
|
|
145
|
+
const shell = {
|
|
146
|
+
borderRadius: theme.radius.composer,
|
|
147
|
+
overflow: "hidden" as const,
|
|
148
|
+
borderWidth: glass ? 0 : StyleSheet.hairlineWidth * 2,
|
|
149
|
+
borderColor: theme.colors.border,
|
|
150
|
+
backgroundColor: glass ? undefined : theme.colors.surfaceRaised,
|
|
151
|
+
shadowColor: "#000",
|
|
152
|
+
shadowOpacity: theme.scheme === "light" ? 0.08 : 0,
|
|
153
|
+
shadowRadius: 16,
|
|
154
|
+
shadowOffset: { width: 0, height: 6 },
|
|
155
|
+
};
|
|
156
|
+
return (
|
|
157
|
+
<View
|
|
158
|
+
style={{
|
|
159
|
+
paddingHorizontal: 12,
|
|
160
|
+
paddingTop: 6,
|
|
161
|
+
paddingBottom: Math.max(props.bottomInset, 10),
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
{glass ? (
|
|
165
|
+
<GlassView glassEffectStyle="regular" isInteractive style={shell}>
|
|
166
|
+
{inner}
|
|
167
|
+
</GlassView>
|
|
168
|
+
) : (
|
|
169
|
+
<View style={shell}>{inner}</View>
|
|
170
|
+
)}
|
|
171
|
+
</View>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Workbench: docked bar with a toolbar row for context chips. */
|
|
176
|
+
function BarComposer(props: AgentComposerProps) {
|
|
177
|
+
const { theme } = props;
|
|
178
|
+
return (
|
|
179
|
+
<View
|
|
180
|
+
style={{
|
|
181
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
182
|
+
borderColor: theme.colors.border,
|
|
183
|
+
backgroundColor: theme.colors.surfaceRaised,
|
|
184
|
+
paddingHorizontal: 12,
|
|
185
|
+
paddingTop: 8,
|
|
186
|
+
paddingBottom: Math.max(props.bottomInset, 8),
|
|
187
|
+
gap: 6,
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<View
|
|
191
|
+
style={{
|
|
192
|
+
borderRadius: theme.radius.composer,
|
|
193
|
+
borderWidth: StyleSheet.hairlineWidth * 2,
|
|
194
|
+
borderColor: theme.colors.border,
|
|
195
|
+
backgroundColor: theme.colors.background,
|
|
196
|
+
paddingHorizontal: 12,
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
<Input {...props} fontSize={theme.type.body} />
|
|
200
|
+
</View>
|
|
201
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
|
202
|
+
<RoundButton
|
|
203
|
+
background={theme.colors.surface}
|
|
204
|
+
label="Add attachment"
|
|
205
|
+
onPress={props.onAttach}
|
|
206
|
+
size={34}
|
|
207
|
+
>
|
|
208
|
+
<AgentIcon color={theme.colors.text} name="plus" size={18} />
|
|
209
|
+
</RoundButton>
|
|
210
|
+
<RoundButton
|
|
211
|
+
background={theme.colors.surface}
|
|
212
|
+
label="Dictate"
|
|
213
|
+
onPress={props.onVoice}
|
|
214
|
+
size={34}
|
|
215
|
+
>
|
|
216
|
+
<AgentIcon color={theme.colors.text} name="mic" size={17} />
|
|
217
|
+
</RoundButton>
|
|
218
|
+
<View style={{ flex: 1, flexDirection: "row", gap: 6 }}>{props.accessories}</View>
|
|
219
|
+
<SendOrStop {...props} size={34} />
|
|
220
|
+
</View>
|
|
221
|
+
</View>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Field: large field with a dominant voice button. */
|
|
226
|
+
function VoiceComposer(props: AgentComposerProps) {
|
|
227
|
+
const { theme } = props;
|
|
228
|
+
const hasText = props.value.trim().length > 0;
|
|
229
|
+
return (
|
|
230
|
+
<View
|
|
231
|
+
style={{
|
|
232
|
+
flexDirection: "row",
|
|
233
|
+
alignItems: "flex-end",
|
|
234
|
+
gap: 10,
|
|
235
|
+
paddingHorizontal: 14,
|
|
236
|
+
paddingTop: 8,
|
|
237
|
+
paddingBottom: Math.max(props.bottomInset, 12),
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
<View
|
|
241
|
+
style={{
|
|
242
|
+
flex: 1,
|
|
243
|
+
flexDirection: "row",
|
|
244
|
+
alignItems: "flex-end",
|
|
245
|
+
minHeight: 56,
|
|
246
|
+
borderRadius: theme.radius.composer,
|
|
247
|
+
backgroundColor: theme.colors.surface,
|
|
248
|
+
borderWidth: StyleSheet.hairlineWidth * 2,
|
|
249
|
+
borderColor: theme.colors.border,
|
|
250
|
+
paddingLeft: 6,
|
|
251
|
+
paddingRight: 14,
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
<RoundButton
|
|
255
|
+
background="transparent"
|
|
256
|
+
label="Add photo or file"
|
|
257
|
+
onPress={props.onAttach}
|
|
258
|
+
size={44}
|
|
259
|
+
>
|
|
260
|
+
<AgentIcon color={theme.colors.text} name="plus" size={24} />
|
|
261
|
+
</RoundButton>
|
|
262
|
+
<Input {...props} fontSize={theme.type.body} />
|
|
263
|
+
</View>
|
|
264
|
+
{hasText || props.running ? (
|
|
265
|
+
<SendOrStop {...props} size={56} />
|
|
266
|
+
) : (
|
|
267
|
+
<RoundButton
|
|
268
|
+
background={theme.colors.accent}
|
|
269
|
+
label="Talk"
|
|
270
|
+
onPress={props.onVoice}
|
|
271
|
+
size={56}
|
|
272
|
+
>
|
|
273
|
+
<AgentIcon color={theme.colors.onAccent} name="mic" size={26} />
|
|
274
|
+
</RoundButton>
|
|
275
|
+
)}
|
|
276
|
+
</View>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function ComposerChip({ theme, label }: { theme: AgentTheme; label: string }) {
|
|
281
|
+
return (
|
|
282
|
+
<View
|
|
283
|
+
style={{
|
|
284
|
+
height: 28,
|
|
285
|
+
paddingHorizontal: 10,
|
|
286
|
+
borderRadius: 14,
|
|
287
|
+
backgroundColor: theme.colors.surface,
|
|
288
|
+
justifyContent: "center",
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
<Text style={{ color: theme.colors.textMuted, fontSize: 12, fontWeight: "500" }}>
|
|
292
|
+
{label}
|
|
293
|
+
</Text>
|
|
294
|
+
</View>
|
|
295
|
+
);
|
|
296
|
+
}
|
package/src/ui/format.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ActivityItem, TimelineGroup, ToolCallItem } from "@opengeni/react/session";
|
|
2
|
+
import { toolDisplayName } from "@opengeni/react/session";
|
|
3
|
+
|
|
4
|
+
export function toolTitle(item: ToolCallItem): string {
|
|
5
|
+
return toolDisplayName(item.name, item.display);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function firstString(record: Record<string, unknown>, keys: string[]): string | null {
|
|
9
|
+
for (const key of keys) {
|
|
10
|
+
const value = record[key];
|
|
11
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One-line human summary of a tool call's input. */
|
|
17
|
+
export function toolDetail(item: ToolCallItem): string | null {
|
|
18
|
+
const args = item.arguments;
|
|
19
|
+
if (typeof args === "string") return args.split("\n")[0] ?? null;
|
|
20
|
+
if (!args || typeof args !== "object") return null;
|
|
21
|
+
const record = args as Record<string, unknown>;
|
|
22
|
+
const known = firstString(record, [
|
|
23
|
+
"cmd",
|
|
24
|
+
"command",
|
|
25
|
+
"query",
|
|
26
|
+
"q",
|
|
27
|
+
"path",
|
|
28
|
+
"url",
|
|
29
|
+
"title",
|
|
30
|
+
"id",
|
|
31
|
+
"name",
|
|
32
|
+
]);
|
|
33
|
+
if (known) return known;
|
|
34
|
+
const pairs = Object.entries(record)
|
|
35
|
+
.filter(([, value]) => value !== null && value !== undefined && value !== "")
|
|
36
|
+
.slice(0, 3)
|
|
37
|
+
.map(
|
|
38
|
+
([key, value]) =>
|
|
39
|
+
`${key.replace(/[_-]+/g, " ")}: ${typeof value === "string" ? value : JSON.stringify(value)}`,
|
|
40
|
+
);
|
|
41
|
+
if (pairs.length === 0) return null;
|
|
42
|
+
const text = pairs.join(" · ");
|
|
43
|
+
return text.length > 110 ? `${text.slice(0, 109)}…` : text;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Short preview of a settled tool output. */
|
|
47
|
+
export function toolOutputPreview(item: ToolCallItem, lines = 2): string | null {
|
|
48
|
+
const output = item.output;
|
|
49
|
+
const text =
|
|
50
|
+
typeof output === "string"
|
|
51
|
+
? output
|
|
52
|
+
: output &&
|
|
53
|
+
typeof output === "object" &&
|
|
54
|
+
typeof (output as { text?: unknown }).text === "string"
|
|
55
|
+
? String((output as { text: string }).text)
|
|
56
|
+
: output == null
|
|
57
|
+
? null
|
|
58
|
+
: JSON.stringify(output);
|
|
59
|
+
if (!text) return null;
|
|
60
|
+
return text.split("\n").slice(0, lines).join("\n");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatDuration(ms: number): string {
|
|
64
|
+
if (!Number.isFinite(ms) || ms < 1000) return "";
|
|
65
|
+
const seconds = Math.round(ms / 1000);
|
|
66
|
+
if (seconds < 60) return `${seconds}s`;
|
|
67
|
+
const minutes = Math.floor(seconds / 60);
|
|
68
|
+
const rest = seconds % 60;
|
|
69
|
+
return rest ? `${minutes}m ${rest}s` : `${minutes}m`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function itemTime(item: { occurredAt?: string }): number {
|
|
73
|
+
return item.occurredAt ? Date.parse(item.occurredAt) : Number.NaN;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Flatten nested work details of a group into its activity items, in order. */
|
|
77
|
+
export function activityItemsOf(group: TimelineGroup): ActivityItem[] {
|
|
78
|
+
if (group.kind === "item") return [];
|
|
79
|
+
if (group.kind === "turn") return group.groups.flatMap(activityItemsOf);
|
|
80
|
+
const nested = group.work?.details.flatMap(activityItemsOf) ?? [];
|
|
81
|
+
return nested.length > 0 ? nested : group.items;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function toolCallsOf(items: readonly ActivityItem[]): ToolCallItem[] {
|
|
85
|
+
return items.filter((item): item is ToolCallItem => item.kind === "tool-call");
|
|
86
|
+
}
|
package/src/ui/icons.tsx
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Minimal stroke icon set (Lucide geometry, ISC) so the kit has no icon-font dependency.
|
|
2
|
+
import Svg, { Circle, Path, Polyline, Rect } from "react-native-svg";
|
|
3
|
+
|
|
4
|
+
export type AgentIconName =
|
|
5
|
+
| "terminal"
|
|
6
|
+
| "search"
|
|
7
|
+
| "edit"
|
|
8
|
+
| "globe"
|
|
9
|
+
| "check"
|
|
10
|
+
| "x"
|
|
11
|
+
| "chevron-right"
|
|
12
|
+
| "chevron-down"
|
|
13
|
+
| "sparkle"
|
|
14
|
+
| "mic"
|
|
15
|
+
| "plus"
|
|
16
|
+
| "arrow-up"
|
|
17
|
+
| "stop"
|
|
18
|
+
| "tool"
|
|
19
|
+
| "send"
|
|
20
|
+
| "alert"
|
|
21
|
+
| "question"
|
|
22
|
+
| "rocket";
|
|
23
|
+
|
|
24
|
+
export function AgentIcon({
|
|
25
|
+
name,
|
|
26
|
+
size = 18,
|
|
27
|
+
color,
|
|
28
|
+
strokeWidth = 2,
|
|
29
|
+
}: {
|
|
30
|
+
name: AgentIconName;
|
|
31
|
+
size?: number;
|
|
32
|
+
color: string;
|
|
33
|
+
strokeWidth?: number;
|
|
34
|
+
}) {
|
|
35
|
+
const common = {
|
|
36
|
+
stroke: color,
|
|
37
|
+
strokeWidth,
|
|
38
|
+
strokeLinecap: "round" as const,
|
|
39
|
+
strokeLinejoin: "round" as const,
|
|
40
|
+
fill: "none",
|
|
41
|
+
};
|
|
42
|
+
return (
|
|
43
|
+
<Svg width={size} height={size} viewBox="0 0 24 24">
|
|
44
|
+
{name === "terminal" ? (
|
|
45
|
+
<>
|
|
46
|
+
<Polyline points="4 17 10 11 4 5" {...common} />
|
|
47
|
+
<Path d="M 12 19h8" {...common} />
|
|
48
|
+
</>
|
|
49
|
+
) : null}
|
|
50
|
+
{name === "search" ? (
|
|
51
|
+
<>
|
|
52
|
+
<Circle cx="11" cy="11" r="7" {...common} />
|
|
53
|
+
<Path d="m20 20-3.5-3.5" {...common} />
|
|
54
|
+
</>
|
|
55
|
+
) : null}
|
|
56
|
+
{name === "edit" ? (
|
|
57
|
+
<>
|
|
58
|
+
<Path d="M 12 20h9" {...common} />
|
|
59
|
+
<Path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" {...common} />
|
|
60
|
+
</>
|
|
61
|
+
) : null}
|
|
62
|
+
{name === "globe" ? (
|
|
63
|
+
<>
|
|
64
|
+
<Circle cx="12" cy="12" r="9" {...common} />
|
|
65
|
+
<Path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" {...common} />
|
|
66
|
+
</>
|
|
67
|
+
) : null}
|
|
68
|
+
{name === "check" ? <Polyline points="20 6 9 17 4 12" {...common} /> : null}
|
|
69
|
+
{name === "x" ? <Path d="M18 6 6 18M6 6l12 12" {...common} /> : null}
|
|
70
|
+
{name === "chevron-right" ? <Polyline points="9 18 15 12 9 6" {...common} /> : null}
|
|
71
|
+
{name === "chevron-down" ? <Polyline points="6 9 12 15 18 9" {...common} /> : null}
|
|
72
|
+
{name === "sparkle" ? (
|
|
73
|
+
<Path d="M 12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9Z" {...common} />
|
|
74
|
+
) : null}
|
|
75
|
+
{name === "mic" ? (
|
|
76
|
+
<>
|
|
77
|
+
<Rect x="9" y="2" width="6" height="12" rx="3" {...common} />
|
|
78
|
+
<Path d="M5 10a7 7 0 0 0 14 0M12 17v5" {...common} />
|
|
79
|
+
</>
|
|
80
|
+
) : null}
|
|
81
|
+
{name === "plus" ? <Path d="M 12 5v14M5 12h14" {...common} /> : null}
|
|
82
|
+
{name === "arrow-up" ? <Path d="M 12 19V5M5 12l7-7 7 7" {...common} /> : null}
|
|
83
|
+
{name === "stop" ? <Rect x="6" y="6" width="12" height="12" rx="2" fill={color} /> : null}
|
|
84
|
+
{name === "tool" ? (
|
|
85
|
+
<Path
|
|
86
|
+
d="M14.7 6.3a4 4 0 0 0-5.4 5.4L3 18l3 3 6.3-6.3a4 4 0 0 0 5.4-5.4l-2.6 2.6-2.4-.6-.6-2.4Z"
|
|
87
|
+
{...common}
|
|
88
|
+
/>
|
|
89
|
+
) : null}
|
|
90
|
+
{name === "send" ? <Path d="m22 2-7 20-4-9-9-4Z M22 2 11 13" {...common} /> : null}
|
|
91
|
+
{name === "alert" ? (
|
|
92
|
+
<>
|
|
93
|
+
<Path
|
|
94
|
+
d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"
|
|
95
|
+
{...common}
|
|
96
|
+
/>
|
|
97
|
+
<Path d="M 12 9v4M12 17h.01" {...common} />
|
|
98
|
+
</>
|
|
99
|
+
) : null}
|
|
100
|
+
{name === "question" ? (
|
|
101
|
+
<>
|
|
102
|
+
<Circle cx="12" cy="12" r="9" {...common} />
|
|
103
|
+
<Path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01" {...common} />
|
|
104
|
+
</>
|
|
105
|
+
) : null}
|
|
106
|
+
{name === "rocket" ? (
|
|
107
|
+
<>
|
|
108
|
+
<Path
|
|
109
|
+
d="M4.5 16.5c-1.5 1.3-2 5-2 5s3.7-.5 5-2c.7-.8.7-2.1-.1-2.9a2.2 2.2 0 0 0-2.9-.1Z"
|
|
110
|
+
{...common}
|
|
111
|
+
/>
|
|
112
|
+
<Path
|
|
113
|
+
d="m 12 15-3-3a22 22 0 0 1 2-4A13 13 0 0 1 22 2c0 2.7-.8 7.5-6 11a22 22 0 0 1-4 2Z"
|
|
114
|
+
{...common}
|
|
115
|
+
/>
|
|
116
|
+
</>
|
|
117
|
+
) : null}
|
|
118
|
+
</Svg>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Category icon for a tool name (best effort, purely presentational). */
|
|
123
|
+
export function toolIconName(name: string): AgentIconName {
|
|
124
|
+
const leaf = name.toLowerCase();
|
|
125
|
+
if (/exec|command|shell|bash|terminal|run/.test(leaf)) return "terminal";
|
|
126
|
+
if (/search|find|grep|list|query|read/.test(leaf)) return "search";
|
|
127
|
+
if (/patch|edit|write|create|update|save/.test(leaf)) return "edit";
|
|
128
|
+
if (/web|browse|fetch|http|url/.test(leaf)) return "globe";
|
|
129
|
+
if (/deploy|release|ship/.test(leaf)) return "rocket";
|
|
130
|
+
return "tool";
|
|
131
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { AgentStepList, AgentTimeline, UserMessage, WorkingLine } from "./timeline";
|
|
2
|
+
export { AgentStepsSheet } from "./steps-sheet";
|
|
3
|
+
export { AgentAttentionTray } from "./attention";
|
|
4
|
+
export type { AgentTimelineProps, MarkdownRenderer } from "./timeline";
|
|
5
|
+
export { AgentComposer, ComposerChip } from "./composer";
|
|
6
|
+
export type { AgentComposerProps } from "./composer";
|
|
7
|
+
export { ApprovalCard, QuestionCard, ActionButton } from "./cards";
|
|
8
|
+
export { AgentIcon, toolIconName } from "./icons";
|
|
9
|
+
export type { AgentIconName } from "./icons";
|
|
10
|
+
export { agentDirections, agentTheme } from "./theme";
|
|
11
|
+
export type {
|
|
12
|
+
AgentDirection,
|
|
13
|
+
AgentDirectionId,
|
|
14
|
+
AgentTheme,
|
|
15
|
+
ActivityStyle,
|
|
16
|
+
ComposerStyle,
|
|
17
|
+
} from "./theme";
|
|
18
|
+
export { formatDuration, toolDetail, toolOutputPreview, toolTitle } from "./format";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ActivityItem } from "@opengeni/react/session";
|
|
2
|
+
import { Modal, Platform, Pressable, ScrollView, Text, View } from "react-native";
|
|
3
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
|
+
import { AgentStepList } from "./timeline";
|
|
5
|
+
import type { AgentTheme } from "./theme";
|
|
6
|
+
|
|
7
|
+
/** Native sheet with the full step-by-step view of one piece of agent work. */
|
|
8
|
+
export function AgentStepsSheet(props: {
|
|
9
|
+
visible: boolean;
|
|
10
|
+
onClose(): void;
|
|
11
|
+
title: string;
|
|
12
|
+
steps: readonly ActivityItem[];
|
|
13
|
+
theme: AgentTheme;
|
|
14
|
+
}) {
|
|
15
|
+
const { theme } = props;
|
|
16
|
+
const insets = useSafeAreaInsets();
|
|
17
|
+
return (
|
|
18
|
+
<Modal
|
|
19
|
+
animationType="slide"
|
|
20
|
+
onRequestClose={props.onClose}
|
|
21
|
+
presentationStyle={Platform.OS === "ios" ? "pageSheet" : "fullScreen"}
|
|
22
|
+
visible={props.visible}
|
|
23
|
+
>
|
|
24
|
+
<View
|
|
25
|
+
style={{
|
|
26
|
+
flex: 1,
|
|
27
|
+
backgroundColor: theme.colors.background,
|
|
28
|
+
paddingTop: Platform.OS === "ios" ? 0 : insets.top,
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<View
|
|
32
|
+
style={{
|
|
33
|
+
height: 56,
|
|
34
|
+
flexDirection: "row",
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
paddingHorizontal: theme.space.gutter,
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<Text
|
|
40
|
+
numberOfLines={1}
|
|
41
|
+
style={{
|
|
42
|
+
flex: 1,
|
|
43
|
+
color: theme.colors.text,
|
|
44
|
+
fontSize: theme.type.body + 1,
|
|
45
|
+
fontWeight: theme.type.weightStrong,
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{props.title}
|
|
49
|
+
</Text>
|
|
50
|
+
<Pressable
|
|
51
|
+
accessibilityRole="button"
|
|
52
|
+
hitSlop={12}
|
|
53
|
+
onPress={props.onClose}
|
|
54
|
+
style={{ minHeight: 44, justifyContent: "center" }}
|
|
55
|
+
>
|
|
56
|
+
<Text
|
|
57
|
+
style={{ color: theme.colors.accent, fontSize: theme.type.body, fontWeight: "600" }}
|
|
58
|
+
>
|
|
59
|
+
Done
|
|
60
|
+
</Text>
|
|
61
|
+
</Pressable>
|
|
62
|
+
</View>
|
|
63
|
+
<ScrollView
|
|
64
|
+
contentContainerStyle={{ padding: theme.space.gutter, paddingBottom: insets.bottom + 24 }}
|
|
65
|
+
>
|
|
66
|
+
<AgentStepList items={props.steps} theme={theme} />
|
|
67
|
+
</ScrollView>
|
|
68
|
+
</View>
|
|
69
|
+
</Modal>
|
|
70
|
+
);
|
|
71
|
+
}
|