KEXEL commited on
Commit
ec8a33e
·
verified ·
1 Parent(s): 3e56ddd

🐳 27/02 - 21:07 - tem como colocar no formato mobile multiplataformas

Browse files
Files changed (4) hide show
  1. capacitor.config.json +22 -0
  2. package.json +29 -0
  3. www/index.html +338 -0
  4. www/js/app.js +492 -0
capacitor.config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "appId": "com.nova.assistant",
3
+ "appName": "Nova Assistant",
4
+ "webDir": "www",
5
+ "bundledWebRuntime": false,
6
+ "plugins": {
7
+ "SplashScreen": {
8
+ "launchShowDuration": 2000,
9
+ "launchAutoHide": true,
10
+ "backgroundColor": "#667eea",
11
+ "androidSplashResourceName": "splash",
12
+ "androidScaleType": "CENTER_CROP",
13
+ "showSpinner": false,
14
+ "splashFullScreen": true,
15
+ "splashImmersive": true
16
+ },
17
+ "StatusBar": {
18
+ "style": "light",
19
+ "backgroundColor": "#667eea"
20
+ }
21
+ }
22
+ }
package.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "nova-assistant",
3
+ "version": "1.0.0",
4
+ "description": "Assistente Virtual Nova - Mobile App",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "npx serve www",
8
+ "build": "npm run copy",
9
+ "copy": "npx cap copy",
10
+ "sync": "npx cap sync",
11
+ "android": "npx cap open android",
12
+ "ios": "npx cap open ios",
13
+ "pwa": "npx serve www -p 8080"
14
+ },
15
+ "dependencies": {
16
+ "@capacitor/android": "^5.7.0",
17
+ "@capacitor/core": "^5.7.0",
18
+ "@capacitor/ios": "^5.7.0",
19
+ "@capacitor/splash-screen": "^5.2.0",
20
+ "@capacitor/status-bar": "^5.0.7",
21
+ "@capacitor/keyboard": "^5.0.8",
22
+ "@capacitor/preferences": "^5.0.7",
23
+ "@capacitor/speech-recognition": "^5.0.0",
24
+ "@capacitor/text-speech": "^5.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@capacitor/cli": "^5.7.0"
28
+ }
29
+ }
www/index.html ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=1">
6
+ <meta name="apple-mobile-web-app-capable" content="yes">
7
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
8
+ <meta name="theme-color" content="#667eea">
9
+ <meta name="mobile-web-app-capable" content="yes">
10
+ <title>Nova Assistant</title>
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+ <script src="https://unpkg.com/lucide@latest"></script>
13
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
14
+ <style>
15
+ * {
16
+ font-family: 'Inter', sans-serif;
17
+ -webkit-tap-highlight-color: transparent;
18
+ }
19
+
20
+ /* Safe area padding for notched devices */
21
+ .safe-area-top {
22
+ padding-top: env(safe-area-inset-top, 0px);
23
+ }
24
+ .safe-area-bottom {
25
+ padding-bottom: env(safe-area-inset-bottom, 0px);
26
+ }
27
+ .safe-area-x {
28
+ padding-left: env(safe-area-inset-left, 0px);
29
+ padding-right: env(safe-area-inset-right, 0px);
30
+ }
31
+
32
+ .gradient-bg {
33
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
34
+ }
35
+
36
+ .glass-effect {
37
+ background: rgba(255, 255, 255, 0.98);
38
+ backdrop-filter: blur(20px);
39
+ -webkit-backdrop-filter: blur(20px);
40
+ }
41
+
42
+ .dark .glass-effect {
43
+ background: rgba(30, 30, 35, 0.98);
44
+ }
45
+
46
+ .message-bubble {
47
+ animation: messageSlide 0.3s ease-out;
48
+ }
49
+
50
+ @keyframes messageSlide {
51
+ from {
52
+ opacity: 0;
53
+ transform: translateY(10px) scale(0.95);
54
+ }
55
+ to {
56
+ opacity: 1;
57
+ transform: translateY(0) scale(1);
58
+ }
59
+ }
60
+
61
+ .typing-dot {
62
+ animation: typingBounce 1.4s infinite ease-in-out both;
63
+ }
64
+
65
+ .typing-dot:nth-child(1) { animation-delay: -0.32s; }
66
+ .typing-dot:nth-child(2) { animation-delay: -0.16s; }
67
+
68
+ @keyframes typingBounce {
69
+ 0%, 80%, 100% { transform: scale(0.6); }
70
+ 40% { transform: scale(1); }
71
+ }
72
+
73
+ .pulse-ring {
74
+ animation: pulseRing 2s ease-out infinite;
75
+ }
76
+
77
+ @keyframes pulseRing {
78
+ 0% {
79
+ transform: scale(0.8);
80
+ opacity: 1;
81
+ }
82
+ 100% {
83
+ transform: scale(2);
84
+ opacity: 0;
85
+ }
86
+ }
87
+
88
+ .wave-animation {
89
+ animation: wave 1s ease-in-out infinite;
90
+ }
91
+
92
+ @keyframes wave {
93
+ 0%, 100% { transform: scaleY(1); }
94
+ 50% { transform: scaleY(0.4); }
95
+ }
96
+
97
+ .scrollbar-hide::-webkit-scrollbar {
98
+ display: none;
99
+ }
100
+ .scrollbar-hide {
101
+ -ms-overflow-style: none;
102
+ scrollbar-width: none;
103
+ }
104
+
105
+ .suggestion-chip {
106
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
107
+ }
108
+ .suggestion-chip:hover {
109
+ transform: translateY(-1px);
110
+ }
111
+ .suggestion-chip:active {
112
+ transform: scale(0.96);
113
+ }
114
+
115
+ /* Mobile-optimized touch targets */
116
+ .touch-target {
117
+ min-height: 44px;
118
+ min-width: 44px;
119
+ }
120
+
121
+ /* Haptic feedback simulation */
122
+ .haptic:active {
123
+ transform: scale(0.97);
124
+ }
125
+
126
+ /* Swipe gestures */
127
+ .swipeable {
128
+ touch-action: pan-y;
129
+ user-select: none;
130
+ }
131
+
132
+ /* Bottom sheet animation */
133
+ .bottom-sheet {
134
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
135
+ }
136
+ .bottom-sheet.hidden {
137
+ transform: translateY(100%);
138
+ }
139
+
140
+ /* Ripple effect */
141
+ .ripple {
142
+ position: relative;
143
+ overflow: hidden;
144
+ }
145
+ .ripple::after {
146
+ content: '';
147
+ position: absolute;
148
+ top: 50%;
149
+ left: 50%;
150
+ width: 0;
151
+ height: 0;
152
+ background: rgba(255,255,255,0.3);
153
+ border-radius: 50%;
154
+ transform: translate(-50%, -50%);
155
+ transition: width 0.3s, height 0.3s;
156
+ }
157
+ .ripple:active::after {
158
+ width: 200%;
159
+ height: 200%;
160
+ }
161
+ </style>
162
+ </head>
163
+ <body class="gradient-bg h-screen overflow-hidden">
164
+
165
+ <!-- Status Bar Background -->
166
+ <div class="fixed top-0 left-0 right-0 h-[env(safe-area-inset-top,20px)] gradient-bg z-50"></div>
167
+
168
+ <!-- Main Container -->
169
+ <div id="app" class="glass-effect h-full flex flex-col overflow-hidden safe-area-top safe-area-bottom safe-area-x">
170
+
171
+ <!-- Header -->
172
+ <header class="bg-white/90 border-b border-gray-100 px-4 pt-2 pb-3 flex items-center justify-between shrink-0">
173
+ <div class="flex items-center gap-3">
174
+ <div class="relative shrink-0">
175
+ <div class="w-11 h-11 rounded-full gradient-bg flex items-center justify-center shadow-lg shadow-purple-200">
176
+ <i data-lucide="bot" class="w-5 h-5 text-white"></i>
177
+ </div>
178
+ <div class="absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 bg-green-400 rounded-full border-2 border-white"></div>
179
+ <div class="absolute inset-0 rounded-full border-2 border-purple-400 pulse-ring"></div>
180
+ </div>
181
+ <div class="min-w-0">
182
+ <h1 class="font-semibold text-gray-800 text-base truncate">Nova Assistant</h1>
183
+ <p class="text-xs text-gray-500 flex items-center gap-1">
184
+ <span class="w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse"></span>
185
+ Online
186
+ </p>
187
+ </div>
188
+ </div>
189
+
190
+ <div class="flex items-center gap-1">
191
+ <button onclick="showMenu()" class="touch-target p-2.5 hover:bg-gray-100 rounded-xl transition-colors ripple">
192
+ <i data-lucide="more-vertical" class="w-5 h-5 text-gray-600"></i>
193
+ </button>
194
+ </div>
195
+ </header>
196
+
197
+ <!-- Chat Area -->
198
+ <main id="chatContainer" class="flex-1 overflow-y-auto scrollbar-hide p-4 space-y-3 bg-gradient-to-b from-gray-50/50 to-white/95">
199
+
200
+ <!-- Welcome Message -->
201
+ <div class="text-center py-6 px-2">
202
+ <div class="w-16 h-16 mx-auto rounded-2xl gradient-bg flex items-center justify-center mb-3 shadow-lg shadow-purple-200">
203
+ <i data-lucide="sparkles" class="w-8 h-8 text-white"></i>
204
+ </div>
205
+ <h2 class="text-xl font-bold text-gray-800 mb-1">Olá! 👋</h2>
206
+ <p class="text-sm text-gray-500">Como posso ajudar você hoje?</p>
207
+ </div>
208
+
209
+ <!-- Suggestions Horizontal Scroll -->
210
+ <div class="flex gap-2 overflow-x-auto scrollbar-hide pb-2 -mx-1 px-1 snap-x">
211
+ <button onclick="sendSuggestion('Que horas são?')" class="suggestion-chip shrink-0 snap-start px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 shadow-sm">
212
+ 🕐 Horário
213
+ </button>
214
+ <button onclick="sendSuggestion('Qual a capital do Japão?')" class="suggestion-chip shrink-0 snap-start px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 shadow-sm">
215
+ 🌍 Geografia
216
+ </button>
217
+ <button onclick="sendSuggestion('Me conte uma piada')" class="suggestion-chip shrink-0 snap-start px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 shadow-sm">
218
+ 😄 Piada
219
+ </button>
220
+ <button onclick="sendSuggestion('Preciso de motivação')" class="suggestion-chip shrink-0 snap-start px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 shadow-sm">
221
+ 💪 Motivação
222
+ </button>
223
+ <button onclick="sendSuggestion('Ajuda')" class="suggestion-chip shrink-0 snap-start px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 shadow-sm">
224
+ ❓ Ajuda
225
+ </button>
226
+ </div>
227
+
228
+ </main>
229
+
230
+ <!-- Quick Actions Bar -->
231
+ <div id="quickActions" class="bg-white border-t border-gray-100 px-4 py-2 shrink-0 hidden">
232
+ <div class="flex gap-3 overflow-x-auto scrollbar-hide">
233
+ <button onclick="sendSuggestion('Calcular 15% de 200')" class="shrink-0 px-3 py-1.5 bg-purple-50 rounded-full text-xs text-purple-600 font-medium">
234
+ 15% de 200
235
+ </button>
236
+ <button onclick="sendSuggestion('Que dia é hoje?')" class="shrink-0 px-3 py-1.5 bg-purple-50 rounded-full text-xs text-purple-600 font-medium">
237
+ Data hoje
238
+ </button>
239
+ <button onclick="sendSuggestion('Capital da França')" class="shrink-0 px-3 py-1.5 bg-purple-50 rounded-full text-xs text-purple-600 font-medium">
240
+ 🇫🇷 França
241
+ </button>
242
+ </div>
243
+ </div>
244
+
245
+ <!-- Input Area -->
246
+ <footer class="bg-white border-t border-gray-100 p-3 safe-area-bottom shrink-0">
247
+ <form id="messageForm" class="flex items-end gap-2">
248
+ <button type="button" onclick="toggleQuickActions()" class="touch-target p-3 hover:bg-gray-100 rounded-xl transition-colors shrink-0">
249
+ <i data-lucide="plus" class="w-5 h-5 text-gray-400"></i>
250
+ </button>
251
+
252
+ <div class="flex-1 relative">
253
+ <textarea
254
+ id="messageInput"
255
+ placeholder="Digite uma mensagem..."
256
+ class="w-full px-3 py-2.5 pr-10 bg-gray-100 border-0 rounded-2xl resize-none focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:bg-white transition-all text-sm"
257
+ rows="1"
258
+ style="min-height: 40px; max-height: 100px;"
259
+ oninput="autoResize(this), checkInput()"
260
+ onkeydown="handleKeyDown(event)"
261
+ ></textarea>
262
+ <button type="button" onclick="startVoiceInput()" id="micBtn" class="absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-lg transition-colors">
263
+ <i data-lucide="mic" class="w-4 h-4 text-gray-400"></i>
264
+ </button>
265
+ </div>
266
+
267
+ <button
268
+ type="submit"
269
+ id="sendBtn"
270
+ class="touch-target p-3 gradient-bg rounded-xl text-white disabled:opacity-40 disabled:scale-100 transition-all transform active:scale-95 ripple shrink-0"
271
+ disabled
272
+ >
273
+ <i data-lucide="send" class="w-5 h-5"></i>
274
+ </button>
275
+ </form>
276
+ </footer>
277
+
278
+ </div>
279
+
280
+ <!-- Menu Bottom Sheet -->
281
+ <div id="menuSheet" class="fixed inset-0 z-50 pointer-events-none opacity-0 transition-opacity" onclick="hideMenu()">
282
+ <div class="absolute inset-0 bg-black/30 backdrop-blur-sm" onclick="hideMenu()"></div>
283
+ <div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-3xl p-6 bottom-sheet hidden transform translate-y-full" onclick="event.stopPropagation()">
284
+ <div class="w-12 h-1 bg-gray-300 rounded-full mx-auto mb-6"></div>
285
+ <h3 class="text-lg font-semibold mb-4">Opções</h3>
286
+
287
+ <div class="space-y-2">
288
+ <button onclick="clearChat(); hideMenu()" class="w-full flex items-center gap-3 p-4 rounded-xl hover:bg-gray-50 transition-colors">
289
+ <i data-lucide="trash-2" class="w-5 h-5 text-red-500"></i>
290
+ <span class="text-gray-700">Limpar conversa</span>
291
+ </button>
292
+ <button onclick="exportChat(); hideMenu()" class="w-full flex items-center gap-3 p-4 rounded-xl hover:bg-gray-50 transition-colors">
293
+ <i data-lucide="share" class="w-5 h-5 text-blue-500"></i>
294
+ <span class="text-gray-700">Exportar conversa</span>
295
+ </button>
296
+ <button onclick="toggleDarkMode(); hideMenu()" class="w-full flex items-center gap-3 p-4 rounded-xl hover:bg-gray-50 transition-colors">
297
+ <i data-lucide="moon" class="w-5 h-5 text-purple-500"></i>
298
+ <span class="text-gray-700">Modo escuro</span>
299
+ </button>
300
+ <button onclick="showAbout(); hideMenu()" class="w-full flex items-center gap-3 p-4 rounded-xl hover:bg-gray-50 transition-colors">
301
+ <i data-lucide="info" class="w-5 h-5 text-gray-500"></i>
302
+ <span class="text-gray-700">Sobre</span>
303
+ </button>
304
+ </div>
305
+
306
+ <div class="mt-4 mb-2">
307
+ <button onclick="hideMenu()" class="w-full p-4 bg-gray-100 rounded-xl font-medium text-gray-700">
308
+ Fechar
309
+ </button>
310
+ </div>
311
+ </div>
312
+ </div>
313
+
314
+ <!-- Voice Input Modal -->
315
+ <div id="voiceModal" class="fixed inset-0 bg-black/70 backdrop-blur-md flex items-center justify-center opacity-0 pointer-events-none transition-opacity z-50">
316
+ <div class="bg-white rounded-3xl p-8 text-center transform scale-90 transition-transform mx-6">
317
+ <div class="flex justify-center gap-1 mb-6 h-12">
318
+ <div class="w-2 h-10 bg-purple-500 rounded-full wave-animation" style="animation-delay: 0s;"></div>
319
+ <div class="w-2 h-14 bg-purple-500 rounded-full wave-animation" style="animation-delay: 0.1s;"></div>
320
+ <div class="w-2 h-8 bg-purple-500 rounded-full wave-animation" style="animation-delay: 0.2s;"></div>
321
+ <div class="w-2 h-12 bg-purple-500 rounded-full wave-animation" style="animation-delay: 0.3s;"></div>
322
+ <div class="w-2 h-6 bg-purple-500 rounded-full wave-animation" style="animation-delay: 0.4s;"></div>
323
+ </div>
324
+ <p class="text-gray-700 font-medium mb-1">Ouvindo...</p>
325
+ <p id="voiceText" class="text-xs text-gray-400">Toque para cancelar</p>
326
+ <button onclick="stopVoiceInput()" class="mt-6 w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto">
327
+ <i data-lucide="x" class="w-5 h-5 text-red-500"></i>
328
+ </button>
329
+ </div>
330
+ </div>
331
+
332
+ <script src="https://unpkg.com/@capacitor/core@latest/dist/capacitor.js"></script>
333
+ <script src="js/app.js"></script>
334
+ <script>
335
+ lucide.createIcons();
336
+ </script>
337
+ </body>
338
+ </html>
www/js/app.js ADDED
@@ -0,0 +1,492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Capacitor Plugins
2
+ let {
3
+ StatusBar,
4
+ SplashScreen,
5
+ Keyboard,
6
+ Preferences,
7
+ Toast
8
+ } = typeof Capacitor !== 'undefined' ? Capacitor.Plugins : {};
9
+
10
+ // ==================== STATE ====================
11
+ let isTyping = false;
12
+ let messageHistory = [];
13
+ let recognition = null;
14
+ let isNative = typeof Capacitor !== 'undefined';
15
+ let isDarkMode = false;
16
+
17
+ // ==================== DOM ELEMENTS ====================
18
+ const chatContainer = document.getElementById('chatContainer');
19
+ const messageForm = document.getElementById('messageForm');
20
+ const messageInput = document.getElementById('messageInput');
21
+ const sendBtn = document.getElementById('sendBtn');
22
+ const voiceModal = document.getElementById('voiceModal');
23
+ const voiceText = document.getElementById('voiceText');
24
+ const menuSheet = document.getElementById('menuSheet');
25
+ const micBtn = document.getElementById('micBtn');
26
+
27
+ // ==================== KNOWLEDGE BASE ====================
28
+ const knowledgeBase = {
29
+ greetings: {
30
+ patterns: ['oi', 'olá', 'ola', 'hey', 'e aí', 'e ai', 'bom dia', 'boa tarde', 'boa noite', 'salve', 'hi', 'hello'],
31
+ responses: [
32
+ 'Oi! Como posso ajudar? 😊',
33
+ 'Olá! Que bom ver você! ✨',
34
+ 'Hey! Pronto para ajudar! 🚀',
35
+ 'Bem-vindo! O que posso fazer?'
36
+ ]
37
+ },
38
+ time: {
39
+ patterns: ['hora', 'horas', 'que horas', 'horário', 'horario'],
40
+ responses: () => {
41
+ const now = new Date();
42
+ return `🕐 ${now.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}`;
43
+ }
44
+ },
45
+ date: {
46
+ patterns: ['data', 'data de hoje', 'que dia é hoje', 'dia de hoje', 'hoje é que dia'],
47
+ responses: () => {
48
+ const now = new Date();
49
+ return `📅 ${now.toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' })}`;
50
+ }
51
+ },
52
+ weather: {
53
+ patterns: ['clima', 'tempo', 'previsão', 'previsao', 'temperatura', 'esta frio', 'esta calor', 'ta frio', 'ta calor'],
54
+ responses: [
55
+ '🌤️ Verifique o app de clima do seu celular para dados em tempo real!',
56
+ '☀️ Não tenho acesso ao GPS. Confira o Clima do iOS/Android!'
57
+ ]
58
+ },
59
+ jokes: {
60
+ patterns: ['piada', 'conta uma piada', 'me faça rir', 'me faz rir', 'algo engraçado'],
61
+ responses: [
62
+ '😄 Por que o computador foi ao médico? Porque tinha vírus! 🦠',
63
+ '😂 O que o pato disse para a pata? Vem quá! 🦆',
64
+ '🤣 Qual é o animal mais antigo? A zebra, preto e branco! 🦓',
65
+ '😅 Por que o livro de matemática se suicidou? Tinha muitos problemas! 📚'
66
+ ]
67
+ },
68
+ capital: {
69
+ patterns: ['capital', 'capital da', 'qual a capital', 'capital de'],
70
+ responses: {
71
+ 'frança': '🇫🇷 Paris - Cidade Luz!',
72
+ 'brasil': '🇧🇷 Brasília',
73
+ 'japão': '🇯🇵 Tóquio',
74
+ 'estados unidos': '🇺🇸 Washington, D.C.',
75
+ 'argentina': '🇦🇷 Buenos Aires',
76
+ 'portugal': '🇵🇹 Lisboa',
77
+ 'espanha': '🇪🇸 Madri',
78
+ 'itália': '🇮🇹 Roma',
79
+ 'alemanha': '🇩🇪 Berlim',
80
+ 'canadá': '🇨🇦 Ottawa'
81
+ }
82
+ },
83
+ thanks: {
84
+ patterns: ['obrigado', 'obrigada', 'valeu', 'agradeço', 'thanks', 'ty', 'vlw', 'tmj'],
85
+ responses: [
86
+ 'Por nada! 😊',
87
+ 'De nada! ✨',
88
+ 'Sempre à disposição! 🙌'
89
+ ]
90
+ },
91
+ calculator: {
92
+ patterns: ['calcular', 'quanto é', 'resultado de', '+', '-', '*', '/', 'x', 'vezes', 'dividido', 'mais', 'menos'],
93
+ responses: (message) => {
94
+ try {
95
+ let expr = message.toLowerCase()
96
+ .replace(/vezes|x/gi, '*')
97
+ .replace(/dividido/gi, '/')
98
+ .replace(/mais/gi, '+')
99
+ .replace(/menos/gi, '-')
100
+ .replace(/[^0-9+\-*/().\s]/g, '');
101
+
102
+ if (!expr.match(/[0-9]/)) return null;
103
+ const result = Function('"use strict"; return (' + expr + ')')();
104
+ return `🧮 ${expr.replace('*', '×').replace('/', '÷')} = ${result}`;
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+ },
110
+ name: {
111
+ patterns: ['seu nome', 'como se chama', 'quem é você', 'quem e voce', 'qual seu nome'],
112
+ responses: [
113
+ 'Eu sou a Nova! 🤖✨',
114
+ 'Me chamo Nova, prazer!',
115
+ 'Olá! Sou a Nova 💜'
116
+ ]
117
+ },
118
+ help: {
119
+ patterns: ['ajuda', 'help', 'o que você faz', 'o que voce faz', 'capacidades', 'comandos'],
120
+ responses: [
121
+ '🛠️ **O que faço:**\n• 🕐 Hora/data\n• 🌍 Capitais\n• 😄 Piadas\n• 🧮 Cálculos\n• 💬 Conversas\n\n**Exemplos:**\n"Que horas são?"\n"Quanto é 25×4?"\n"Capital do Japão?"'
122
+ ]
123
+ },
124
+ motivation: {
125
+ patterns: ['motivação', 'motivacao', 'ânimo', 'animo', 'triste', 'deprimido', 'incentivo', 'não consigo', 'nao consigo', 'desanimado'],
126
+ responses: [
127
+ '💪 "O único limite é a sua determinação."\n\nVocê consegue! ✨',
128
+ '🌟 "Levanta, sacode a poeira e dá a volta por cima!"\n\nVai dar certo! 💜',
129
+ '⭐ "Acredite em você!"\n\nVocê é capaz de grandes conquistas! 🚀'
130
+ ]
131
+ },
132
+ love: {
133
+ patterns: ['te amo', 'amo você', 'gosto de você', 'gosto de ti', 'quer casar'],
134
+ responses: [
135
+ '💜 Ahh, que fofo! Eu também gosto muito de conversar com você!',
136
+ '😊 Fico muito feliz em ouvir isso! Você é especial!',
137
+ '💖 Obrigada! Você me faz querer ser uma assistente ainda melhor!'
138
+ ]
139
+ },
140
+ goodbye: {
141
+ patterns: ['tchau', 'adeus', 'até logo', 'ate logo', 'bye', 'goodbye', 'até mais', 'ate mais', 'flw'],
142
+ responses: [
143
+ 'Até logo! 👋 Foi ótimo conversar!',
144
+ 'Tchau! Volte sempre! ✨',
145
+ 'Até a próxima! Cuida-se! 💜'
146
+ ]
147
+ }
148
+ };
149
+
150
+ // ==================== INITIALIZATION ====================
151
+ async function initApp() {
152
+ // Hide splash after load
153
+ if (isNative && SplashScreen) {
154
+ setTimeout(() => SplashScreen.hide(), 1000);
155
+ }
156
+
157
+ // Set status bar style
158
+ if (isNative && StatusBar) {
159
+ StatusBar.setStyle({ style: 'Light' });
160
+ StatusBar.setBackgroundColor({ color: '#667eea' });
161
+ }
162
+
163
+ // Handle keyboard
164
+ if (isNative && Keyboard) {
165
+ Keyboard.addListener('keyboardWillShow', () => {
166
+ chatContainer.scrollTop = chatContainer.scrollHeight;
167
+ });
168
+ }
169
+
170
+ // Check saved preference
171
+ const saved = await getStorage('darkMode');
172
+ if (saved === 'true') toggleDarkMode(true);
173
+
174
+ // Focus input
175
+ setTimeout(() => messageInput.focus(), 300);
176
+
177
+ // Welcome with vibration
178
+ vibrate([50, 30, 50]);
179
+ }
180
+
181
+ // ==================== UI FUNCTIONS ====================
182
+ function autoResize(textarea) {
183
+ textarea.style.height = 'auto';
184
+ textarea.style.height = Math.min(textarea.scrollHeight, 100) + 'px';
185
+ }
186
+
187
+ function checkInput() {
188
+ sendBtn.disabled = messageInput.value.trim().length === 0;
189
+ micBtn.style.opacity = messageInput.value ? '0' : '1';
190
+ }
191
+
192
+ function handleKeyDown(e) {
193
+ if (e.key === 'Enter' && !e.shiftKey) {
194
+ e.preventDefault();
195
+ if (messageInput.value.trim()) {
196
+ messageForm.dispatchEvent(new Event('submit'));
197
+ }
198
+ }
199
+ }
200
+
201
+ function toggleQuickActions() {
202
+ const qa = document.getElementById('quickActions');
203
+ qa.classList.toggle('hidden');
204
+ if (!qa.classList.contains('hidden')) {
205
+ setTimeout(() => {
206
+ const suggestions = qa.querySelectorAll('button');
207
+ suggestions[0]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
208
+ }, 10);
209
+ }
210
+ }
211
+
212
+ function showMenu() {
213
+ menuSheet.classList.remove('opacity-0', 'pointer-events-none');
214
+ menuSheet.querySelector('.bottom-sheet').classList.remove('hidden', 'translate-y-full');
215
+ vibrate(20);
216
+ }
217
+
218
+ function hideMenu() {
219
+ menuSheet.classList.add('opacity-0', 'pointer-events-none');
220
+ menuSheet.querySelector('.bottom-sheet').classList.add('translate-y-full');
221
+ setTimeout(() => {
222
+ menuSheet.querySelector('.bottom-sheet').classList.add('hidden');
223
+ }, 300);
224
+ }
225
+
226
+ function createMessageElement(text, isUser = false) {
227
+ const wrapper = document.createElement('div');
228
+ wrapper.className = `flex ${isUser ? 'justify-end' : 'justify-start'} message-bubble swipeable`;
229
+
230
+ const content = document.createElement('div');
231
+ content.className = `max-w-[85%] px-3.5 py-2.5 rounded-2xl text-sm ${
232
+ isUser
233
+ ? 'gradient-bg text-white rounded-br-sm'
234
+ : 'bg-white border border-gray-100 text-gray-800 rounded-bl-sm shadow-sm'
235
+ }`;
236
+
237
+ content.innerHTML = text
238
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
239
+ .replace(/\n/g, '<br>');
240
+
241
+ wrapper.appendChild(content);
242
+ return wrapper;
243
+ }
244
+
245
+ function createTypingIndicator() {
246
+ const wrapper = document.createElement('div');
247
+ wrapper.id = 'typingIndicator';
248
+ wrapper.className = 'flex justify-start message-bubble';
249
+
250
+ const content = document.createElement('div');
251
+ content.className = 'bg-white border border-gray-100 px-3.5 py-3 rounded-2xl rounded-bl-sm shadow-sm';
252
+ content.innerHTML = `
253
+ <div class="flex gap-1 items-center h-4">
254
+ <span class="typing-dot w-1.5 h-1.5 bg-purple-400 rounded-full"></span>
255
+ <span class="typing-dot w-1.5 h-1.5 bg-purple-400 rounded-full"></span>
256
+ <span class="typing-dot w-1.5 h-1.5 bg-purple-400 rounded-full"></span>
257
+ </div>
258
+ `;
259
+
260
+ wrapper.appendChild(content);
261
+ return wrapper;
262
+ }
263
+
264
+ // ==================== MESSAGE PROCESSING ====================
265
+ function processMessage(message) {
266
+ const lowerMessage = message.toLowerCase();
267
+
268
+ for (const category of Object.values(knowledgeBase)) {
269
+ const matched = category.patterns.some(p => lowerMessage.includes(p));
270
+
271
+ if (matched) {
272
+ if (category === knowledgeBase.calculator) {
273
+ const result = category.responses(message);
274
+ if (result) return result;
275
+ }
276
+ if (category === knowledgeBase.capital) {
277
+ for (const [country, response] of Object.entries(category.responses)) {
278
+ if (lowerMessage.includes(country)) return response;
279
+ }
280
+ return 'Diga o país! 🇫🇷🇧🇷🇯🇵🇺🇸🇦🇷🇵🇹🇪🇸🇮🇹🇩🇪🇨🇦';
281
+ }
282
+ if (typeof category.responses === 'function') {
283
+ return category.responses();
284
+ }
285
+ return category.responses[Math.floor(Math.random() * category.responses.length)];
286
+ }
287
+ }
288
+
289
+ return [
290
+ '🤔 Conte-me mais!',
291
+ '💭 Pode explicar melhor?',
292
+ '🌟 Interessante! O que mais?',
293
+ '💡 Entendi! E agora?'
294
+ ][Math.floor(Math.random() * 4)];
295
+ }
296
+
297
+ async function sendMessage(message) {
298
+ if (!message.trim() || isTyping) return;
299
+
300
+ // Hide quick actions
301
+ document.getElementById('quickActions').classList.add('hidden');
302
+
303
+ // Add user message
304
+ chatContainer.appendChild(createMessageElement(message, true));
305
+ chatContainer.scrollTop = chatContainer.scrollHeight;
306
+ messageHistory.push({ role: 'user', content: message });
307
+
308
+ // Reset input
309
+ messageInput.value = '';
310
+ messageInput.style.height = 'auto';
311
+ checkInput();
312
+
313
+ // Typing indicator
314
+ isTyping = true;
315
+ sendBtn.disabled = true;
316
+ const typing = createTypingIndicator();
317
+ chatContainer.appendChild(typing);
318
+ chatContainer.scrollTop = chatContainer.scrollHeight;
319
+
320
+ // Simulate thinking time
321
+ const delay = 600 + Math.random() * 600;
322
+ await new Promise(r => setTimeout(r, delay));
323
+
324
+ // Remove typing, show response
325
+ typing.remove();
326
+ const response = processMessage(message);
327
+ chatContainer.appendChild(createMessageElement(response, false));
328
+ chatContainer.scrollTop = chatContainer.scrollHeight;
329
+ messageHistory.push({ role: 'assistant', content: response });
330
+
331
+ // Haptic feedback
332
+ vibrate(30);
333
+
334
+ // Try to speak response (optional)
335
+ if (isNative && 'speechSynthesis' in window) {
336
+ const utter = new SpeechSynthesisUtterance(response.replace(/[^\w\sáéíóúâêîôûãõàèìòùçÁÉÍÓÚÂÊÎÔÛÃÕÀÈÌÒÙÇ]/g, ''));
337
+ utter.lang = 'pt-BR';
338
+ utter.rate = 1.1;
339
+ speechSynthesis.speak(utter);
340
+ }
341
+
342
+ isTyping = false;
343
+ checkInput();
344
+ }
345
+
346
+ function sendSuggestion(text) {
347
+ messageInput.value = text;
348
+ checkInput();
349
+ setTimeout(() => messageForm.dispatchEvent(new Event('submit')), 100);
350
+ }
351
+
352
+ // ==================== VOICE INPUT ====================
353
+ function startVoiceInput() {
354
+ if (isNative) {
355
+ // Use native speech recognition
356
+ showVoiceModal();
357
+ // Capacitor speech recognition would go here
358
+ // For now, use Web API as fallback
359
+ }
360
+
361
+ if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
362
+ showToast('Voz não suportada no seu dispositivo');
363
+ return;
364
+ }
365
+
366
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
367
+ recognition = new SpeechRecognition();
368
+ recognition.lang = 'pt-BR';
369
+ recognition.continuous = false;
370
+ recognition.interimResults = false;
371
+
372
+ showVoiceModal();
373
+
374
+ recognition.onresult = (e) => {
375
+ const transcript = e.results[0][0].transcript;
376
+ voiceText.textContent = transcript;
377
+ setTimeout(() => {
378
+ stopVoiceInput();
379
+ messageInput.value = transcript;
380
+ checkInput();
381
+ messageForm.dispatchEvent(new Event('submit'));
382
+ }, 400);
383
+ };
384
+
385
+ recognition.onerror = () => {
386
+ voiceText.textContent = '❌ Não entendi';
387
+ setTimeout(stopVoiceInput, 800);
388
+ };
389
+
390
+ recognition.onend = () => {
391
+ if (voiceText.textContent === 'Ouvindo...') {
392
+ stopVoiceInput();
393
+ }
394
+ };
395
+
396
+ recognition.start();
397
+ }
398
+
399
+ function showVoiceModal() {
400
+ voiceModal.classList.remove('opacity-0', 'pointer-events-none');
401
+ voiceModal.querySelector('div').classList.remove('scale-90');
402
+ voiceText.textContent = 'Ouvindo...';
403
+ vibrate(20);
404
+ }
405
+
406
+ function stopVoiceInput() {
407
+ if (recognition) recognition.stop();
408
+ voiceModal.classList.add('opacity-0', 'pointer-events-none');
409
+ voiceModal.querySelector('div').classList.add('scale-90');
410
+ }
411
+
412
+ // ==================== MENU ACTIONS ====================
413
+ async function clearChat() {
414
+ if (confirm('Limpar toda a conversa?')) {
415
+ const welcome = chatContainer.children[0];
416
+ const suggestions = chatContainer.children[1];
417
+ chatContainer.innerHTML = '';
418
+ chatContainer.append(welcome, suggestions);
419
+ messageHistory = [];
420
+ vibrate([30, 50, 30]);
421
+ }
422
+ }
423
+
424
+ function exportChat() {
425
+ const text = messageHistory.map(m => `${m.role}: ${m.content}`).join('\n\n');
426
+ const blob = new Blob([text], { type: 'text/plain' });
427
+ const url = URL.createObjectURL(blob);
428
+
429
+ const a = document.createElement('a');
430
+ a.href = url;
431
+ a.download = `conversa-nova-${Date.now()}.txt`;
432
+ a.click();
433
+
434
+ showToast('Conversa exportada! 📄');
435
+ vibrate(20);
436
+ }
437
+
438
+ function toggleDarkMode(force = null) {
439
+ isDarkMode = force !== null ? force : !isDarkMode;
440
+ document.documentElement.classList.toggle('dark', isDarkMode);
441
+ setStorage('darkMode', isDarkMode.toString());
442
+ vibrate(15);
443
+ }
444
+
445
+ function showAbout() {
446
+ alert('🤖 Nova Assistant v1.0\n\nSua assistente virtual inteligente feita com 💜\n\n© 2024');
447
+ }
448
+
449
+ // ==================== UTILITIES ====================
450
+ function vibrate(pattern) {
451
+ if (isNative && navigator.vibrate) {
452
+ navigator.vibrate(pattern);
453
+ }
454
+ }
455
+
456
+ function showToast(message) {
457
+ if (isNative && Toast) {
458
+ Toast.show({ text: message, duration: 'short' });
459
+ } else {
460
+ // Web fallback
461
+ const toast = document.createElement('div');
462
+ toast.className = 'fixed bottom-32 left-1/2 -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-full text-sm z-50 animate-bounce';
463
+ toast.textContent = message;
464
+ document.body.appendChild(toast);
465
+ setTimeout(() => toast.remove(), 2000);
466
+ }
467
+ }
468
+
469
+ async function setStorage(key, value) {
470
+ if (isNative && Preferences) {
471
+ await Preferences.set({ key, value });
472
+ } else {
473
+ localStorage.setItem(key, value);
474
+ }
475
+ }
476
+
477
+ async function getStorage(key) {
478
+ if (isNative && Preferences) {
479
+ const { value } = await Preferences.get({ key });
480
+ return value;
481
+ }
482
+ return localStorage.getItem(key);
483
+ }
484
+
485
+ // ==================== EVENT LISTENERS ====================
486
+ messageForm.addEventListener('submit', (e) => {
487
+ e.preventDefault();
488
+ sendMessage(messageInput.value);
489
+ });
490
+
491
+ // Init
492
+ initApp();