react-native-srschat 0.1.13 → 0.1.15
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/lib/commonjs/components/email.js +243 -0
- package/lib/commonjs/components/email.js.map +1 -0
- package/lib/commonjs/components/feedback.js +115 -0
- package/lib/commonjs/components/feedback.js.map +1 -0
- package/lib/commonjs/components/header.js +35 -17
- package/lib/commonjs/components/header.js.map +1 -1
- package/lib/commonjs/components/input.js +106 -0
- package/lib/commonjs/components/input.js.map +1 -0
- package/lib/commonjs/components/productCard.js +234 -0
- package/lib/commonjs/components/productCard.js.map +1 -0
- package/lib/commonjs/components/testing.js +19 -6
- package/lib/commonjs/components/testing.js.map +1 -1
- package/lib/commonjs/{layout/chatIcon.js → components/welcomeButton.js} +40 -37
- package/lib/commonjs/components/welcomeButton.js.map +1 -0
- package/lib/commonjs/components/welcomeInput.js +87 -0
- package/lib/commonjs/components/welcomeInput.js.map +1 -0
- package/lib/commonjs/contexts/AppContext.js +272 -56
- package/lib/commonjs/contexts/AppContext.js.map +1 -1
- package/lib/commonjs/hooks/{Stream.js → stream.js} +107 -39
- package/lib/commonjs/hooks/stream.js.map +1 -0
- package/lib/commonjs/hooks/useAsyncStorage.js +36 -0
- package/lib/commonjs/hooks/useAsyncStorage.js.map +1 -0
- package/lib/commonjs/index.js +13 -6
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/layout/disclaimer.js +200 -0
- package/lib/commonjs/layout/disclaimer.js.map +1 -0
- package/lib/commonjs/layout/ex.js +272 -0
- package/lib/commonjs/layout/ex.js.map +1 -0
- package/lib/commonjs/layout/icon.js +114 -0
- package/lib/commonjs/layout/icon.js.map +1 -0
- package/lib/commonjs/layout/layout.js +148 -14
- package/lib/commonjs/layout/layout.js.map +1 -1
- package/lib/commonjs/layout/welcome.js +135 -0
- package/lib/commonjs/layout/welcome.js.map +1 -0
- package/lib/commonjs/layout/window.js +205 -0
- package/lib/commonjs/layout/window.js.map +1 -0
- package/lib/module/components/email.js +233 -0
- package/lib/module/components/email.js.map +1 -0
- package/lib/module/components/feedback.js +105 -0
- package/lib/module/components/feedback.js.map +1 -0
- package/lib/module/components/header.js +35 -17
- package/lib/module/components/header.js.map +1 -1
- package/lib/module/components/input.js +96 -0
- package/lib/module/components/input.js.map +1 -0
- package/lib/module/components/productCard.js +225 -0
- package/lib/module/components/productCard.js.map +1 -0
- package/lib/module/components/testing.js +20 -7
- package/lib/module/components/testing.js.map +1 -1
- package/lib/module/components/welcomeButton.js +48 -0
- package/lib/module/components/welcomeButton.js.map +1 -0
- package/lib/module/components/welcomeInput.js +77 -0
- package/lib/module/components/welcomeInput.js.map +1 -0
- package/lib/module/contexts/AppContext.js +272 -57
- package/lib/module/contexts/AppContext.js.map +1 -1
- package/lib/module/hooks/{Stream.js → stream.js} +107 -39
- package/lib/module/hooks/stream.js.map +1 -0
- package/lib/module/hooks/useAsyncStorage.js +29 -0
- package/lib/module/hooks/useAsyncStorage.js.map +1 -0
- package/lib/module/index.js +13 -6
- package/lib/module/index.js.map +1 -1
- package/lib/module/layout/disclaimer.js +190 -0
- package/lib/module/layout/disclaimer.js.map +1 -0
- package/lib/module/layout/ex.js +262 -0
- package/lib/module/layout/ex.js.map +1 -0
- package/lib/module/layout/icon.js +104 -0
- package/lib/module/layout/icon.js.map +1 -0
- package/lib/module/layout/layout.js +150 -16
- package/lib/module/layout/layout.js.map +1 -1
- package/lib/module/layout/welcome.js +126 -0
- package/lib/module/layout/welcome.js.map +1 -0
- package/lib/module/layout/window.js +195 -0
- package/lib/module/layout/window.js.map +1 -0
- package/lib/typescript/components/email.d.ts +5 -0
- package/lib/typescript/components/email.d.ts.map +1 -0
- package/lib/typescript/components/feedback.d.ts +6 -0
- package/lib/typescript/components/feedback.d.ts.map +1 -0
- package/lib/typescript/components/header.d.ts.map +1 -1
- package/lib/typescript/components/input.d.ts +6 -0
- package/lib/typescript/components/input.d.ts.map +1 -0
- package/lib/typescript/components/productCard.d.ts +5 -0
- package/lib/typescript/components/productCard.d.ts.map +1 -0
- package/lib/typescript/components/testing.d.ts.map +1 -1
- package/lib/typescript/components/welcomeButton.d.ts +4 -0
- package/lib/typescript/components/welcomeButton.d.ts.map +1 -0
- package/lib/typescript/components/welcomeInput.d.ts +6 -0
- package/lib/typescript/components/welcomeInput.d.ts.map +1 -0
- package/lib/typescript/contexts/AppContext.d.ts +5 -1
- package/lib/typescript/contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/hooks/{Stream.d.ts → stream.d.ts} +1 -1
- package/lib/typescript/hooks/stream.d.ts.map +1 -0
- package/lib/typescript/hooks/useAsyncStorage.d.ts +2 -0
- package/lib/typescript/hooks/useAsyncStorage.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/layout/disclaimer.d.ts +5 -0
- package/lib/typescript/layout/disclaimer.d.ts.map +1 -0
- package/lib/typescript/layout/ex.d.ts +3 -0
- package/lib/typescript/layout/ex.d.ts.map +1 -0
- package/lib/typescript/layout/{chatIcon.d.ts → icon.d.ts} +1 -1
- package/lib/typescript/layout/icon.d.ts.map +1 -0
- package/lib/typescript/layout/layout.d.ts +1 -4
- package/lib/typescript/layout/layout.d.ts.map +1 -1
- package/lib/typescript/layout/welcome.d.ts +5 -0
- package/lib/typescript/layout/welcome.d.ts.map +1 -0
- package/lib/typescript/layout/window.d.ts +5 -0
- package/lib/typescript/layout/window.d.ts.map +1 -0
- package/package.json +7 -1
- package/src/components/email.js +210 -0
- package/src/components/feedback.js +114 -0
- package/src/components/header.js +32 -17
- package/src/components/input.js +95 -0
- package/src/components/productCard.js +240 -0
- package/src/components/testing.js +17 -4
- package/src/components/welcomeButton.js +51 -0
- package/src/components/welcomeInput.js +81 -0
- package/src/contexts/AppContext.js +237 -52
- package/src/hooks/{Stream.js → stream.js} +123 -41
- package/src/hooks/useAsyncStorage.js +33 -0
- package/src/index.js +7 -3
- package/src/layout/disclaimer.js +187 -0
- package/src/layout/ex.js +251 -0
- package/src/layout/icon.js +96 -0
- package/src/layout/layout.js +137 -10
- package/src/layout/welcome.js +124 -0
- package/src/layout/window.js +194 -0
- package/lib/commonjs/hooks/Stream.js.map +0 -1
- package/lib/commonjs/layout/chatIcon.js.map +0 -1
- package/lib/commonjs/layout/chatWindow.js +0 -214
- package/lib/commonjs/layout/chatWindow.js.map +0 -1
- package/lib/module/hooks/Stream.js.map +0 -1
- package/lib/module/layout/chatIcon.js +0 -44
- package/lib/module/layout/chatIcon.js.map +0 -1
- package/lib/module/layout/chatWindow.js +0 -204
- package/lib/module/layout/chatWindow.js.map +0 -1
- package/lib/typescript/hooks/Stream.d.ts.map +0 -1
- package/lib/typescript/layout/chatIcon.d.ts.map +0 -1
- package/lib/typescript/layout/chatWindow.d.ts +0 -6
- package/lib/typescript/layout/chatWindow.d.ts.map +0 -1
- package/src/layout/chatIcon.js +0 -38
- package/src/layout/chatWindow.js +0 -207
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import React, { useContext } from "react";
|
|
2
|
+
import { View, Text, ScrollView, StyleSheet, TouchableOpacity } from "react-native";
|
|
3
|
+
import { AppContext } from "../contexts/AppContext";
|
|
4
|
+
import Feather from "react-native-vector-icons/Feather";
|
|
5
|
+
import { Header } from '../components/header';
|
|
6
|
+
import { Testing } from "../components/testing";
|
|
7
|
+
|
|
8
|
+
export const Disclaimer = ({ panHandlers }) => {
|
|
9
|
+
const { setShowModal, confirmDisclaimer, uiConfig, onAddToCartClick, onProductCardClick } = useContext(AppContext);
|
|
10
|
+
|
|
11
|
+
const sections = [
|
|
12
|
+
{
|
|
13
|
+
title: "General Information",
|
|
14
|
+
details: "The AI agent is an automated system powered by Instalily. It is designed to provide information and assistance based on HeritagePool+ Suppliers and documentation in product brand manuals. While we strive to provide accurate information through the AI agent, we cannot guarantee the accuracy, completeness, or up-to-date nature of the information provided. We recommend that you use responses as a starting point and verify the information in the linked public documentation sources.",
|
|
15
|
+
icon: "file-text"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: "User Responsibility",
|
|
19
|
+
details: "Users of the AI agent bear sole responsibility for their interactions and reliance on the information provided. It is important to exercise caution and use your discretion while interpreting and acting upon the agent's responses. We cannot be held liable for any actions, losses, or damages resulting from the use of the agent.",
|
|
20
|
+
icon: "thumbs-up"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: "Data Privacy and Security",
|
|
24
|
+
details: "We prioritize the privacy and security of our users' information. Be aware that agent conversations are recorded and stored for later review and quality monitoring by SRS Distribution and its contractors, including by SRS’ content team to improve results. While SRS and the vendor retain logs of these conversations, this data is not used to train their AI models.",
|
|
25
|
+
icon: "lock"
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<View {...panHandlers}>
|
|
32
|
+
<Header />
|
|
33
|
+
</View>
|
|
34
|
+
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
|
35
|
+
<View style={styles.header}>
|
|
36
|
+
<Text style={styles.welcomeTitle}>Welcome to your AI Agent</Text>
|
|
37
|
+
<View style={styles.betaTag}>
|
|
38
|
+
<Text style={styles.betaText}>BETA</Text>
|
|
39
|
+
</View>
|
|
40
|
+
</View>
|
|
41
|
+
|
|
42
|
+
<Text style={styles.disclaimerText}>
|
|
43
|
+
The following disclaimer is intended to inform users of the limitations and usage guidelines of the AI agent.
|
|
44
|
+
</Text>
|
|
45
|
+
<Text style={styles.disclaimerText}>
|
|
46
|
+
Please read this disclaimer carefully before engaging with the agent.
|
|
47
|
+
</Text>
|
|
48
|
+
|
|
49
|
+
{sections.map((section, index) => (
|
|
50
|
+
<Paragraph key={index} title={section.title} details={section.details} icon={section.icon} />
|
|
51
|
+
))}
|
|
52
|
+
|
|
53
|
+
<Text style={styles.disclaimerText}>
|
|
54
|
+
Do not share any sensitive or personal information in your conversations.
|
|
55
|
+
</Text>
|
|
56
|
+
<Text style={styles.disclaimerText}>
|
|
57
|
+
By using our AI agent, you indicate your acceptance and understanding of the above disclaimer. If you do not agree with any part of this disclaimer, we recommend refraining from using the agent. For further assistance or inquiries, please contact <Text style={styles.boldText}>srs@instalily.ai</Text>.
|
|
58
|
+
</Text>
|
|
59
|
+
</ScrollView>
|
|
60
|
+
{uiConfig.testButtons &&
|
|
61
|
+
<Testing onProductCardClick={onProductCardClick} onAddToCartClick={onAddToCartClick} />
|
|
62
|
+
}
|
|
63
|
+
<View style={styles.confirmArea}>
|
|
64
|
+
<TouchableOpacity style={styles.button} onPress={confirmDisclaimer}>
|
|
65
|
+
<Text style={styles.buttonText}>Confirm</Text>
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
<Text style={styles.confirmText}>
|
|
68
|
+
By clicking the "Confirm" button above, you understand and confirm that your use of Heritage Pool+ AI Agent is subject to our current Terms of Service and Privacy Policy, and you confirm your understanding of the above.
|
|
69
|
+
</Text>
|
|
70
|
+
</View>
|
|
71
|
+
</View>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const Paragraph = ({ title, details, icon }) => {
|
|
76
|
+
return (
|
|
77
|
+
<View style={styles.sectionContainer}>
|
|
78
|
+
<View style={styles.iconContainer}>
|
|
79
|
+
<Feather name={icon} size={18} color="#1976d2" />
|
|
80
|
+
</View>
|
|
81
|
+
<View style={styles.textContainer}>
|
|
82
|
+
<Text style={styles.sectionTitle}>{title}</Text>
|
|
83
|
+
<Text style={styles.sectionDetails}>{details}</Text>
|
|
84
|
+
</View>
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const styles = StyleSheet.create({
|
|
90
|
+
container: {
|
|
91
|
+
flex: 1,
|
|
92
|
+
},
|
|
93
|
+
scrollContainer: {
|
|
94
|
+
paddingBottom: 20,
|
|
95
|
+
padding: 20,
|
|
96
|
+
},
|
|
97
|
+
header: {
|
|
98
|
+
flexDirection: "row",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
marginBottom: 10,
|
|
101
|
+
},
|
|
102
|
+
welcomeTitle: {
|
|
103
|
+
fontSize: 18,
|
|
104
|
+
fontWeight: "500",
|
|
105
|
+
color: "#161616",
|
|
106
|
+
},
|
|
107
|
+
betaTag: {
|
|
108
|
+
backgroundColor: "#f6921e",
|
|
109
|
+
borderRadius: 8,
|
|
110
|
+
paddingHorizontal: 10,
|
|
111
|
+
paddingVertical: 4,
|
|
112
|
+
marginLeft: 10,
|
|
113
|
+
},
|
|
114
|
+
betaText: {
|
|
115
|
+
color: "#ffffff",
|
|
116
|
+
fontSize: 11,
|
|
117
|
+
fontWeight: "500",
|
|
118
|
+
},
|
|
119
|
+
disclaimerText: {
|
|
120
|
+
fontSize: 13,
|
|
121
|
+
color: "#505050",
|
|
122
|
+
lineHeight: 18,
|
|
123
|
+
marginBottom: 8,
|
|
124
|
+
},
|
|
125
|
+
boldText: {
|
|
126
|
+
fontWeight: "bold",
|
|
127
|
+
},
|
|
128
|
+
sectionContainer: {
|
|
129
|
+
flexDirection: "row",
|
|
130
|
+
alignItems: "flex-start",
|
|
131
|
+
backgroundColor: "#fff",
|
|
132
|
+
padding: 12,
|
|
133
|
+
marginBottom: 10,
|
|
134
|
+
borderRadius: 10,
|
|
135
|
+
elevation: 1,
|
|
136
|
+
},
|
|
137
|
+
iconContainer: {
|
|
138
|
+
backgroundColor: "#bbdefb",
|
|
139
|
+
borderRadius: 10,
|
|
140
|
+
padding: 10,
|
|
141
|
+
alignItems: "center",
|
|
142
|
+
justifyContent: "center",
|
|
143
|
+
marginRight: 10,
|
|
144
|
+
},
|
|
145
|
+
textContainer: {
|
|
146
|
+
flex: 1,
|
|
147
|
+
},
|
|
148
|
+
sectionTitle: {
|
|
149
|
+
fontSize: 14,
|
|
150
|
+
fontWeight: "500",
|
|
151
|
+
color: "#161616",
|
|
152
|
+
marginBottom: 3,
|
|
153
|
+
},
|
|
154
|
+
sectionDetails: {
|
|
155
|
+
fontSize: 12,
|
|
156
|
+
color: "#505050",
|
|
157
|
+
lineHeight: 18,
|
|
158
|
+
},
|
|
159
|
+
confirmArea: {
|
|
160
|
+
alignItems: "center",
|
|
161
|
+
padding: 20,
|
|
162
|
+
backgroundColor: '#f6f6f6',
|
|
163
|
+
borderTopWidth: 1,
|
|
164
|
+
borderTopColor: 'rgba(0, 0, 0, 0.1)',
|
|
165
|
+
paddingBottom: 25,
|
|
166
|
+
},
|
|
167
|
+
button: {
|
|
168
|
+
backgroundColor: "#367CB6",
|
|
169
|
+
width: "100%",
|
|
170
|
+
paddingVertical: 12,
|
|
171
|
+
borderRadius: 5,
|
|
172
|
+
alignItems: "center",
|
|
173
|
+
marginBottom: 10,
|
|
174
|
+
},
|
|
175
|
+
buttonText: {
|
|
176
|
+
color: "#FFFFFF",
|
|
177
|
+
fontSize: 16,
|
|
178
|
+
fontWeight: "500",
|
|
179
|
+
},
|
|
180
|
+
confirmText: {
|
|
181
|
+
fontSize: 11,
|
|
182
|
+
color: "gray",
|
|
183
|
+
lineHeight: 16,
|
|
184
|
+
paddingHorizontal: 10,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
package/src/layout/ex.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import React, { useState, useEffect, useContext, useRef } from 'react';
|
|
2
|
+
import { Text, StyleSheet, View, TextInput, TouchableOpacity, Platform, KeyboardAvoidingView,
|
|
3
|
+
Keyboard, Animated, PanResponder, Pressable } from 'react-native';
|
|
4
|
+
import { Header } from '../components/header';
|
|
5
|
+
import { AppContext } from '../contexts/AppContext';
|
|
6
|
+
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
7
|
+
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
|
8
|
+
import { Testing } from '../components/testing';
|
|
9
|
+
import { ChatInput } from '../components/input';
|
|
10
|
+
import { useWebSocketMessage } from '../hooks/stream';
|
|
11
|
+
import { ProductCard } from '../components/productCard'
|
|
12
|
+
import Markdown from 'react-native-markdown-display';
|
|
13
|
+
|
|
14
|
+
export const ChatWindow = () => {
|
|
15
|
+
const { handleSend, messages, input, setInput, ghostMessage, handleButtonClick, setShowModal } = useContext(AppContext);
|
|
16
|
+
|
|
17
|
+
const scrollViewRef = useRef(null);
|
|
18
|
+
const fadeAnim = useRef(new Animated.Value(0.6)).current;
|
|
19
|
+
const panY = useRef(new Animated.Value(0)).current;
|
|
20
|
+
const isDragging = useRef(false);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (scrollViewRef.current) {
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
scrollViewRef.current.scrollToEnd({ animated: false });
|
|
26
|
+
}, 100);
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (ghostMessage) {
|
|
32
|
+
Animated.loop(
|
|
33
|
+
Animated.sequence([
|
|
34
|
+
Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: false }),
|
|
35
|
+
Animated.timing(fadeAnim, { toValue: 0.6, duration: 500, useNativeDriver: false }),
|
|
36
|
+
])
|
|
37
|
+
).start();
|
|
38
|
+
}
|
|
39
|
+
}, [ghostMessage]);
|
|
40
|
+
|
|
41
|
+
useWebSocketMessage()
|
|
42
|
+
|
|
43
|
+
const headerPanResponder = PanResponder.create({
|
|
44
|
+
onStartShouldSetPanResponder: () => true,
|
|
45
|
+
onMoveShouldSetPanResponder: () => true,
|
|
46
|
+
onPanResponderGrant: () => {
|
|
47
|
+
isDragging.current = true;
|
|
48
|
+
},
|
|
49
|
+
onPanResponderMove: (evt, gestureState) => {
|
|
50
|
+
if (isDragging.current && gestureState.dy > 0) { // Downward swipe
|
|
51
|
+
panY.setValue(gestureState.dy);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
onPanResponderRelease: (evt, gestureState) => {
|
|
55
|
+
if (gestureState.dy > 100) { // Swiped down enough
|
|
56
|
+
handleClick()
|
|
57
|
+
} else {
|
|
58
|
+
Animated.spring(panY, { toValue: 0, useNativeDriver: false }).start();
|
|
59
|
+
}
|
|
60
|
+
isDragging.current = false;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const handleClick = () => {
|
|
65
|
+
if ((uiConfig.showIcon ?? true) !== true) {
|
|
66
|
+
setShowModal("Off");
|
|
67
|
+
} else {
|
|
68
|
+
setShowModal("Icon");
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<View style={styles.overlay}>
|
|
74
|
+
{/* Click outside chat to close */}
|
|
75
|
+
<Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
|
|
76
|
+
|
|
77
|
+
<Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
|
|
78
|
+
{/* Header - Only this section triggers drag-to-close */}
|
|
79
|
+
<View style={styles.headerContainer} {...headerPanResponder.panHandlers}>
|
|
80
|
+
<Header />
|
|
81
|
+
</View>
|
|
82
|
+
|
|
83
|
+
{/* Messages area */}
|
|
84
|
+
<KeyboardAwareScrollView
|
|
85
|
+
ref={scrollViewRef}
|
|
86
|
+
contentContainerStyle={styles.messagesContent}
|
|
87
|
+
enableOnAndroid
|
|
88
|
+
contentInsetAdjustmentBehavior="never"
|
|
89
|
+
automaticallyAdjustContentInsets={false}
|
|
90
|
+
contentInset={{ bottom: 0 }}
|
|
91
|
+
keyboardShouldPersistTaps="always"
|
|
92
|
+
showsVerticalScrollIndicator={false}
|
|
93
|
+
extraScrollHeight={0}
|
|
94
|
+
onContentSizeChange={() => {
|
|
95
|
+
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
{messages.map((msg, i) => (
|
|
99
|
+
|
|
100
|
+
<View key={i} style={styles.messageWrapper}>
|
|
101
|
+
{msg.type !== "middle" && (
|
|
102
|
+
<View style={[ styles.messageBubble, msg.type === "user" ? styles.userMessage : styles.aiMessage,]}>
|
|
103
|
+
<Markdown style={{ body: { color: msg.type === "user" ? "#ffffff" : "#161616",fontSize: 16, lineHeight: 22 }}}>
|
|
104
|
+
{msg.text}
|
|
105
|
+
</Markdown>
|
|
106
|
+
</View>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{msg.products && msg.products.length > 0 &&
|
|
110
|
+
msg.products.map((prod, index) => (
|
|
111
|
+
<View key={index} style={styles.productCardWrapper}>
|
|
112
|
+
<ProductCard prod={prod} />
|
|
113
|
+
</View>
|
|
114
|
+
))}
|
|
115
|
+
|
|
116
|
+
{msg.suggested_questions && Array.isArray(msg.questions) && msg.questions.map((question, index) => (
|
|
117
|
+
<TouchableOpacity key={index} style={styles.suggestedQuestionButton}
|
|
118
|
+
onPress={() => handleButtonClick(question)}>
|
|
119
|
+
<Text style={styles.suggestedQuestionText}>{question}</Text>
|
|
120
|
+
</TouchableOpacity>
|
|
121
|
+
))}
|
|
122
|
+
|
|
123
|
+
{ghostMessage && i === messages.length - 1 && (
|
|
124
|
+
<View style={styles.ghostMessageContainer}>
|
|
125
|
+
<Animated.View style={[styles.ghostBar, styles.ghostBarShort, { opacity: fadeAnim }]} />
|
|
126
|
+
<Animated.View style={[styles.ghostBar, styles.ghostBarLong, { opacity: fadeAnim }]} />
|
|
127
|
+
<Animated.View style={[styles.ghostBar, styles.ghostBarMedium, { opacity: fadeAnim }]} />
|
|
128
|
+
</View>
|
|
129
|
+
)}
|
|
130
|
+
</View>
|
|
131
|
+
))}
|
|
132
|
+
</KeyboardAwareScrollView>
|
|
133
|
+
|
|
134
|
+
<KeyboardAvoidingView
|
|
135
|
+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
136
|
+
keyboardVerticalOffset={Platform.OS === 'ios' ? 120 : 60}
|
|
137
|
+
>
|
|
138
|
+
<ChatInput/>
|
|
139
|
+
</KeyboardAvoidingView>
|
|
140
|
+
</Animated.View>
|
|
141
|
+
</View>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const styles = StyleSheet.create({
|
|
146
|
+
overlay: {
|
|
147
|
+
flex: 1,
|
|
148
|
+
position: 'absolute',
|
|
149
|
+
left: 0,
|
|
150
|
+
right: 0,
|
|
151
|
+
top: 0,
|
|
152
|
+
bottom: 0,
|
|
153
|
+
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
154
|
+
justifyContent: 'flex-end',
|
|
155
|
+
},
|
|
156
|
+
outsideTouchable: {
|
|
157
|
+
flex: 1, // Takes up the space above the chat, allows clicking to close
|
|
158
|
+
},
|
|
159
|
+
container: {
|
|
160
|
+
flex: 1,
|
|
161
|
+
backgroundColor: '#f6f6f6',
|
|
162
|
+
position: 'absolute',
|
|
163
|
+
zIndex: 1000,
|
|
164
|
+
left: 0,
|
|
165
|
+
right: 0,
|
|
166
|
+
bottom: 0,
|
|
167
|
+
top: 140,
|
|
168
|
+
pointerEvents: 'box-none',
|
|
169
|
+
borderTopWidth: 1,
|
|
170
|
+
borderTopColor: '#DDD',
|
|
171
|
+
borderTopLeftRadius: 16,
|
|
172
|
+
borderTopRightRadius: 16,
|
|
173
|
+
overflow: 'hidden',
|
|
174
|
+
},
|
|
175
|
+
headerContainer: {
|
|
176
|
+
alignItems: "center",
|
|
177
|
+
backgroundColor: "#f6f6f6",
|
|
178
|
+
borderTopLeftRadius: 16,
|
|
179
|
+
borderTopRightRadius: 16,
|
|
180
|
+
},
|
|
181
|
+
messagesContent: {
|
|
182
|
+
padding: 16,
|
|
183
|
+
justifyContent: 'flex-end',
|
|
184
|
+
},
|
|
185
|
+
messageWrapper: {
|
|
186
|
+
marginBottom: 0,
|
|
187
|
+
},
|
|
188
|
+
messageBubble: {
|
|
189
|
+
padding: 6,
|
|
190
|
+
paddingHorizontal: 16,
|
|
191
|
+
borderRadius: 12,
|
|
192
|
+
marginBottom: 5
|
|
193
|
+
},
|
|
194
|
+
userMessage: {
|
|
195
|
+
alignSelf: 'flex-end',
|
|
196
|
+
backgroundColor: "#003764",
|
|
197
|
+
color: "#ffffff"
|
|
198
|
+
},
|
|
199
|
+
aiMessage: {
|
|
200
|
+
alignSelf: 'flex-start',
|
|
201
|
+
backgroundColor: '#FFFFFF',
|
|
202
|
+
width: '100%',
|
|
203
|
+
},
|
|
204
|
+
productCardWrapper: {
|
|
205
|
+
marginTop: 5,
|
|
206
|
+
},
|
|
207
|
+
suggestedQuestionButton: {
|
|
208
|
+
backgroundColor: "white",
|
|
209
|
+
borderWidth: 1,
|
|
210
|
+
borderColor: "#004687",
|
|
211
|
+
borderRadius: 18,
|
|
212
|
+
paddingVertical: 10,
|
|
213
|
+
paddingHorizontal: 16,
|
|
214
|
+
marginBottom: 7,
|
|
215
|
+
alignSelf: "flex-start",
|
|
216
|
+
},
|
|
217
|
+
suggestedQuestionText: {
|
|
218
|
+
color: "#004687",
|
|
219
|
+
fontSize: 13,
|
|
220
|
+
textAlign: "left",
|
|
221
|
+
},
|
|
222
|
+
ghostMessageContainer: {
|
|
223
|
+
alignSelf: 'flex-start',
|
|
224
|
+
width: '100%',
|
|
225
|
+
backgroundColor: "#FFFFFF",
|
|
226
|
+
borderRadius: 10,
|
|
227
|
+
borderTopLeftRadius: 0,
|
|
228
|
+
padding: 14,
|
|
229
|
+
marginVertical: 5,
|
|
230
|
+
},
|
|
231
|
+
ghostBar: {
|
|
232
|
+
height: 20,
|
|
233
|
+
borderRadius: 10,
|
|
234
|
+
backgroundColor: "#ebebeb",
|
|
235
|
+
marginVertical: 3,
|
|
236
|
+
},
|
|
237
|
+
ghostBarShort: {
|
|
238
|
+
width: "50%",
|
|
239
|
+
},
|
|
240
|
+
ghostBarMedium: {
|
|
241
|
+
width: "75%",
|
|
242
|
+
},
|
|
243
|
+
ghostBarLong: {
|
|
244
|
+
width: "100%",
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
{/* <Testing
|
|
249
|
+
onProductCardClick={onProductCardClick}
|
|
250
|
+
onAddToCartClick={onAddToCartClick}
|
|
251
|
+
/> */}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import { TouchableOpacity, View, StyleSheet, Text } from 'react-native';
|
|
3
|
+
import { AppContext } from '../contexts/AppContext';
|
|
4
|
+
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
5
|
+
|
|
6
|
+
export const ChatIcon = () => {
|
|
7
|
+
const { setShowModal, messages, maintenance, disclaimer, uiConfig } = useContext(AppContext);
|
|
8
|
+
|
|
9
|
+
// Determine position from uiConfig or default
|
|
10
|
+
const iconPosition = {
|
|
11
|
+
bottom: uiConfig?.iconPosition?.bottom ?? 80,
|
|
12
|
+
right: uiConfig?.iconPosition?.right ?? 20,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Determine icon type (bubble or tab)
|
|
16
|
+
const iconType = uiConfig?.iconType ?? 'bubble';
|
|
17
|
+
|
|
18
|
+
const handleClick = () => {
|
|
19
|
+
if (!disclaimer) {
|
|
20
|
+
setShowModal("Form");
|
|
21
|
+
} else {
|
|
22
|
+
if (messages.length > 1 || maintenance) {
|
|
23
|
+
setShowModal("ChatWindow");
|
|
24
|
+
} else {
|
|
25
|
+
setShowModal("Welcome");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Different styles for bubble and tab
|
|
31
|
+
const containerStyle =
|
|
32
|
+
iconType === 'tab'
|
|
33
|
+
? [styles.tabContainer, iconPosition]
|
|
34
|
+
: [styles.bubbleContainer, iconPosition];
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<TouchableOpacity style={containerStyle} onPress={handleClick} activeOpacity={0.7}>
|
|
38
|
+
<View style={styles.iconContent}>
|
|
39
|
+
<Ionicons name="chatbubble-ellipses" size={28} color="white" />
|
|
40
|
+
{iconType === 'tab' && (
|
|
41
|
+
<Text style={styles.tabText}>Chat with Heritage</Text>
|
|
42
|
+
)}
|
|
43
|
+
</View>
|
|
44
|
+
</TouchableOpacity>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const styles = StyleSheet.create({
|
|
49
|
+
// Bubble Style
|
|
50
|
+
bubbleContainer: {
|
|
51
|
+
position: 'absolute',
|
|
52
|
+
width: 60,
|
|
53
|
+
height: 60,
|
|
54
|
+
borderRadius: 30,
|
|
55
|
+
backgroundColor: '#FFA500',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
justifyContent: 'center',
|
|
58
|
+
zIndex: 10,
|
|
59
|
+
shadowColor: '#000',
|
|
60
|
+
shadowOffset: { width: 0, height: 2 },
|
|
61
|
+
shadowOpacity: 0.2,
|
|
62
|
+
shadowRadius: 3,
|
|
63
|
+
elevation: 5,
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// Tab Style
|
|
67
|
+
tabContainer: {
|
|
68
|
+
position: 'absolute',
|
|
69
|
+
width: 130,
|
|
70
|
+
height: 50,
|
|
71
|
+
borderTopLeftRadius: 25,
|
|
72
|
+
borderBottomLeftRadius: 25,
|
|
73
|
+
backgroundColor: '#367CB6',
|
|
74
|
+
flexDirection: 'row',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
paddingHorizontal: 15,
|
|
77
|
+
justifyContent: 'flex-start',
|
|
78
|
+
zIndex: 10,
|
|
79
|
+
shadowColor: '#000',
|
|
80
|
+
shadowOffset: { width: 0, height: 2 },
|
|
81
|
+
shadowOpacity: 0.2,
|
|
82
|
+
shadowRadius: 3,
|
|
83
|
+
elevation: 5,
|
|
84
|
+
},
|
|
85
|
+
iconContent: {
|
|
86
|
+
flexDirection: 'row',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
tabText: {
|
|
91
|
+
color: 'white',
|
|
92
|
+
fontSize: 14,
|
|
93
|
+
fontWeight: '500',
|
|
94
|
+
marginLeft: 8,
|
|
95
|
+
},
|
|
96
|
+
});
|
package/src/layout/layout.js
CHANGED
|
@@ -1,27 +1,154 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
3
|
-
Platform, TouchableOpacity, RefreshControl, } from 'react-native';
|
|
4
|
-
import { ChatWindow } from './chatWindow';
|
|
1
|
+
import React, { useRef, useState, useContext, useEffect } from 'react';
|
|
2
|
+
import { View, Animated, PanResponder, Pressable, StyleSheet } from 'react-native';
|
|
5
3
|
import { AppContext } from '../contexts/AppContext';
|
|
6
|
-
import { ChatIcon } from './chatIcon';
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
// Pages
|
|
6
|
+
import { ChatWindow } from './window';
|
|
7
|
+
import { ChatIcon } from './icon';
|
|
8
|
+
import { Welcome } from './welcome'
|
|
9
|
+
import { Disclaimer } from './disclaimer';
|
|
10
|
+
import { EmailForm } from '../components/email';
|
|
11
|
+
|
|
12
|
+
export const Layout = () => {
|
|
13
|
+
const { showModal, setShowModal, uiConfig, disclaimer, maintenance, messages } = useContext(AppContext);
|
|
14
|
+
|
|
15
|
+
const panY = useRef(new Animated.Value(0)).current;
|
|
16
|
+
const isDragging = useRef(false);
|
|
17
|
+
|
|
18
|
+
const handleClick = () => {
|
|
19
|
+
if ((uiConfig.showIcon ?? true) !== true) {
|
|
20
|
+
setShowModal("Off");
|
|
21
|
+
} else {
|
|
22
|
+
setShowModal("Icon");
|
|
23
|
+
}
|
|
24
|
+
};
|
|
10
25
|
|
|
26
|
+
const panResponder = PanResponder.create({
|
|
27
|
+
onStartShouldSetPanResponder: () => true,
|
|
28
|
+
onMoveShouldSetPanResponder: () => true,
|
|
29
|
+
onPanResponderGrant: () => {
|
|
30
|
+
isDragging.current = true;
|
|
31
|
+
},
|
|
32
|
+
onPanResponderMove: (evt, gestureState) => {
|
|
33
|
+
if (isDragging.current && gestureState.dy >= 0) {
|
|
34
|
+
panY.setValue(gestureState.dy);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
onPanResponderRelease: (evt, gestureState) => {
|
|
38
|
+
if (gestureState.dy > 120) {
|
|
39
|
+
// Close if dragged far enough
|
|
40
|
+
Animated.timing(panY, {
|
|
41
|
+
toValue: 600, // Off-screen
|
|
42
|
+
duration: 200,
|
|
43
|
+
useNativeDriver: false,
|
|
44
|
+
}).start(() => {
|
|
45
|
+
handleClick();
|
|
46
|
+
panY.setValue(0); // Reset for next time
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
// Snap back to full height
|
|
50
|
+
Animated.spring(panY, {
|
|
51
|
+
toValue: 0,
|
|
52
|
+
friction: 6,
|
|
53
|
+
tension: 80,
|
|
54
|
+
useNativeDriver: false,
|
|
55
|
+
}).start();
|
|
56
|
+
}
|
|
57
|
+
isDragging.current = false;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// uiConfig.showIcon, uiConfig.toggleChat
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!disclaimer) {
|
|
65
|
+
setShowModal("Form");
|
|
66
|
+
} else {
|
|
67
|
+
if (messages.length > 1 || maintenance) {
|
|
68
|
+
setShowModal("ChatWindow");
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
setShowModal("Welcome")
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}, [uiConfig.toggleChat]);
|
|
75
|
+
|
|
11
76
|
return (
|
|
12
|
-
<View style={styles.
|
|
13
|
-
{showModal === "Icon"
|
|
77
|
+
<View style={styles.layout} pointerEvents="box-none">
|
|
78
|
+
{showModal === "Icon" && (uiConfig.showIcon ?? true) && (
|
|
79
|
+
<ChatIcon />
|
|
80
|
+
)}
|
|
81
|
+
{showModal === "ChatWindow" && (
|
|
82
|
+
<>
|
|
83
|
+
<Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
|
|
84
|
+
<Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
|
|
85
|
+
<ChatWindow panHandlers={panResponder.panHandlers} />
|
|
86
|
+
</Animated.View>
|
|
87
|
+
</>
|
|
88
|
+
)}
|
|
89
|
+
{showModal === "Welcome" && (
|
|
90
|
+
<>
|
|
91
|
+
<Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
|
|
92
|
+
<Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
|
|
93
|
+
<Welcome panHandlers={panResponder.panHandlers} />
|
|
94
|
+
</Animated.View>
|
|
95
|
+
</>
|
|
96
|
+
)}
|
|
97
|
+
{showModal === "Form" &&
|
|
98
|
+
<>
|
|
99
|
+
<Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
|
|
100
|
+
<Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
|
|
101
|
+
<Disclaimer panHandlers={panResponder.panHandlers} />
|
|
102
|
+
</Animated.View>
|
|
103
|
+
</>
|
|
104
|
+
}
|
|
105
|
+
{showModal === "Email" &&
|
|
106
|
+
<>
|
|
107
|
+
<Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
|
|
108
|
+
<Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
|
|
109
|
+
<EmailForm panHandlers={panResponder.panHandlers} />
|
|
110
|
+
</Animated.View>
|
|
111
|
+
</>
|
|
112
|
+
}
|
|
14
113
|
</View>
|
|
15
114
|
);
|
|
16
115
|
};
|
|
17
116
|
|
|
18
117
|
const styles = StyleSheet.create({
|
|
19
|
-
|
|
118
|
+
layout: {
|
|
20
119
|
flex: 1,
|
|
21
120
|
position: 'absolute',
|
|
22
121
|
top: 0,
|
|
23
122
|
left: 0,
|
|
24
123
|
right: 0,
|
|
25
124
|
bottom: 0,
|
|
125
|
+
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
126
|
+
justifyContent: 'flex-end',
|
|
127
|
+
},
|
|
128
|
+
container: {
|
|
129
|
+
flex: 1,
|
|
130
|
+
backgroundColor: '#f6f6f6',
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
zIndex: 1000,
|
|
133
|
+
left: 0,
|
|
134
|
+
right: 0,
|
|
135
|
+
bottom: 0,
|
|
136
|
+
top: 140,
|
|
137
|
+
pointerEvents: 'box-none',
|
|
138
|
+
borderTopWidth: 1,
|
|
139
|
+
borderTopColor: '#DDD',
|
|
140
|
+
borderTopLeftRadius: 16,
|
|
141
|
+
borderTopRightRadius: 16,
|
|
142
|
+
overflow: 'hidden',
|
|
143
|
+
},
|
|
144
|
+
outsideTouchable: {
|
|
145
|
+
flex: 1,
|
|
26
146
|
},
|
|
147
|
+
dragHandle: {
|
|
148
|
+
height: 20,
|
|
149
|
+
width: '100%',
|
|
150
|
+
backgroundColor: '#DDD',
|
|
151
|
+
borderTopLeftRadius: 16,
|
|
152
|
+
borderTopRightRadius: 16,
|
|
153
|
+
}
|
|
27
154
|
});
|