@ateam-ai-mobile/ui 0.1.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/index.d.mts +166 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +1870 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1853 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +34 -0
- package/src/AdasChatScreen.tsx +170 -0
- package/src/Lightbox.tsx +55 -0
- package/src/chat/ChatInput.tsx +134 -0
- package/src/chat/JobProgressBar.tsx +221 -0
- package/src/chat/MessageBubble.tsx +494 -0
- package/src/chat/TableCardBlock.tsx +96 -0
- package/src/chat/TypingIndicator.tsx +20 -0
- package/src/chat/UIActionsRow.tsx +128 -0
- package/src/index.ts +38 -0
- package/src/plugin/EmbeddedPluginWebView.tsx +170 -0
- package/src/plugin/Plugin.tsx +178 -0
- package/src/plugin/PluginSurface.tsx +77 -0
- package/src/plugin/usePluginBridge.ts +383 -0
- package/src/providers.ts +46 -0
- package/src/stores/lightboxStore.ts +18 -0
- package/src/widgets/WidgetWebView.tsx +173 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
TextInput,
|
|
5
|
+
Pressable,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Platform,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import Svg, { Path, Line, Polygon } from 'react-native-svg';
|
|
10
|
+
import { colors, borderRadius } from '@ateam-ai-mobile/core';
|
|
11
|
+
import { getHaptics } from '@ateam-ai-mobile/plugin-runtime';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
onSend: (text: string) => void;
|
|
15
|
+
onCallStart?: () => void;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
/** Pre-fill the composer (e.g. from bridge.prepareMessage). The text is
|
|
18
|
+
* placed in the input and focused — not sent. */
|
|
19
|
+
prefill?: string;
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function ChatInput({ onSend, onCallStart, loading, prefill, placeholder }: Props) {
|
|
24
|
+
const [text, setText] = useState('');
|
|
25
|
+
const [height, setHeight] = useState(40);
|
|
26
|
+
const inputRef = useRef<TextInput>(null);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (prefill) {
|
|
30
|
+
setText(prefill);
|
|
31
|
+
inputRef.current?.focus();
|
|
32
|
+
}
|
|
33
|
+
}, [prefill]);
|
|
34
|
+
|
|
35
|
+
// NOTE: no `loading` guard and no editable={!loading} — sending WHILE a
|
|
36
|
+
// chain runs is a feature (backend Chain Additions router decides
|
|
37
|
+
// attach/cancel/new), and the input must never be disabled mid-run.
|
|
38
|
+
const handleSend = useCallback(() => {
|
|
39
|
+
const trimmed = text.trim();
|
|
40
|
+
if (!trimmed) return;
|
|
41
|
+
getHaptics()?.impact('light');
|
|
42
|
+
onSend(trimmed);
|
|
43
|
+
setText('');
|
|
44
|
+
setHeight(40);
|
|
45
|
+
}, [text, loading, onSend]);
|
|
46
|
+
|
|
47
|
+
const canSend = text.trim().length > 0;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<View style={styles.dock}>
|
|
51
|
+
<View style={styles.inputRow}>
|
|
52
|
+
{onCallStart && (
|
|
53
|
+
<Pressable style={styles.callBtn} onPress={onCallStart}>
|
|
54
|
+
<Svg width={20} height={20} viewBox="0 0 24 24" fill="none" stroke={colors.light.accent} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
|
|
55
|
+
<Path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.63A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" />
|
|
56
|
+
</Svg>
|
|
57
|
+
</Pressable>
|
|
58
|
+
)}
|
|
59
|
+
<TextInput
|
|
60
|
+
ref={inputRef}
|
|
61
|
+
style={[styles.input, { height: Math.min(Math.max(40, height), 120) }]}
|
|
62
|
+
value={text}
|
|
63
|
+
onChangeText={setText}
|
|
64
|
+
onContentSizeChange={e => setHeight(e.nativeEvent.contentSize.height)}
|
|
65
|
+
placeholder={placeholder || 'Message A-Team...'}
|
|
66
|
+
placeholderTextColor={colors.light.textMuted}
|
|
67
|
+
multiline
|
|
68
|
+
returnKeyType="default"
|
|
69
|
+
blurOnSubmit={false}
|
|
70
|
+
onSubmitEditing={() => {
|
|
71
|
+
if (Platform.OS === 'web') handleSend();
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
<Pressable
|
|
75
|
+
style={[styles.sendBtn, canSend && styles.sendBtnActive]}
|
|
76
|
+
onPress={handleSend}
|
|
77
|
+
disabled={!canSend}
|
|
78
|
+
>
|
|
79
|
+
<Svg width={18} height={18} viewBox="0 0 24 24" fill="none" stroke={canSend ? '#fff' : colors.light.textMuted} strokeWidth={2}>
|
|
80
|
+
<Line x1="22" y1="2" x2="11" y2="13" />
|
|
81
|
+
<Polygon points="22 2 15 22 11 13 2 9 22 2" />
|
|
82
|
+
</Svg>
|
|
83
|
+
</Pressable>
|
|
84
|
+
</View>
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const styles = StyleSheet.create({
|
|
90
|
+
dock: {
|
|
91
|
+
paddingHorizontal: 12,
|
|
92
|
+
paddingVertical: 8,
|
|
93
|
+
backgroundColor: colors.light.bgPrimary,
|
|
94
|
+
borderTopWidth: 0.5,
|
|
95
|
+
borderTopColor: colors.light.border,
|
|
96
|
+
},
|
|
97
|
+
inputRow: {
|
|
98
|
+
flexDirection: 'row',
|
|
99
|
+
alignItems: 'flex-end',
|
|
100
|
+
backgroundColor: colors.light.bgInput,
|
|
101
|
+
borderRadius: borderRadius.xl,
|
|
102
|
+
borderWidth: 1,
|
|
103
|
+
borderColor: colors.light.border,
|
|
104
|
+
paddingHorizontal: 4,
|
|
105
|
+
paddingVertical: 4,
|
|
106
|
+
gap: 4,
|
|
107
|
+
},
|
|
108
|
+
callBtn: {
|
|
109
|
+
width: 36,
|
|
110
|
+
height: 36,
|
|
111
|
+
borderRadius: 18,
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
},
|
|
115
|
+
input: {
|
|
116
|
+
flex: 1,
|
|
117
|
+
fontSize: 16,
|
|
118
|
+
color: colors.light.textPrimary,
|
|
119
|
+
paddingHorizontal: 8,
|
|
120
|
+
paddingVertical: 8,
|
|
121
|
+
maxHeight: 120,
|
|
122
|
+
},
|
|
123
|
+
sendBtn: {
|
|
124
|
+
width: 36,
|
|
125
|
+
height: 36,
|
|
126
|
+
borderRadius: 18,
|
|
127
|
+
justifyContent: 'center',
|
|
128
|
+
alignItems: 'center',
|
|
129
|
+
backgroundColor: colors.light.bgTertiary,
|
|
130
|
+
},
|
|
131
|
+
sendBtnActive: {
|
|
132
|
+
backgroundColor: colors.light.accent,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { View, Text, Animated, StyleSheet } from 'react-native';
|
|
3
|
+
import type { JobProgress } from '@ateam-ai-mobile/core';
|
|
4
|
+
import { ada, fontSize, borderRadius } from '@ateam-ai-mobile/core';
|
|
5
|
+
|
|
6
|
+
// Semantic warm-palette colors for terminal states (kept local — Ada palette
|
|
7
|
+
// is mostly orange/ink, but progress states need a small warm green/red/amber
|
|
8
|
+
// vocabulary to stay readable).
|
|
9
|
+
const WARM_GREEN = '#5A8A5C';
|
|
10
|
+
const WARM_GREEN_SOFT = 'rgba(90, 138, 92, 0.14)';
|
|
11
|
+
const WARM_RED = '#C15545';
|
|
12
|
+
const WARM_RED_SOFT = 'rgba(193, 85, 69, 0.14)';
|
|
13
|
+
const WARM_AMBER = '#D49A4E';
|
|
14
|
+
const WARM_AMBER_SOFT = 'rgba(212, 154, 78, 0.16)';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
progress: JobProgress;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function JobProgressBar({ progress }: Props) {
|
|
21
|
+
const pulseAnim = useRef(new Animated.Value(0.5)).current;
|
|
22
|
+
const [elapsed, setElapsed] = useState(0);
|
|
23
|
+
|
|
24
|
+
const isRunning = progress.status === 'running';
|
|
25
|
+
const isDone = progress.status === 'done';
|
|
26
|
+
const isError = progress.status === 'error';
|
|
27
|
+
const isPaused = progress.status === 'paused';
|
|
28
|
+
|
|
29
|
+
// Pulse animation while running
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!isRunning) {
|
|
32
|
+
pulseAnim.setValue(1);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const anim = Animated.loop(
|
|
36
|
+
Animated.sequence([
|
|
37
|
+
Animated.timing(pulseAnim, { toValue: 1, duration: 800, useNativeDriver: true }),
|
|
38
|
+
Animated.timing(pulseAnim, { toValue: 0.5, duration: 800, useNativeDriver: true }),
|
|
39
|
+
]),
|
|
40
|
+
);
|
|
41
|
+
anim.start();
|
|
42
|
+
return () => anim.stop();
|
|
43
|
+
}, [isRunning, pulseAnim]);
|
|
44
|
+
|
|
45
|
+
// Elapsed timer — ticks every second while running, clears on terminal state
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!isRunning || !progress.chainStartedAt) {
|
|
48
|
+
setElapsed(0);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const start = progress.chainStartedAt;
|
|
52
|
+
setElapsed(Math.floor((Date.now() - start) / 1000));
|
|
53
|
+
const id = setInterval(() => {
|
|
54
|
+
setElapsed(Math.floor((Date.now() - start) / 1000));
|
|
55
|
+
}, 1000);
|
|
56
|
+
return () => clearInterval(id);
|
|
57
|
+
}, [isRunning, progress.chainStartedAt]);
|
|
58
|
+
|
|
59
|
+
const statusColor = isDone
|
|
60
|
+
? WARM_GREEN
|
|
61
|
+
: isError
|
|
62
|
+
? WARM_RED
|
|
63
|
+
: isPaused
|
|
64
|
+
? WARM_AMBER
|
|
65
|
+
: ada.orange;
|
|
66
|
+
|
|
67
|
+
const statusBg = isDone
|
|
68
|
+
? WARM_GREEN_SOFT
|
|
69
|
+
: isError
|
|
70
|
+
? WARM_RED_SOFT
|
|
71
|
+
: isPaused
|
|
72
|
+
? WARM_AMBER_SOFT
|
|
73
|
+
: ada.orangeSoft;
|
|
74
|
+
|
|
75
|
+
// Terminal states — simple one-liner
|
|
76
|
+
if (isDone || isError) {
|
|
77
|
+
return (
|
|
78
|
+
<View style={styles.container}>
|
|
79
|
+
<View style={styles.row}>
|
|
80
|
+
<View style={[styles.dot, { backgroundColor: statusColor }]} />
|
|
81
|
+
<Text style={[styles.label, { color: statusColor }]}>
|
|
82
|
+
{isDone ? 'Completed' : 'Failed'}
|
|
83
|
+
</Text>
|
|
84
|
+
</View>
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (isPaused) {
|
|
90
|
+
return (
|
|
91
|
+
<View style={styles.container}>
|
|
92
|
+
<View style={styles.row}>
|
|
93
|
+
<View style={[styles.dot, { backgroundColor: statusColor }]} />
|
|
94
|
+
<Text style={[styles.label, { color: statusColor }]}>
|
|
95
|
+
Waiting for your reply
|
|
96
|
+
</Text>
|
|
97
|
+
</View>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Running — voice-agent-style compact line:
|
|
103
|
+
// ● messaging-agent → whatsapp.send · iter 3 · 0:42
|
|
104
|
+
const elapsedStr = progress.chainStartedAt
|
|
105
|
+
? `${Math.floor(elapsed / 60)}:${String(elapsed % 60).padStart(2, '0')}`
|
|
106
|
+
: null;
|
|
107
|
+
|
|
108
|
+
const metaParts = [
|
|
109
|
+
progress.iteration != null && progress.iteration > 0 ? `iter ${progress.iteration}` : null,
|
|
110
|
+
elapsedStr,
|
|
111
|
+
].filter(Boolean) as string[];
|
|
112
|
+
|
|
113
|
+
const lp = progress.liveProgress;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<View style={styles.container}>
|
|
117
|
+
<View style={styles.row}>
|
|
118
|
+
<Animated.View
|
|
119
|
+
style={[styles.dot, { backgroundColor: ada.orange, opacity: pulseAnim }]}
|
|
120
|
+
/>
|
|
121
|
+
<Text style={styles.skill} numberOfLines={1}>
|
|
122
|
+
{progress.skillSlug || 'Processing...'}
|
|
123
|
+
</Text>
|
|
124
|
+
{!!progress.currentTool && (
|
|
125
|
+
<Text style={styles.tool} numberOfLines={1}>
|
|
126
|
+
{` → ${progress.currentTool}`}
|
|
127
|
+
</Text>
|
|
128
|
+
)}
|
|
129
|
+
{metaParts.length > 0 && (
|
|
130
|
+
<Text style={styles.meta} numberOfLines={1}>
|
|
131
|
+
{` · ${metaParts.join(' · ')}`}
|
|
132
|
+
</Text>
|
|
133
|
+
)}
|
|
134
|
+
</View>
|
|
135
|
+
{/* Live progress from adas_emit_progress() — shown during fat tool execution */}
|
|
136
|
+
{!!lp?.message && (
|
|
137
|
+
<View style={styles.liveProgressRow}>
|
|
138
|
+
{lp.step != null && lp.total != null && (
|
|
139
|
+
<Text style={styles.liveProgressStep}>[{lp.step}/{lp.total}]</Text>
|
|
140
|
+
)}
|
|
141
|
+
<Text style={styles.liveProgressMsg} numberOfLines={1}>{lp.message}</Text>
|
|
142
|
+
</View>
|
|
143
|
+
)}
|
|
144
|
+
<View style={[styles.progressTrack, { backgroundColor: statusBg }]}>
|
|
145
|
+
<Animated.View
|
|
146
|
+
style={[styles.progressFill, { backgroundColor: ada.orange, opacity: pulseAnim }]}
|
|
147
|
+
/>
|
|
148
|
+
</View>
|
|
149
|
+
</View>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const styles = StyleSheet.create({
|
|
154
|
+
container: {
|
|
155
|
+
marginTop: 8,
|
|
156
|
+
paddingTop: 8,
|
|
157
|
+
borderTopWidth: 1,
|
|
158
|
+
borderTopColor: ada.border,
|
|
159
|
+
},
|
|
160
|
+
row: {
|
|
161
|
+
flexDirection: 'row',
|
|
162
|
+
alignItems: 'center',
|
|
163
|
+
flexWrap: 'nowrap',
|
|
164
|
+
overflow: 'hidden',
|
|
165
|
+
},
|
|
166
|
+
dot: {
|
|
167
|
+
width: 7,
|
|
168
|
+
height: 7,
|
|
169
|
+
borderRadius: 4,
|
|
170
|
+
marginRight: 6,
|
|
171
|
+
flexShrink: 0,
|
|
172
|
+
},
|
|
173
|
+
label: {
|
|
174
|
+
fontSize: fontSize.xs,
|
|
175
|
+
fontWeight: '600',
|
|
176
|
+
},
|
|
177
|
+
// Running state text segments
|
|
178
|
+
skill: {
|
|
179
|
+
fontSize: fontSize.xs,
|
|
180
|
+
fontWeight: '600',
|
|
181
|
+
color: ada.orange,
|
|
182
|
+
flexShrink: 1,
|
|
183
|
+
},
|
|
184
|
+
tool: {
|
|
185
|
+
fontSize: fontSize.xs,
|
|
186
|
+
color: ada.inkMuted,
|
|
187
|
+
flexShrink: 2,
|
|
188
|
+
},
|
|
189
|
+
meta: {
|
|
190
|
+
fontSize: fontSize.xs,
|
|
191
|
+
color: ada.inkMuted,
|
|
192
|
+
flexShrink: 0,
|
|
193
|
+
},
|
|
194
|
+
liveProgressRow: {
|
|
195
|
+
flexDirection: 'row',
|
|
196
|
+
alignItems: 'center',
|
|
197
|
+
marginTop: 4,
|
|
198
|
+
gap: 4,
|
|
199
|
+
},
|
|
200
|
+
liveProgressStep: {
|
|
201
|
+
fontSize: fontSize.xs,
|
|
202
|
+
fontWeight: '600',
|
|
203
|
+
color: ada.inkMuted,
|
|
204
|
+
},
|
|
205
|
+
liveProgressMsg: {
|
|
206
|
+
fontSize: fontSize.xs,
|
|
207
|
+
color: ada.orange,
|
|
208
|
+
flex: 1,
|
|
209
|
+
},
|
|
210
|
+
progressTrack: {
|
|
211
|
+
height: 3,
|
|
212
|
+
borderRadius: 2,
|
|
213
|
+
marginTop: 6,
|
|
214
|
+
overflow: 'hidden',
|
|
215
|
+
},
|
|
216
|
+
progressFill: {
|
|
217
|
+
height: '100%',
|
|
218
|
+
width: '60%',
|
|
219
|
+
borderRadius: 2,
|
|
220
|
+
},
|
|
221
|
+
});
|