@mazees/mark 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +209 -0
  3. package/bin/mark.js +27 -0
  4. package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
  5. package/out/renderer/assets/index-G2fADWO-.js +4768 -0
  6. package/out/renderer/assets/index-f3mVse3c.css +1 -0
  7. package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
  8. package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
  9. package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
  10. package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
  11. package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
  12. package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
  13. package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
  14. package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
  15. package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
  16. package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  17. package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  18. package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  19. package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  20. package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  21. package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  22. package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  23. package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  24. package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
  25. package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
  26. package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
  27. package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
  28. package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
  29. package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
  30. package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
  31. package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
  32. package/out/renderer/favicon.ico +0 -0
  33. package/out/renderer/icon-256.png +0 -0
  34. package/out/renderer/icon.svg +1 -0
  35. package/out/renderer/index.html +25 -0
  36. package/out/renderer/manifest.json +25 -0
  37. package/out/renderer/robot-simulator.html +1640 -0
  38. package/package.json +106 -0
  39. package/src/cli/index.js +332 -0
  40. package/src/cli/monitor.js +413 -0
  41. package/src/cli/theme.js +347 -0
  42. package/src/launcher/MarkTray.cs +227 -0
  43. package/src/main/browser-agent.js +1132 -0
  44. package/src/main/git-service.js +90 -0
  45. package/src/main/google/google-calendar.js +69 -0
  46. package/src/main/google/google-drive.js +263 -0
  47. package/src/main/google/google-gmail.js +131 -0
  48. package/src/main/google/google-service.js +241 -0
  49. package/src/main/node-tools.js +23 -0
  50. package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
  51. package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
  52. package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
  53. package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
  54. package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
  55. package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
  56. package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
  57. package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
  58. package/src/main/plugins/plugin-loader.js +306 -0
  59. package/src/main/skills/skill-manager.js +265 -0
  60. package/src/main/syntax-validator.js +455 -0
  61. package/src/main/task-daemon.js +117 -0
  62. package/src/main/telegram/telegram-service.js +835 -0
  63. package/src/main/tools/browser-tools.js +516 -0
  64. package/src/main/tools/file-tools.js +1126 -0
  65. package/src/main/tools/git-tools.js +79 -0
  66. package/src/main/tools/google-tools.js +566 -0
  67. package/src/main/tools/index.js +36 -0
  68. package/src/main/tools/media-tools.js +42 -0
  69. package/src/main/tools/system-tools.js +295 -0
  70. package/src/main/tools/task-tools.js +73 -0
  71. package/src/main/tools/telegram-tools.js +101 -0
  72. package/src/renderer/index.html +24 -0
  73. package/src/renderer/public/favicon.ico +0 -0
  74. package/src/renderer/public/icon-256.png +0 -0
  75. package/src/renderer/public/icon.svg +1 -0
  76. package/src/renderer/public/manifest.json +25 -0
  77. package/src/renderer/public/robot-simulator.html +1640 -0
  78. package/src/renderer/src/App.jsx +304 -0
  79. package/src/renderer/src/api/ai/awareness.js +294 -0
  80. package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
  81. package/src/renderer/src/api/ai/contextCompactor.js +129 -0
  82. package/src/renderer/src/api/ai/contextManager.js +1130 -0
  83. package/src/renderer/src/api/ai/core.js +167 -0
  84. package/src/renderer/src/api/ai/index.js +4 -0
  85. package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
  86. package/src/renderer/src/api/ai/persona.js +115 -0
  87. package/src/renderer/src/api/ai/planning.js +317 -0
  88. package/src/renderer/src/api/ai/relationship.js +177 -0
  89. package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
  90. package/src/renderer/src/api/ai/tools.js +167 -0
  91. package/src/renderer/src/api/ai/utils.js +359 -0
  92. package/src/renderer/src/api/db.js +1013 -0
  93. package/src/renderer/src/api/oramaStore.js +592 -0
  94. package/src/renderer/src/api/ragPipeline.js +87 -0
  95. package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
  96. package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
  97. package/src/renderer/src/api/subagent/subagentStore.js +173 -0
  98. package/src/renderer/src/api/taskExecutor.js +18 -0
  99. package/src/renderer/src/api/taskStore.js +315 -0
  100. package/src/renderer/src/api/tools/core-tools.js +707 -0
  101. package/src/renderer/src/api/tools/index.js +151 -0
  102. package/src/renderer/src/api/turnPairMigrator.js +200 -0
  103. package/src/renderer/src/api/vectorMemory.js +136 -0
  104. package/src/renderer/src/api/wakeWord.js +101 -0
  105. package/src/renderer/src/api/web-bridge.js +885 -0
  106. package/src/renderer/src/api/webSpeech.js +178 -0
  107. package/src/renderer/src/api/workspaceRag.js +76 -0
  108. package/src/renderer/src/assets/electron.svg +10 -0
  109. package/src/renderer/src/assets/icon.svg +1 -0
  110. package/src/renderer/src/assets/main.css +589 -0
  111. package/src/renderer/src/assets/music-cover.png +0 -0
  112. package/src/renderer/src/assets/wavy-lines.svg +25 -0
  113. package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
  114. package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
  115. package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
  116. package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
  117. package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
  118. package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
  119. package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
  120. package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
  121. package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
  122. package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
  123. package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
  124. package/src/renderer/src/components/Chat/index.js +11 -0
  125. package/src/renderer/src/components/ChatList.jsx +434 -0
  126. package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
  127. package/src/renderer/src/components/Versions.jsx +15 -0
  128. package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
  129. package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
  130. package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
  131. package/src/renderer/src/components/config/CameraSection.jsx +100 -0
  132. package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
  133. package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
  134. package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
  135. package/src/renderer/src/components/config/index.js +5 -0
  136. package/src/renderer/src/components/core/Avatar.jsx +2197 -0
  137. package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
  138. package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
  139. package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
  140. package/src/renderer/src/components/core/HoloCard.jsx +64 -0
  141. package/src/renderer/src/components/core/InputBar.jsx +1346 -0
  142. package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
  143. package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
  144. package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
  145. package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
  146. package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
  147. package/src/renderer/src/components/core/native-skills.js +43 -0
  148. package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
  149. package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
  150. package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
  151. package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
  152. package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
  153. package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
  154. package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
  155. package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
  156. package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
  157. package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
  158. package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
  159. package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
  160. package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
  161. package/src/renderer/src/contexts/ChatContext.jsx +61 -0
  162. package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
  163. package/src/renderer/src/data/faqData.js +202 -0
  164. package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
  165. package/src/renderer/src/hooks/agent/index.js +4 -0
  166. package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
  167. package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
  168. package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
  169. package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
  170. package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
  171. package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
  172. package/src/renderer/src/hooks/useAwareness.js +289 -0
  173. package/src/renderer/src/hooks/useChatArchiver.js +52 -0
  174. package/src/renderer/src/hooks/useConfirm.jsx +68 -0
  175. package/src/renderer/src/hooks/useMarkAgent.js +454 -0
  176. package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
  177. package/src/renderer/src/hooks/useVAD.js +511 -0
  178. package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
  179. package/src/renderer/src/main.jsx +12 -0
  180. package/src/renderer/src/pages/ChatStudio.jsx +922 -0
  181. package/src/renderer/src/pages/Configuration.jsx +360 -0
  182. package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
  183. package/src/renderer/src/pages/Guidebook.jsx +917 -0
  184. package/src/renderer/src/pages/Knowledge.jsx +241 -0
  185. package/src/renderer/src/pages/MarkHome.jsx +491 -0
  186. package/src/renderer/src/pages/NeuralCore.jsx +786 -0
  187. package/src/renderer/src/pages/Plugins.jsx +630 -0
  188. package/src/renderer/src/pages/SkillEditor.jsx +380 -0
  189. package/src/renderer/src/pages/Skills.jsx +219 -0
  190. package/src/renderer/src/pages/Subagents.jsx +342 -0
  191. package/src/renderer/src/pages/TelegramBot.jsx +343 -0
  192. package/src/renderer/src/utils/chatContentParser.js +203 -0
  193. package/src/renderer/src/utils/toolClusters.js +201 -0
  194. package/src/server/bin/sha3_wasm_bg.wasm +0 -0
  195. package/src/server/config-manager.js +57 -0
  196. package/src/server/index.js +195 -0
  197. package/src/server/launcher.js +87 -0
  198. package/src/server/memory/db-store.js +758 -0
  199. package/src/server/memory/orama-store.js +177 -0
  200. package/src/server/memory/vector-engine.js +109 -0
  201. package/src/server/routes/ai.routes.js +280 -0
  202. package/src/server/routes/awareness.routes.js +62 -0
  203. package/src/server/routes/chat.routes.js +61 -0
  204. package/src/server/routes/config.routes.js +41 -0
  205. package/src/server/routes/index.js +35 -0
  206. package/src/server/routes/integrations.routes.js +116 -0
  207. package/src/server/routes/memory.routes.js +256 -0
  208. package/src/server/routes/plugins.routes.js +72 -0
  209. package/src/server/routes/skills.routes.js +139 -0
  210. package/src/server/routes/subagents.routes.js +70 -0
  211. package/src/server/routes/tasks.routes.js +175 -0
  212. package/src/server/routes/workspace.routes.js +127 -0
  213. package/src/server/services/ai/ai-utils.js +161 -0
  214. package/src/server/services/ai/openai-provider.js +757 -0
  215. package/src/server/services/ai/web-provider.js +729 -0
  216. package/src/server/services/ai-bridge.js +46 -0
  217. package/src/server/services/deepseek-web.js +956 -0
  218. package/src/server/services/gemini-web.js +170 -0
  219. package/src/server/services/notification-service.js +91 -0
  220. package/src/server/services/updater.js +125 -0
  221. package/src/server/tools/awareness-tracker.js +292 -0
  222. package/src/server/tools/group-tools.js +1164 -0
  223. package/src/server/tools/media-tools.js +244 -0
  224. package/src/server/tools/pc-agent.js +327 -0
  225. package/src/server/tools/screen-service.js +51 -0
  226. package/src/server/ws-hub.js +168 -0
  227. package/src/shared/parsers/mark-tag-parser.js +275 -0
@@ -0,0 +1,360 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { getAllConfig, saveConfiguration } from '../api/db'
3
+ import { getExtractor } from '../api/vectorMemory'
4
+ import { driver } from 'driver.js'
5
+ import 'driver.js/dist/driver.css'
6
+ import { useConfirm } from '../hooks/useConfirm'
7
+ import { useChat } from '../contexts/ChatContext'
8
+ import {
9
+ AiEngineSection,
10
+ CameraSection,
11
+ ShortcutSection,
12
+ VoiceSection,
13
+ PermissionsSection
14
+ } from '../components/config'
15
+
16
+ const Configuration = ({ isFirstSetup = false, onSetupComplete = null }) => {
17
+ const [config, setConfig] = useState({
18
+ personality: 'Santai layaknya seorang teman dan suka bercanda.',
19
+ model: 'local-model',
20
+ customModel: 'default-model',
21
+ temperature: 0,
22
+ ttsRate: 10,
23
+ ttsPitch: 55,
24
+ aiProvider: 'gemini-web',
25
+ geminiWebModel: 'gemini-3.6-flash',
26
+ deepseekWebModel: 'deepseek-chat',
27
+ deepseekUserToken: '',
28
+ wakeWordEnabled: true,
29
+ customWakeWords: '',
30
+ speechLanguage: 'id-ID',
31
+ tgBotToken: '',
32
+ tgAdminIds: '',
33
+ awarenessEnabled: true,
34
+ cameraDeviceId: 'default',
35
+ cameraEnabled: true,
36
+ bgOverlayOpacity: 65
37
+ })
38
+ const [videoDevices, setVideoDevices] = useState([])
39
+ const [loading, setLoading] = useState(true)
40
+ const [isDownloadingModel, setIsDownloadingModel] = useState(false)
41
+ const [downloadProgress, setDownloadProgress] = useState(0)
42
+ const { ModalComponent } = useConfirm()
43
+ const chatContext = useChat()
44
+
45
+ useEffect(() => {
46
+ let isMounted = true
47
+ const init = async () => {
48
+ try {
49
+ setLoading(true)
50
+ await loadConfig()
51
+ } catch (err) {
52
+ console.error('Error loading config:', err)
53
+ } finally {
54
+ if (isMounted) setLoading(false)
55
+ }
56
+ }
57
+ init()
58
+
59
+ navigator.mediaDevices
60
+ .getUserMedia({ video: true })
61
+ .then((stream) => {
62
+ navigator.mediaDevices
63
+ .enumerateDevices()
64
+ .then((devices) => {
65
+ const cameras = devices.filter((d) => d.kind === 'videoinput')
66
+ if (isMounted) setVideoDevices(cameras)
67
+ })
68
+ .catch((err) => console.error('Error enumerating devices', err))
69
+
70
+ // Stop stream immediately since we just needed permission
71
+ stream.getTracks().forEach((track) => track.stop())
72
+ })
73
+ .catch((err) => console.error('Cam permission denied', err))
74
+
75
+ return () => {
76
+ isMounted = false
77
+ }
78
+ }, [])
79
+
80
+ useEffect(() => {
81
+ if (isFirstSetup) {
82
+ setTimeout(() => {
83
+ const driverObj = driver({
84
+ showProgress: true,
85
+ animate: true,
86
+ nextBtnText: 'Lanjut',
87
+ prevBtnText: 'Kembali',
88
+ doneBtnText: 'Paham!',
89
+ steps: [
90
+ {
91
+ popover: {
92
+ title: 'Halo, Selamat Datang di Mark! 👋',
93
+ description:
94
+ 'Mark adalah asisten AI pribadimu. Sebelum mulai ngobrol, ayo kita kenalan dulu sama pengaturan utamanya biar Mark bisa kerja maksimal buat kamu!',
95
+ side: 'top',
96
+ align: 'center'
97
+ }
98
+ },
99
+ {
100
+ element: '#tour-ai-provider',
101
+ popover: {
102
+ title: '1. Pilih Mesin AI',
103
+ description:
104
+ 'Kamu bisa milih mau pakai Gemini Gratis tanpa API Key, AI lokal (LM Studio), atau Custom API OpenAI-Compatible.',
105
+ side: 'bottom',
106
+ align: 'start'
107
+ }
108
+ },
109
+ {
110
+ element: '#tour-embed-provider',
111
+ popover: {
112
+ title: '2. Memori AI',
113
+ description:
114
+ 'Ini otak tempat Mark mengingat semuanya. Berjalan 100% lokal dengan vector embeddings tanpa setup tambahan.',
115
+ side: 'top',
116
+ align: 'start'
117
+ }
118
+ },
119
+ {
120
+ element: '#tour-wakeword',
121
+ popover: {
122
+ title: '3. Wake Word & Voice',
123
+ description:
124
+ 'Mark mendukung deteksi suara "Hey Mark / Mark" otomatis via Web Speech API bawaan Microsoft Edge / Chrome.',
125
+ side: 'top',
126
+ align: 'start'
127
+ }
128
+ },
129
+ {
130
+ element: '#tour-persona',
131
+ popover: {
132
+ title: '4. Kepribadian Mark',
133
+ description:
134
+ 'Di sini kamu bebas nentuin gaya bicara Mark. Mau dia formal kayak asisten pro, atau santai kayak temen nongkrong? Tulis aja di sini!',
135
+ side: 'top',
136
+ align: 'start'
137
+ }
138
+ },
139
+ {
140
+ element: '#tour-temperature',
141
+ popover: {
142
+ title: '5. Kreativitas AI',
143
+ description:
144
+ 'Temperature nentuin seberapa kreatif Mark. Angka kecil (0-0.3) bikin dia kaku tapi akurat, angka besar (0.7-1.0) bikin dia imajinatif dan luwes.',
145
+ side: 'top',
146
+ align: 'start'
147
+ }
148
+ },
149
+ {
150
+ element: '#tour-tts',
151
+ popover: {
152
+ title: '6. Pengaturan Suara',
153
+ description:
154
+ 'Atur kecepatan (Rate) dan tinggi-rendahnya nada suara (Pitch) Mark. Kamu bisa klik "Test Suara Mark" buat dengerin hasil racikanmu!',
155
+ side: 'top',
156
+ align: 'start'
157
+ }
158
+ },
159
+ {
160
+ element: '#tour-save-btn',
161
+ popover: {
162
+ title: 'Simpan & Mulai',
163
+ description:
164
+ 'Kalau udah diisi semua (termasuk API key kalau pakai Cloud), klik di sini buat mulai ngobrol sama Mark!',
165
+ side: 'top',
166
+ align: 'center'
167
+ }
168
+ }
169
+ ]
170
+ })
171
+ driverObj.drive()
172
+ }, 500)
173
+ }
174
+ }, [isFirstSetup])
175
+
176
+ const loadConfig = async () => {
177
+ const data = await getAllConfig()
178
+ if (data.length > 0) {
179
+ setConfig((prev) => ({
180
+ ...prev,
181
+ ...data[0],
182
+ aiProvider: data[0].aiProvider || 'gemini-web',
183
+ geminiWebModel: data[0].geminiWebModel || 'gemini-3.6-flash',
184
+ deepseekWebModel: data[0].deepseekWebModel || 'deepseek-chat',
185
+ deepseekUserToken: data[0].deepseekUserToken || '',
186
+ micDeviceId: data[0].micDeviceId || 'default',
187
+ awarenessEnabled: data[0].awarenessEnabled ?? true,
188
+ bgOverlayOpacity:
189
+ data[0].bgOverlayOpacity !== undefined ? Number(data[0].bgOverlayOpacity) : 65
190
+ }))
191
+ }
192
+ }
193
+
194
+ const handleSaveConfiguration = async () => {
195
+ if (config.aiProvider === 'custom') {
196
+ const endpoint = config.customEndpoint?.trim() || ''
197
+ if (!endpoint.endsWith('/chat/completions')) {
198
+ alert(
199
+ 'Gagal Menyimpan: Custom Endpoint URL tidak valid! URL wajib diakhiri dengan /chat/completions (Contoh: https://api.openai.com/v1/chat/completions).'
200
+ )
201
+ return
202
+ }
203
+ }
204
+
205
+ setIsDownloadingModel(true)
206
+ setDownloadProgress(0)
207
+
208
+ try {
209
+ let extStats = {}
210
+ await getExtractor((info) => {
211
+ if (info.status === 'initiate') {
212
+ extStats[info.file] = { loaded: 0, total: info.total || 0 }
213
+ } else if (info.status === 'progress') {
214
+ if (extStats[info.file]) {
215
+ extStats[info.file].loaded = info.loaded
216
+ extStats[info.file].total = info.total
217
+ }
218
+ const values = Object.values(extStats)
219
+ const totalBytes = values.reduce((acc, curr) => acc + curr.total, 0)
220
+ const loadedBytes = values.reduce((acc, curr) => acc + curr.loaded, 0)
221
+ if (totalBytes > 0) {
222
+ setDownloadProgress(Math.round((loadedBytes / totalBytes) * 100))
223
+ }
224
+ } else if (info.status === 'done' || info.status === 'ready') {
225
+ setDownloadProgress(100)
226
+ }
227
+ })
228
+ } catch (e) {
229
+ console.error(e)
230
+ }
231
+ setIsDownloadingModel(false)
232
+ await saveConfiguration(config)
233
+
234
+ // Update global state without reloading the page
235
+ if (chatContext && chatContext.setConfig) {
236
+ chatContext.setConfig([config])
237
+ }
238
+
239
+ if (isFirstSetup && onSetupComplete) {
240
+ onSetupComplete()
241
+ } else {
242
+ window.location.href = '#/'
243
+ }
244
+ }
245
+
246
+ const handleBack = () => window.history.back()
247
+
248
+ return (
249
+ <div className="h-screen text-white overflow-hidden relative font-['Poppins',sans-serif] bg-base-300 rounded-xl border border-white/5 shadow-2xl">
250
+ {/* Background Ambience */}
251
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,oklch(var(--n))_0%,transparent_70%)] opacity-20 pointer-events-none" />
252
+ <div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/stardust.png')] opacity-10 pointer-events-none" />
253
+
254
+ {/* Main Content Area */}
255
+ <div className="relative z-10 w-full h-full overflow-y-auto custom-scrollbar">
256
+ <div className="max-w-2xl mx-auto px-4 py-8 pb-32 space-y-8">
257
+ {/* Page Header */}
258
+ <div className="flex items-center gap-4">
259
+ {!isFirstSetup && (
260
+ <button
261
+ type="button"
262
+ onClick={handleBack}
263
+ className="btn btn-ghost btn-sm btn-circle shrink-0"
264
+ style={{ WebkitAppRegion: 'no-drag' }}
265
+ title="Kembali"
266
+ >
267
+ <svg
268
+ xmlns="http://www.w3.org/2000/svg"
269
+ width="1.2em"
270
+ height="1.2em"
271
+ fill="none"
272
+ viewBox="0 0 24 24"
273
+ stroke="currentColor"
274
+ strokeWidth={2}
275
+ >
276
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
277
+ </svg>
278
+ </button>
279
+ )}
280
+ <div>
281
+ <h1 className="text-2xl font-bold">
282
+ {isFirstSetup ? 'Selamat Datang di Mark!' : 'Pengaturan Mark'}
283
+ </h1>
284
+ <p className="opacity-50 text-sm mt-1">
285
+ {isFirstSetup
286
+ ? 'Sebelum mulai ngobrol, atur provider AI dan pengaturan dasar lainnya di bawah ini.'
287
+ : 'Sesuaikan perilaku Mark dengan preferensimu.'}
288
+ </p>
289
+ </div>
290
+ </div>
291
+
292
+ {loading ? (
293
+ <div className="flex-1 min-h-[50vh] flex flex-col items-center justify-center gap-3">
294
+ <span className="loading loading-spinner loading-lg text-primary" />
295
+ <p className="font-mono text-xs text-base-content/50 tracking-widest uppercase">
296
+ Memuat Pengaturan Mark...
297
+ </p>
298
+ </div>
299
+ ) : (
300
+ <>
301
+ {/* 1. AI Engine & Tools */}
302
+ <AiEngineSection config={config} setConfig={setConfig} />
303
+
304
+ <div className="divider"></div>
305
+
306
+ {/* 2. Camera Settings */}
307
+ <CameraSection config={config} setConfig={setConfig} videoDevices={videoDevices} />
308
+
309
+ {/* 3. Global Shortcut Settings */}
310
+ <ShortcutSection config={config} setConfig={setConfig} />
311
+
312
+ <div className="divider"></div>
313
+
314
+ {/* 4. Voice & Wake Word Settings */}
315
+ <VoiceSection config={config} setConfig={setConfig} />
316
+
317
+ <div className="divider"></div>
318
+
319
+ {/* 6. Security & Whitelist Permissions */}
320
+ <PermissionsSection />
321
+
322
+ {/* 7. Save Bar */}
323
+ <div className="flex flex-col items-end pt-2">
324
+ {isDownloadingModel && (
325
+ <div className="w-full max-w-xs mb-4">
326
+ <div className="flex justify-between text-xs mb-1">
327
+ <span>Mengunduh Model Embeddings...</span>
328
+ <span>{downloadProgress}%</span>
329
+ </div>
330
+ <progress
331
+ className="progress progress-primary w-full"
332
+ value={downloadProgress}
333
+ max="100"
334
+ ></progress>
335
+ </div>
336
+ )}
337
+ <button
338
+ id="tour-save-btn"
339
+ onClick={handleSaveConfiguration}
340
+ disabled={isDownloadingModel}
341
+ className="btn btn-primary px-8 cursor-pointer"
342
+ >
343
+ {isDownloadingModel
344
+ ? 'Menyimpan...'
345
+ : isFirstSetup
346
+ ? 'Simpan & Mulai Gunakan Mark'
347
+ : 'Simpan Pengaturan'}
348
+ </button>
349
+ </div>
350
+ </>
351
+ )}
352
+ </div>
353
+
354
+ <ModalComponent />
355
+ </div>
356
+ </div>
357
+ )
358
+ }
359
+
360
+ export default Configuration
@@ -0,0 +1,293 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import { FaGoogle, FaKey, FaSave, FaCheckCircle, FaExclamationTriangle } from 'react-icons/fa'
4
+ import { getAllConfig, saveConfiguration } from '../api/db'
5
+ import { webApi } from '../api/web-bridge'
6
+
7
+ const GoogleWorkspace = () => {
8
+ const navigate = useNavigate()
9
+ const [config, setConfig] = useState({})
10
+ const [isLoading, setIsLoading] = useState(true)
11
+ const [isSaving, setIsSaving] = useState(false)
12
+ const [toastMessage, setToastMessage] = useState(null)
13
+ const [isConnected, setIsConnected] = useState(false)
14
+ const [isConnecting, setIsConnecting] = useState(false)
15
+
16
+ useEffect(() => {
17
+ loadConfig()
18
+ checkConnectionStatus()
19
+ }, [])
20
+
21
+ const checkConnectionStatus = async () => {
22
+ const res = await webApi.googleStatus()
23
+ setIsConnected(Boolean(res?.isConnected))
24
+ }
25
+
26
+ const handleConnect = async () => {
27
+ if (!config.googleClientId || !config.googleClientSecret) {
28
+ showToast('Client ID & Secret harus diisi dan disimpan dulu!')
29
+ return
30
+ }
31
+ setIsConnecting(true)
32
+ try {
33
+ const res = await webApi.googleConnect(config.googleClientId, config.googleClientSecret)
34
+ if (res.success) {
35
+ showToast('Berhasil terhubung ke Google Workspace!')
36
+ setIsConnected(true)
37
+ } else {
38
+ showToast(`Gagal: ${res.error}`)
39
+ }
40
+ } catch (e) {
41
+ showToast('Gagal memproses login.')
42
+ } finally {
43
+ setIsConnecting(false)
44
+ }
45
+ }
46
+
47
+ const handleDisconnect = async () => {
48
+ try {
49
+ await webApi.googleDisconnect()
50
+ setIsConnected(false)
51
+ showToast('Terputus dari Google Workspace.')
52
+ } catch (e) {
53
+ showToast('Gagal memutuskan koneksi.')
54
+ }
55
+ }
56
+
57
+ const loadConfig = async () => {
58
+ try {
59
+ const data = await getAllConfig()
60
+ if (data && data.length > 0) {
61
+ setConfig(data[0])
62
+ }
63
+ } catch (error) {
64
+ console.error('Failed to load config:', error)
65
+ } finally {
66
+ setIsLoading(false)
67
+ }
68
+ }
69
+
70
+ const handleSave = async () => {
71
+ setIsSaving(true)
72
+ try {
73
+ await saveConfiguration(config)
74
+ await webApi.syncConfig(config)
75
+ showToast('Kredensial Google Workspace berhasil disimpan!')
76
+ } catch (error) {
77
+ console.error('Failed to save config:', error)
78
+ showToast('Gagal menyimpan kredensial.')
79
+ } finally {
80
+ setIsSaving(false)
81
+ }
82
+ }
83
+
84
+ const showToast = (msg) => {
85
+ setToastMessage(msg)
86
+ setTimeout(() => setToastMessage(null), 3000)
87
+ }
88
+
89
+ if (isLoading) {
90
+ return (
91
+ <div className="min-h-screen bg-base-300 text-base-content flex items-center justify-center">
92
+ <span className="loading loading-spinner text-primary w-12 h-12"></span>
93
+ </div>
94
+ )
95
+ }
96
+
97
+ return (
98
+ <div className="h-screen bg-base-300 text-base-content overflow-hidden relative font-['Poppins',sans-serif]">
99
+ {/* Background Ambience */}
100
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,oklch(var(--n))_0%,transparent_70%)] opacity-20 pointer-events-none" />
101
+ <div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/stardust.png')] opacity-10 pointer-events-none" />
102
+
103
+ {/* Main Content Area */}
104
+ <div className="relative z-10 w-full h-full overflow-y-auto custom-scrollbar">
105
+ <div className="max-w-4xl mx-auto px-4 py-8 pb-32 space-y-8">
106
+ {/* Page Header */}
107
+ <div className="flex items-center justify-between">
108
+ <div className="flex items-center gap-4">
109
+ <button
110
+ type="button"
111
+ onClick={() => navigate('/')}
112
+ className="btn btn-ghost btn-sm btn-circle shrink-0"
113
+ style={{ WebkitAppRegion: 'no-drag' }}
114
+ title="Kembali"
115
+ >
116
+ <svg
117
+ xmlns="http://www.w3.org/2000/svg"
118
+ width="1.2em"
119
+ height="1.2em"
120
+ fill="none"
121
+ viewBox="0 0 24 24"
122
+ stroke="currentColor"
123
+ strokeWidth={2}
124
+ >
125
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
126
+ </svg>
127
+ </button>
128
+ <div>
129
+ <h1 className="text-2xl font-bold">Google Workspace</h1>
130
+ <p className="opacity-50 text-sm mt-1">
131
+ Integrasi Drive, Calendar, dan Gmail ke Mark AI.
132
+ </p>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ {/* Info Card */}
137
+ <div className="card bg-base-100 border border-info/30 shadow-xl shadow-info/10">
138
+ <div className="card-body">
139
+ <h2 className="card-title text-info mb-2 flex items-center gap-2">
140
+ <FaExclamationTriangle /> Panduan Singkat
141
+ </h2>
142
+ <p className="text-sm opacity-80 mb-4">
143
+ Untuk menghubungkan Mark dengan Google Workspace milikmu, kamu harus membuat{' '}
144
+ <strong>OAuth Client ID</strong> bertipe Desktop App di Google Cloud Console.
145
+ </p>
146
+ <ol className="list-decimal list-inside text-sm space-y-2 opacity-80 bg-base-200 p-4 rounded-xl font-mono">
147
+ <li>
148
+ Buka{' '}
149
+ <a
150
+ href="https://console.cloud.google.com/"
151
+ target="_blank"
152
+ rel="noreferrer"
153
+ className="text-primary hover:underline"
154
+ >
155
+ console.cloud.google.com
156
+ </a>
157
+ </li>
158
+ <li>
159
+ Buat Project Baru & Aktifkan: <strong>Drive API, Gmail API, Calendar API</strong>
160
+ </li>
161
+ <li>
162
+ Buat <strong>OAuth Consent Screen</strong> (External, masukkan emailmu di Test
163
+ Users)
164
+ </li>
165
+ <li>
166
+ Buat Credentials baru: <strong>OAuth client ID</strong> &gt;{' '}
167
+ <strong>Desktop App</strong>
168
+ </li>
169
+ <li>Copy Client ID & Client Secret ke form di bawah ini.</li>
170
+ </ol>
171
+ </div>
172
+ </div>
173
+
174
+ {/* Form Card */}
175
+ <div className="card bg-base-100 border border-white/5 shadow-2xl">
176
+ <div className="card-body space-y-6">
177
+ <h2 className="card-title text-xl border-b border-white/10 pb-4 flex items-center gap-2">
178
+ <FaKey className="text-warning" /> API Credentials
179
+ </h2>
180
+
181
+ <div className="form-control w-full">
182
+ <label className="label">
183
+ <span className="label-text font-bold text-sm">Google Client ID</span>
184
+ </label>
185
+ <input
186
+ type="text"
187
+ placeholder="Misal: 123456789-abcdefghijkl.apps.googleusercontent.com"
188
+ className="input input-bordered w-full font-mono text-sm focus:border-primary"
189
+ value={config.googleClientId || ''}
190
+ onChange={(e) => setConfig({ ...config, googleClientId: e.target.value })}
191
+ />
192
+ </div>
193
+
194
+ <div className="form-control w-full">
195
+ <label className="label">
196
+ <span className="label-text font-bold text-sm">Google Client Secret</span>
197
+ </label>
198
+ <input
199
+ type="password"
200
+ placeholder="GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx"
201
+ className="input input-bordered w-full font-mono text-sm focus:border-primary"
202
+ value={config.googleClientSecret || ''}
203
+ onChange={(e) => setConfig({ ...config, googleClientSecret: e.target.value })}
204
+ />
205
+ </div>
206
+
207
+ <div className="bg-warning/10 border border-warning/30 rounded-xl p-4 mt-4 flex gap-3 items-start">
208
+ <FaExclamationTriangle className="text-warning mt-1 shrink-0" />
209
+ <div>
210
+ <h3 className="font-bold text-warning text-sm">Peringatan Keamanan</h3>
211
+ <p className="text-xs opacity-70 mt-1">
212
+ Kredensial ini disimpan secara lokal dan aman di databasemu. Jangan pernah
213
+ membagikan Client Secret ke orang lain. Mark hanya akan menggunakan kredensial
214
+ ini untuk meminta akses ke akun Google-mu secara resmi.
215
+ </p>
216
+ </div>
217
+ </div>
218
+
219
+ {/* Connection Status & Buttons */}
220
+ <div className="border-t border-white/10 pt-6 mt-2 flex flex-col gap-4">
221
+ <div className="flex items-center justify-between">
222
+ <div>
223
+ <h3 className="font-bold text-sm">Status Koneksi</h3>
224
+ <p className="text-xs opacity-70">
225
+ {isConnected
226
+ ? 'Mark terhubung dan memiliki akses ke Google Workspace.'
227
+ : 'Mark belum diberikan akses masuk ke Google Workspace.'}
228
+ </p>
229
+ </div>
230
+ <div
231
+ className={`px-3 py-1 rounded-full text-xs font-bold ${isConnected ? 'bg-success/20 text-success' : 'bg-base-300 text-white/50'}`}
232
+ >
233
+ {isConnected ? 'TERHUBUNG' : 'TERPUTUS'}
234
+ </div>
235
+ </div>
236
+
237
+ {isConnected ? (
238
+ <button onClick={handleDisconnect} className="btn btn-error btn-outline w-full">
239
+ Putuskan Koneksi Google
240
+ </button>
241
+ ) : (
242
+ <button
243
+ onClick={handleConnect}
244
+ disabled={isConnecting || !config.googleClientId || !config.googleClientSecret}
245
+ className="btn btn-info w-full"
246
+ >
247
+ {isConnecting ? (
248
+ <span className="loading loading-spinner"></span>
249
+ ) : (
250
+ <FaGoogle />
251
+ )}
252
+ Login & Beri Akses
253
+ </button>
254
+ )}
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ {/* Sticky Save Button */}
261
+ <div className="fixed bottom-0 left-0 w-full bg-base-300/80 backdrop-blur border-t border-white/10 p-4 z-50">
262
+ <div className="max-w-5xl mx-auto flex justify-end">
263
+ <button
264
+ onClick={handleSave}
265
+ disabled={isSaving || !config.googleClientId || !config.googleClientSecret}
266
+ className="btn btn-primary min-w-[200px]"
267
+ >
268
+ {isSaving ? (
269
+ <span className="loading loading-spinner"></span>
270
+ ) : (
271
+ <>
272
+ <FaSave /> Simpan Kredensial
273
+ </>
274
+ )}
275
+ </button>
276
+ </div>
277
+ </div>
278
+
279
+ {/* Toast Notification */}
280
+ {toastMessage && (
281
+ <div className="toast toast-top toast-center z-[100]">
282
+ <div className="alert alert-success shadow-lg">
283
+ <FaCheckCircle className="text-xl" />
284
+ <span>{toastMessage}</span>
285
+ </div>
286
+ </div>
287
+ )}
288
+ </div>
289
+ </div>
290
+ )
291
+ }
292
+
293
+ export default GoogleWorkspace