machinaos 0.0.1

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.
Files changed (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,465 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Received Messages - WhatsApp Controller{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="max-w-6xl mx-auto">
7
+ <div class="bg-white rounded-lg shadow-md p-6">
8
+ <div class="flex justify-between items-center mb-6">
9
+ <h2 class="text-2xl font-bold text-gray-800">Received Messages</h2>
10
+ <div class="flex items-center space-x-4">
11
+ <button onclick="loadMessages()" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
12
+ Refresh
13
+ </button>
14
+ <button onclick="clearMessages()" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition">
15
+ Clear Display
16
+ </button>
17
+ </div>
18
+ </div>
19
+
20
+ <!-- Messages count -->
21
+ <div class="mb-4 p-3 bg-gray-100 rounded">
22
+ <p class="text-sm text-gray-600">
23
+ <span class="font-semibold">Total Messages:</span>
24
+ <span id="message-count">0</span>
25
+ </p>
26
+ </div>
27
+
28
+ <!-- Messages container -->
29
+ <div id="messages-container" class="space-y-4 max-h-[600px] overflow-y-auto p-4 bg-gray-50 rounded">
30
+ <p class="text-gray-500 text-center py-8">No messages yet. Waiting for incoming messages...</p>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- Media Viewer Modal -->
36
+ <div id="media-modal" class="fixed inset-0 bg-black bg-opacity-75 hidden z-50 flex items-center justify-center">
37
+ <div class="bg-white rounded-lg max-w-4xl max-h-[90vh] w-full m-4 overflow-hidden">
38
+ <div class="flex justify-between items-center p-4 border-b">
39
+ <h3 class="text-lg font-semibold" id="media-modal-title">Media Viewer</h3>
40
+ <button onclick="closeMediaModal()" class="text-gray-500 hover:text-gray-700">
41
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
42
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
43
+ </svg>
44
+ </button>
45
+ </div>
46
+ <div id="media-modal-content" class="p-4 overflow-auto max-h-[calc(90vh-80px)] flex items-center justify-center bg-gray-100">
47
+ <p class="text-gray-500">Loading...</p>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ {% endblock %}
53
+
54
+ {% block scripts %}
55
+ <script>
56
+ let messages = [];
57
+ let groupsCache = {}; // Cache group names by JID
58
+ const messagesContainer = document.getElementById('messages-container');
59
+ const messageCount = document.getElementById('message-count');
60
+
61
+ // Load groups cache on page load
62
+ loadGroupsCache();
63
+
64
+ // Subscribe to messages via WebSocket
65
+ socket.emit('subscribe_messages');
66
+
67
+ // Load groups into cache for name lookup
68
+ async function loadGroupsCache() {
69
+ try {
70
+ const response = await fetch('/api/groups');
71
+ const data = await response.json();
72
+ if (data.success && data.data) {
73
+ data.data.forEach(group => {
74
+ groupsCache[group.jid] = group.name;
75
+ });
76
+ console.log(`Cached ${Object.keys(groupsCache).length} group names`);
77
+ }
78
+ } catch (error) {
79
+ console.error('Failed to load groups cache:', error);
80
+ }
81
+ }
82
+
83
+ // Get group name from cache
84
+ function getGroupName(jid) {
85
+ return groupsCache[jid] || null;
86
+ }
87
+
88
+ // Listen for new messages via WebSocket
89
+ socket.on('whatsapp_event', function(data) {
90
+ console.log('Received WhatsApp event:', data);
91
+
92
+ if (data.type === 'message_received') {
93
+ // Add new message to the beginning
94
+ messages.unshift(data);
95
+ renderMessages();
96
+
97
+ // Show notification
98
+ showNotification('New message received from ' + formatSender(data.data.sender));
99
+ }
100
+ });
101
+
102
+ // Refresh just re-renders (messages come via WebSocket)
103
+ function loadMessages() {
104
+ renderMessages();
105
+ console.log('Messages display refreshed');
106
+ }
107
+
108
+ // Render all messages
109
+ function renderMessages() {
110
+ if (messages.length === 0) {
111
+ messagesContainer.innerHTML = `
112
+ <div class="text-center py-8">
113
+ <p class="text-gray-500 mb-2">No messages yet.</p>
114
+ <p class="text-sm text-gray-400">Messages will appear here in real-time as they are received.</p>
115
+ <p class="text-xs text-gray-400 mt-2">Make sure WhatsApp is connected.</p>
116
+ </div>
117
+ `;
118
+ messageCount.textContent = '0';
119
+ return;
120
+ }
121
+
122
+ messageCount.textContent = messages.length;
123
+
124
+ messagesContainer.innerHTML = messages.map(msg => {
125
+ const data = msg.data || {};
126
+ return createMessageCard(data);
127
+ }).join('');
128
+ }
129
+
130
+ // Create message card HTML
131
+ function createMessageCard(data) {
132
+ const messageType = data.message_type || 'unknown';
133
+ const timestamp = formatTimestamp(data.timestamp);
134
+ const sender = formatSender(data.sender);
135
+ const isFromMe = data.is_from_me;
136
+ const isGroup = data.is_group || false;
137
+ const isForwarded = data.is_forwarded || false;
138
+ const forwardingScore = data.forwarding_score || 0;
139
+ const chatId = data.chat_id || '';
140
+
141
+ // Get group name if it's a group message
142
+ let groupName = null;
143
+ if (isGroup && chatId) {
144
+ groupName = getGroupName(chatId);
145
+ }
146
+
147
+ let contentHtml = '';
148
+ let typeIcon = '';
149
+ let typeColor = 'bg-blue-100 text-blue-800';
150
+
151
+ // Render based on message type
152
+ switch(messageType) {
153
+ case 'text':
154
+ typeIcon = '&#128172;'; // Speech bubble
155
+ contentHtml = `<p class="text-gray-800">${escapeHtml(data.text || '')}</p>`;
156
+ break;
157
+
158
+ case 'image':
159
+ typeIcon = '&#128247;'; // Camera
160
+ typeColor = 'bg-purple-100 text-purple-800';
161
+ const imageInfo = data.image || {};
162
+ contentHtml = `
163
+ <div class="flex items-center space-x-3 mb-2">
164
+ <svg class="w-16 h-16 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
165
+ <path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"></path>
166
+ </svg>
167
+ <div class="flex-1">
168
+ <p class="text-sm text-gray-600">Image message</p>
169
+ <p class="text-xs text-gray-500">${imageInfo.mime_type || 'image'} - ${formatFileSize(imageInfo.file_length || 0)}</p>
170
+ ${data.caption ? `<p class="text-gray-800 italic mt-1">${escapeHtml(data.caption)}</p>` : ''}
171
+ </div>
172
+ </div>
173
+ ${imageInfo.url ? `<button onclick="viewMedia('${escapeHtml(data.message_id)}', 'image', '${escapeHtml(imageInfo.url)}')" class="mt-2 px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600 text-sm">View Image</button>` : ''}
174
+ `;
175
+ break;
176
+
177
+ case 'video':
178
+ typeIcon = '&#127909;'; // Video camera
179
+ typeColor = 'bg-pink-100 text-pink-800';
180
+ const videoInfo = data.video || {};
181
+ contentHtml = `
182
+ <div class="flex items-center space-x-3 mb-2">
183
+ <svg class="w-16 h-16 text-pink-400" fill="currentColor" viewBox="0 0 20 20">
184
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path>
185
+ </svg>
186
+ <div class="flex-1">
187
+ <p class="text-sm text-gray-600">Video message</p>
188
+ <p class="text-xs text-gray-500">${videoInfo.mime_type || 'video'} - ${formatFileSize(videoInfo.file_length || 0)} - ${videoInfo.seconds || 0}s</p>
189
+ ${data.caption ? `<p class="text-gray-800 italic mt-1">${escapeHtml(data.caption)}</p>` : ''}
190
+ </div>
191
+ </div>
192
+ ${videoInfo.url ? `<button onclick="viewMedia('${escapeHtml(data.message_id)}', 'video', '${escapeHtml(videoInfo.url)}')" class="mt-2 px-4 py-2 bg-pink-500 text-white rounded hover:bg-pink-600 text-sm">View Video</button>` : ''}
193
+ `;
194
+ break;
195
+
196
+ case 'audio':
197
+ typeIcon = '&#127925;'; // Musical note
198
+ typeColor = 'bg-green-100 text-green-800';
199
+ contentHtml = `<p class="text-sm text-gray-600">Audio message</p>`;
200
+ break;
201
+
202
+ case 'document':
203
+ typeIcon = '&#128196;'; // Document
204
+ typeColor = 'bg-yellow-100 text-yellow-800';
205
+ contentHtml = `
206
+ <p class="text-sm text-gray-600 mb-2">Document</p>
207
+ ${data.file_name ? `<p class="text-gray-700 font-mono text-xs">${escapeHtml(data.file_name)}</p>` : ''}
208
+ `;
209
+ break;
210
+
211
+ case 'sticker':
212
+ typeIcon = '&#128526;'; // Sticker emoji
213
+ typeColor = 'bg-orange-100 text-orange-800';
214
+ contentHtml = `<p class="text-sm text-gray-600">Sticker</p>`;
215
+ break;
216
+
217
+ case 'location':
218
+ typeIcon = '&#128205;'; // Pin
219
+ typeColor = 'bg-red-100 text-red-800';
220
+ contentHtml = `
221
+ <p class="text-sm text-gray-600 mb-2">Location</p>
222
+ ${data.location ? `
223
+ <p class="text-gray-800">
224
+ <span class="font-semibold">${escapeHtml(data.location.name || 'Unknown')}</span><br>
225
+ <span class="text-sm text-gray-600">${escapeHtml(data.location.address || '')}</span><br>
226
+ <span class="text-xs text-gray-500">Lat: ${data.location.latitude}, Lon: ${data.location.longitude}</span>
227
+ </p>
228
+ ` : ''}
229
+ `;
230
+ break;
231
+
232
+ case 'contact':
233
+ typeIcon = '&#128100;'; // Contact
234
+ typeColor = 'bg-indigo-100 text-indigo-800';
235
+ contentHtml = `
236
+ <p class="text-sm text-gray-600 mb-2">Contact Card</p>
237
+ ${data.contact ? `
238
+ <p class="text-gray-800 font-semibold">${escapeHtml(data.contact.display_name || 'Unknown')}</p>
239
+ ` : ''}
240
+ `;
241
+ break;
242
+
243
+ case 'contacts':
244
+ typeIcon = '&#128101;'; // Contacts
245
+ typeColor = 'bg-teal-100 text-teal-800';
246
+ contentHtml = `<p class="text-sm text-gray-600">Multiple contacts (${data.contacts_count || 0})</p>`;
247
+ break;
248
+
249
+ default:
250
+ typeIcon = '&#10067;'; // Question mark
251
+ typeColor = 'bg-gray-100 text-gray-800';
252
+ contentHtml = `<p class="text-sm text-gray-600">Unknown message type: ${messageType}</p>`;
253
+ }
254
+
255
+ // Add quoted message if exists
256
+ let quotedHtml = '';
257
+ if (data.quoted) {
258
+ quotedHtml = `
259
+ <div class="mt-2 p-2 bg-gray-100 border-l-4 border-gray-400 rounded">
260
+ <p class="text-xs text-gray-600">Replying to:</p>
261
+ <p class="text-sm text-gray-700 italic">${escapeHtml(data.quoted.content || '')}</p>
262
+ </div>
263
+ `;
264
+ }
265
+
266
+ // Build group info line
267
+ let groupInfoHtml = '';
268
+ if (isGroup) {
269
+ const displayGroupName = groupName || chatId.replace('@g.us', '');
270
+ groupInfoHtml = `<p class="text-xs text-purple-600">in <span class="font-medium">${escapeHtml(displayGroupName)}</span></p>`;
271
+ }
272
+
273
+ // Build forwarded indicator
274
+ let forwardedHtml = '';
275
+ if (isForwarded) {
276
+ const forwardedLabel = forwardingScore > 4 ? 'Forwarded many times' : 'Forwarded';
277
+ forwardedHtml = `<span class="inline-block px-2 py-1 rounded text-xs font-medium bg-gray-200 text-gray-700 ml-1" title="Forwarding score: ${forwardingScore}">&#8631; ${forwardedLabel}</span>`;
278
+ }
279
+
280
+ return `
281
+ <div class="bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition ${isFromMe ? 'border-l-4 border-green-500' : isGroup ? 'border-l-4 border-purple-500' : 'border-l-4 border-blue-500'}">
282
+ <div class="flex justify-between items-start mb-2">
283
+ <div class="flex items-center space-x-2">
284
+ <span class="text-2xl">${typeIcon}</span>
285
+ <div>
286
+ <p class="font-semibold text-gray-800">${sender}</p>
287
+ ${groupInfoHtml}
288
+ <span class="inline-block px-2 py-1 rounded text-xs font-medium ${typeColor}">${messageType}</span>${forwardedHtml}
289
+ </div>
290
+ </div>
291
+ <div class="text-right">
292
+ <p class="text-xs text-gray-500">${timestamp}</p>
293
+ ${isFromMe ? '<span class="text-xs text-green-600 font-medium">Sent by me</span>' : ''}
294
+ ${isGroup && !isFromMe ? '<span class="text-xs text-purple-600 font-medium">Group message</span>' : ''}
295
+ </div>
296
+ </div>
297
+ <div class="mt-2">
298
+ ${contentHtml}
299
+ ${quotedHtml}
300
+ </div>
301
+ <div class="mt-2 pt-2 border-t text-xs text-gray-400 flex justify-between items-center">
302
+ <span class="font-mono">ID: ${escapeHtml(data.message_id || 'N/A')}</span>
303
+ ${!isFromMe ? `<button onclick="markAsRead('${escapeHtml(data.message_id)}', '${escapeHtml(chatId)}', '${escapeHtml(data.sender || '')}')" class="px-2 py-1 bg-blue-500 text-white rounded text-xs hover:bg-blue-600">Mark Read</button>` : ''}
304
+ </div>
305
+ </div>
306
+ `;
307
+ }
308
+
309
+ // Format timestamp
310
+ function formatTimestamp(timestamp) {
311
+ if (!timestamp) return 'Unknown time';
312
+ // Handle both ISO 8601 strings and Unix timestamps
313
+ const date = typeof timestamp === 'string' ? new Date(timestamp) : new Date(timestamp * 1000);
314
+ if (isNaN(date.getTime())) return 'Invalid Date';
315
+ return date.toLocaleString();
316
+ }
317
+
318
+ // Format sender
319
+ function formatSender(sender) {
320
+ if (!sender) return 'Unknown';
321
+ // Remove @s.whatsapp.net suffix
322
+ return sender.replace('@s.whatsapp.net', '').replace('@c.us', '');
323
+ }
324
+
325
+ // Escape HTML
326
+ function escapeHtml(text) {
327
+ const div = document.createElement('div');
328
+ div.textContent = text;
329
+ return div.innerHTML;
330
+ }
331
+
332
+ // Clear messages display
333
+ function clearMessages() {
334
+ if (confirm('Clear all displayed messages? (This will not delete them from storage)')) {
335
+ messages = [];
336
+ renderMessages();
337
+ }
338
+ }
339
+
340
+ // Format file size
341
+ function formatFileSize(bytes) {
342
+ if (!bytes || bytes === 0) return '0 B';
343
+ const k = 1024;
344
+ const sizes = ['B', 'KB', 'MB', 'GB'];
345
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
346
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
347
+ }
348
+
349
+ // View media function
350
+ function viewMedia(messageId, mediaType, url) {
351
+ console.log('View media:', messageId, mediaType, url);
352
+
353
+ const modal = document.getElementById('media-modal');
354
+ const modalTitle = document.getElementById('media-modal-title');
355
+ const modalContent = document.getElementById('media-modal-content');
356
+
357
+ // Show modal
358
+ modal.classList.remove('hidden');
359
+
360
+ // Set title
361
+ modalTitle.textContent = `Viewing ${mediaType}`;
362
+
363
+ // Show loading
364
+ modalContent.innerHTML = '<p class="text-gray-500">Downloading and decrypting media...</p>';
365
+
366
+ // Fetch media via Flask RPC endpoint (returns JSON with base64 data)
367
+ fetch(`/api/media/${messageId}`)
368
+ .then(response => response.json())
369
+ .then(result => {
370
+ if (!result.success) {
371
+ throw new Error(result.error || 'Failed to download media');
372
+ }
373
+
374
+ const mediaData = result.data;
375
+ const base64Data = mediaData.data;
376
+ const mimeType = mediaData.mime_type || 'application/octet-stream';
377
+
378
+ // Convert base64 to blob
379
+ const byteCharacters = atob(base64Data);
380
+ const byteNumbers = new Array(byteCharacters.length);
381
+ for (let i = 0; i < byteCharacters.length; i++) {
382
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
383
+ }
384
+ const byteArray = new Uint8Array(byteNumbers);
385
+ const blob = new Blob([byteArray], { type: mimeType });
386
+ const blobUrl = URL.createObjectURL(blob);
387
+
388
+ if (mediaType === 'image') {
389
+ modalContent.innerHTML = `
390
+ <img src="${blobUrl}" class="max-w-full max-h-full object-contain" alt="WhatsApp Image">
391
+ `;
392
+ } else if (mediaType === 'video') {
393
+ modalContent.innerHTML = `
394
+ <video controls class="max-w-full max-h-full" autoplay>
395
+ <source src="${blobUrl}" type="${mimeType}">
396
+ Your browser does not support the video tag.
397
+ </video>
398
+ `;
399
+ } else if (mediaType === 'audio') {
400
+ modalContent.innerHTML = `
401
+ <audio controls autoplay class="w-full">
402
+ <source src="${blobUrl}" type="${mimeType}">
403
+ Your browser does not support the audio tag.
404
+ </audio>
405
+ `;
406
+ } else {
407
+ modalContent.innerHTML = `
408
+ <div class="text-center">
409
+ <p class="text-gray-600 mb-4">Media loaded successfully</p>
410
+ <a href="${blobUrl}" download class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
411
+ Download ${mediaType}
412
+ </a>
413
+ </div>
414
+ `;
415
+ }
416
+ })
417
+ .catch(error => {
418
+ console.error('Error loading media:', error);
419
+ modalContent.innerHTML = `
420
+ <div class="text-center text-red-600">
421
+ <p>Failed to load media: ${error.message}</p>
422
+ <p class="text-sm text-gray-500 mt-2">The message may have expired or been deleted.</p>
423
+ </div>
424
+ `;
425
+ });
426
+ }
427
+
428
+ // Close media modal
429
+ function closeMediaModal() {
430
+ const modal = document.getElementById('media-modal');
431
+ modal.classList.add('hidden');
432
+
433
+ // Clear content to stop videos/audio
434
+ const modalContent = document.getElementById('media-modal-content');
435
+ modalContent.innerHTML = '<p class="text-gray-500">Loading...</p>';
436
+ }
437
+
438
+ // Show notification
439
+ function showNotification(message, type = 'info') {
440
+ // Simple console notification for now
441
+ console.log(`[${type.toUpperCase()}] ${message}`);
442
+
443
+ // Could add a toast notification here in the future
444
+ }
445
+
446
+ // Mark message as read via WebSocket
447
+ function markAsRead(messageId, chatJid, senderJid) {
448
+ console.log('Marking as read:', messageId, chatJid, senderJid);
449
+ socket.emit('mark_read', {
450
+ message_ids: [messageId],
451
+ chat_jid: chatJid,
452
+ sender_jid: senderJid || null
453
+ });
454
+ }
455
+
456
+ // Listen for mark_read result
457
+ socket.on('mark_read_result', function(data) {
458
+ if (data.success) {
459
+ showNotification('Message marked as read');
460
+ } else {
461
+ showNotification('Failed to mark as read: ' + data.error, 'error');
462
+ }
463
+ });
464
+ </script>
465
+ {% endblock %}