@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,592 @@
1
+ import { create, insert, insertMultiple, search, remove, removeMultiple } from '@orama/orama'
2
+ import { generateVector } from './vectorMemory'
3
+
4
+ // Dimensi vektor sesuai model Transformers.js (all-MiniLM-L6-v2 = 384)
5
+ const VECTOR_SIZE = 384
6
+
7
+ let archiveIndex = null
8
+ let documentIndex = null
9
+ let memoryIndex = null
10
+ let turnPairIndex = null
11
+
12
+ export async function initOramaIndices() {
13
+ memoryIndex = await create({
14
+ schema: {
15
+ type: 'string',
16
+ summary: 'string',
17
+ memory: 'string',
18
+ timestamp: 'number',
19
+ dbId: 'string',
20
+ vector: `vector[${VECTOR_SIZE}]`
21
+ }
22
+ })
23
+
24
+ archiveIndex = await create({
25
+ schema: {
26
+ summary: 'string',
27
+ topic: 'string',
28
+ timestamp: 'number',
29
+ dbId: 'string',
30
+ vector: `vector[${VECTOR_SIZE}]`
31
+ }
32
+ })
33
+
34
+ documentIndex = await create({
35
+ schema: {
36
+ docName: 'string',
37
+ chunkIndex: 'number',
38
+ content: 'string',
39
+ timestamp: 'number',
40
+ dbId: 'string',
41
+ vector: `vector[${VECTOR_SIZE}]`
42
+ }
43
+ })
44
+
45
+ turnPairIndex = await create({
46
+ schema: {
47
+ pairId: 'string',
48
+ sessionId: 'string',
49
+ sessionTitle: 'string',
50
+ userText: 'string',
51
+ aiText: 'string',
52
+ combinedText: 'string',
53
+ timestamp: 'number',
54
+ vector: `vector[${VECTOR_SIZE}]`
55
+ }
56
+ })
57
+ }
58
+
59
+ // Dipanggil saat app start: load semua data dari SQLite database ke Orama
60
+ export async function hydrateFromDb(onProgress) {
61
+ const { db } = await import('./db')
62
+
63
+ // 1. CHAT TURNS HYDRATION & SMART MIGRATION
64
+ let validTurnsCount = 0
65
+ try {
66
+ const turnCount = await db.chatTurns.count()
67
+ const sessionCount = await db.sessions.count()
68
+
69
+ if (turnCount === 0 && sessionCount > 0) {
70
+ console.log('[Orama] Kondisi 1: chatTurns kosong & sessions ada. Memulai smart migration...')
71
+ const { migrateOldSessionsToTurns } = await import('./turnPairMigrator')
72
+ validTurnsCount = await migrateOldSessionsToTurns(onProgress)
73
+ } else if (turnCount > 0) {
74
+ const turns = await db.chatTurns.toArray()
75
+ const validTurns = turns
76
+ .filter((t) => t.vector && t.vector.length === VECTOR_SIZE)
77
+ .map((t) => {
78
+ const rawTs = t.timestamp
79
+ const numericTs =
80
+ typeof rawTs === 'number' && !isNaN(rawTs)
81
+ ? rawTs
82
+ : typeof rawTs === 'string' && !isNaN(Date.parse(rawTs))
83
+ ? Date.parse(rawTs)
84
+ : Number(rawTs) || Date.now()
85
+ return {
86
+ pairId: String(t.pairId || t.id || ''),
87
+ sessionId: String(t.sessionId || t.session_id || '1'),
88
+ sessionTitle: String(t.sessionTitle || t.session_title || 'Session'),
89
+ userText: String(t.userText || t.user_text || ''),
90
+ aiText: String(t.aiText || t.ai_text || ''),
91
+ combinedText: String(t.combinedText || t.combined_text || ''),
92
+ timestamp: numericTs,
93
+ vector: t.vector
94
+ }
95
+ })
96
+ if (validTurns.length > 0) {
97
+ await insertMultiple(turnPairIndex, validTurns)
98
+ validTurnsCount = validTurns.length
99
+ }
100
+ console.log(`[Orama] Kondisi 2: Hydrated ${validTurnsCount} turn pairs from database`)
101
+ } else {
102
+ console.log('[Orama] Kondisi 3: Fresh install, no chat turns to migrate')
103
+ }
104
+ } catch (err) {
105
+ console.error('[Orama] Error hydrating turn pairs:', err)
106
+ }
107
+
108
+ const archives = await db.chatArchive.toArray()
109
+ const validArchives = []
110
+ const needsMigration = localStorage.getItem('migrated_vectors_v1') !== 'true'
111
+
112
+ for (let a of archives) {
113
+ if (needsMigration || !a.vector || a.vector.length !== VECTOR_SIZE) {
114
+ console.log(`[Orama] Re-generating vector for archive ID ${a.id}`)
115
+ a.vector = await generateVector(a.summary)
116
+ if (a.vector && a.vector.length === VECTOR_SIZE) {
117
+ db.chatArchive.update(a.id, { vector: a.vector }).catch(console.error)
118
+ }
119
+ }
120
+ if (a.vector && a.vector.length === VECTOR_SIZE) {
121
+ validArchives.push({
122
+ summary: String(a.summary || ''),
123
+ topic: String(a.topic || 'General'),
124
+ timestamp: Number(a.timestamp) || Date.now(),
125
+ dbId: String(a.id || ''),
126
+ vector: a.vector
127
+ })
128
+ }
129
+ }
130
+
131
+ if (validArchives.length > 0) {
132
+ await insertMultiple(archiveIndex, validArchives)
133
+ }
134
+
135
+ const docs = await db.documents.toArray()
136
+ const validDocs = []
137
+ for (let d of docs) {
138
+ if (needsMigration || !d.vector || d.vector.length !== VECTOR_SIZE) {
139
+ console.log(`[Orama] Re-generating vector for doc ID ${d.id}`)
140
+ d.vector = await generateVector(d.content)
141
+ if (d.vector && d.vector.length === VECTOR_SIZE) {
142
+ db.documents.update(d.id, { vector: d.vector }).catch(console.error)
143
+ }
144
+ }
145
+ if (d.vector && d.vector.length === VECTOR_SIZE) {
146
+ validDocs.push({
147
+ docName: String(d.docName || d.doc_name || ''),
148
+ chunkIndex: Number(d.chunkIndex ?? d.chunk_index ?? 0),
149
+ content: String(d.content || ''),
150
+ timestamp: Number(d.timestamp) || Date.now(),
151
+ dbId: String(d.id || ''),
152
+ vector: d.vector
153
+ })
154
+ }
155
+ }
156
+
157
+ if (validDocs.length > 0) {
158
+ await insertMultiple(documentIndex, validDocs)
159
+ }
160
+
161
+ const memories = await db.memory.toArray()
162
+ const validMemories = []
163
+ for (let m of memories) {
164
+ if (needsMigration || !m.vector || m.vector.length !== VECTOR_SIZE) {
165
+ console.log(`[Orama] Re-generating vector for memory ID ${m.id}`)
166
+ m.vector = await generateVector(m.memory)
167
+ if (m.vector && m.vector.length === VECTOR_SIZE) {
168
+ db.memory.update(m.id, { vector: m.vector }).catch(console.error)
169
+ }
170
+ }
171
+ if (m.vector && m.vector.length === VECTOR_SIZE) {
172
+ validMemories.push({
173
+ type: String(m.type || 'notes'),
174
+ summary: String(m.summary || ''),
175
+ memory: String(m.memory || ''),
176
+ timestamp: Number(m.created_at || m.timestamp) || Date.now(),
177
+ dbId: String(m.id || ''),
178
+ vector: m.vector
179
+ })
180
+ }
181
+ }
182
+
183
+ if (validMemories.length > 0) {
184
+ await insertMultiple(memoryIndex, validMemories)
185
+ }
186
+
187
+ if (needsMigration) {
188
+ localStorage.setItem('migrated_vectors_v1', 'true')
189
+ console.log('[Orama] Successfully migrated all old vectors to new model!')
190
+ }
191
+
192
+ console.log(`[Orama] Hydrated: ${validTurnsCount} turn pairs, ${validArchives.length} archives, ${validDocs.length} doc chunks, ${validMemories.length} memories`)
193
+ }
194
+
195
+ // Vector search di arsip obrolan
196
+ export async function searchArchives(queryVector, limit = 3) {
197
+ if (!archiveIndex) return []
198
+ try {
199
+ const results = await search(archiveIndex, {
200
+ mode: 'vector',
201
+ vector: { value: queryVector, property: 'vector' },
202
+ similarity: 0.25,
203
+ limit
204
+ })
205
+ return results.hits.map(hit => hit.document)
206
+ } catch (err) {
207
+ console.error('[Orama] Error in searchArchives:', err)
208
+ return []
209
+ }
210
+ }
211
+
212
+ // Vector search di dokumen RAG
213
+ export async function searchDocuments(queryText, queryVector, limit = 5) {
214
+ if (!documentIndex) return []
215
+ try {
216
+ const results = await search(documentIndex, {
217
+ term: queryText,
218
+ mode: 'hybrid',
219
+ vector: { value: queryVector, property: 'vector' },
220
+ similarity: 0.25,
221
+ limit
222
+ })
223
+ return results.hits.map(hit => hit.document)
224
+ } catch (error) {
225
+ console.error('[Orama] Error in searchDocuments:', error)
226
+ return []
227
+ }
228
+ }
229
+
230
+ // Insert baru (dipanggil setelah penyimpanan arsip)
231
+ export async function insertArchiveToOrama(data) {
232
+ if (!archiveIndex) return
233
+ await insert(archiveIndex, {
234
+ summary: String(data.summary || ''),
235
+ topic: String(data.topic || 'General'),
236
+ timestamp: Number(data.timestamp) || Date.now(),
237
+ dbId: String(data.dbId || data.id || ''),
238
+ vector: data.vector
239
+ })
240
+ }
241
+
242
+ export async function insertDocumentChunksToOrama(chunks) {
243
+ if (!documentIndex || !Array.isArray(chunks)) return
244
+ const validChunks = chunks.map((c) => ({
245
+ docName: String(c.docName || c.doc_name || ''),
246
+ chunkIndex: Number(c.chunkIndex ?? c.chunk_index ?? 0),
247
+ content: String(c.content || ''),
248
+ timestamp: Number(c.timestamp) || Date.now(),
249
+ dbId: String(c.dbId || c.id || ''),
250
+ vector: c.vector
251
+ }))
252
+ await insertMultiple(documentIndex, validChunks)
253
+ }
254
+
255
+ export async function deleteArchiveFromOrama(dbId) {
256
+ if (!archiveIndex || !dbId) return
257
+ try {
258
+ const res = await search(archiveIndex, { where: { dbId: String(dbId) } })
259
+ if (res.hits.length > 0) {
260
+ for (let h of res.hits) {
261
+ await remove(archiveIndex, h.id)
262
+ }
263
+ }
264
+ } catch (err) {
265
+ console.error('[Orama] Error deleteArchiveFromOrama:', err)
266
+ }
267
+ }
268
+
269
+ export async function deleteDocumentFromOrama(docName) {
270
+ if (!documentIndex) return
271
+ const res = await search(documentIndex, { term: docName, properties: ['docName'] })
272
+ const ids = res.hits.map(h => h.id)
273
+ await removeMultiple(documentIndex, ids)
274
+ }
275
+
276
+ // ======================== TURN PAIR ORAMA INDEX ========================
277
+
278
+ export async function insertTurnPairToOrama(data) {
279
+ if (!turnPairIndex || !data.vector || data.vector.length !== VECTOR_SIZE) return
280
+ try {
281
+ const rawTs = data.timestamp
282
+ const numericTs =
283
+ typeof rawTs === 'number' && !isNaN(rawTs)
284
+ ? rawTs
285
+ : typeof rawTs === 'string' && !isNaN(Date.parse(rawTs))
286
+ ? Date.parse(rawTs)
287
+ : Number(rawTs) || Date.now()
288
+
289
+ await insert(turnPairIndex, {
290
+ pairId: String(data.pairId || data.id || ''),
291
+ sessionId: String(data.sessionId || data.session_id || '1'),
292
+ sessionTitle: String(data.sessionTitle || data.session_title || 'Session'),
293
+ userText: String(data.userText || data.user_text || ''),
294
+ aiText: String(data.aiText || data.ai_text || ''),
295
+ combinedText: String(data.combinedText || data.combined_text || ''),
296
+ timestamp: numericTs,
297
+ vector: data.vector
298
+ })
299
+ } catch (err) {
300
+ console.error('[Orama] Error insertTurnPairToOrama:', err)
301
+ }
302
+ }
303
+
304
+ export async function insertBatchTurnPairsToOrama(turns) {
305
+ if (!turnPairIndex || !Array.isArray(turns) || turns.length === 0) return
306
+ try {
307
+ const valid = turns
308
+ .filter((t) => t.vector && t.vector.length === VECTOR_SIZE)
309
+ .map((t) => {
310
+ const rawTs = t.timestamp
311
+ const numericTs =
312
+ typeof rawTs === 'number' && !isNaN(rawTs)
313
+ ? rawTs
314
+ : typeof rawTs === 'string' && !isNaN(Date.parse(rawTs))
315
+ ? Date.parse(rawTs)
316
+ : Number(rawTs) || Date.now()
317
+
318
+ return {
319
+ pairId: String(t.pairId || t.id || ''),
320
+ sessionId: String(t.sessionId || t.session_id || '1'),
321
+ sessionTitle: String(t.sessionTitle || t.session_title || 'Session'),
322
+ userText: String(t.userText || t.user_text || ''),
323
+ aiText: String(t.aiText || t.ai_text || ''),
324
+ combinedText: String(t.combinedText || t.combined_text || ''),
325
+ timestamp: numericTs,
326
+ vector: t.vector
327
+ }
328
+ })
329
+
330
+ if (valid.length > 0) {
331
+ await insertMultiple(turnPairIndex, valid)
332
+ }
333
+ } catch (err) {
334
+ console.error('[Orama] Error insertBatchTurnPairsToOrama:', err)
335
+ }
336
+ }
337
+
338
+ export async function searchTurnPairsInOrama(queryText, queryVector, limit = 5, threshold = 0.5) {
339
+ if (!turnPairIndex || !queryVector) return []
340
+ try {
341
+ const results = await search(turnPairIndex, {
342
+ term: queryText,
343
+ mode: 'hybrid',
344
+ vector: { value: queryVector, property: 'vector' },
345
+ similarity: threshold,
346
+ limit
347
+ })
348
+ return results.hits.map((hit) => ({
349
+ ...hit.document,
350
+ score: hit.score
351
+ }))
352
+ } catch (err) {
353
+ console.error('[Orama] Error in searchTurnPairsInOrama:', err)
354
+ return []
355
+ }
356
+ }
357
+
358
+ export async function deleteTurnPairsBySessionFromOrama(sessionId) {
359
+ if (!turnPairIndex || !sessionId) return
360
+ try {
361
+ const results = await search(turnPairIndex, {
362
+ where: { sessionId: String(sessionId) }
363
+ })
364
+ if (results.hits.length > 0) {
365
+ const ids = results.hits.map((h) => h.id)
366
+ await removeMultiple(turnPairIndex, ids)
367
+ }
368
+ } catch (err) {
369
+ console.error('[Orama] Error deleteTurnPairsBySessionFromOrama:', err)
370
+ }
371
+ }
372
+
373
+ // ======================== MEMORY ORAMA INDEX ========================
374
+
375
+ export async function searchMemoriesInOrama(queryText, queryVector, limit = 5, filterTypes = null, threshold = 0.5) {
376
+ if (!memoryIndex || !queryVector) return []
377
+ try {
378
+ const results = await search(memoryIndex, {
379
+ term: queryText,
380
+ mode: 'hybrid',
381
+ vector: { value: queryVector, property: 'vector' },
382
+ similarity: threshold,
383
+ limit: limit * 4
384
+ })
385
+ let hits = results.hits.map(hit => ({ ...hit.document, id: hit.document.dbId, score: hit.score }))
386
+ if (filterTypes) {
387
+ const typesArr = Array.isArray(filterTypes) ? filterTypes : [filterTypes]
388
+ hits = hits.filter(h => typesArr.includes(h.type))
389
+ }
390
+ hits.sort((a, b) => b.score - a.score)
391
+ return hits.slice(0, limit)
392
+ } catch (err) {
393
+ console.error('[Orama] Error in searchMemoriesInOrama:', err)
394
+ return []
395
+ }
396
+ }
397
+
398
+ export async function insertMemoryToOrama(data) {
399
+ if (!memoryIndex || !data.vector || data.vector.length !== VECTOR_SIZE) return
400
+ try {
401
+ await insert(memoryIndex, {
402
+ type: String(data.type || 'notes'),
403
+ summary: String(data.summary || ''),
404
+ memory: String(data.memory || ''),
405
+ timestamp: Number(data.created_at || data.timestamp) || Date.now(),
406
+ dbId: String(data.id || data.dbId || ''),
407
+ vector: data.vector
408
+ })
409
+ } catch (err) {
410
+ console.error('[Orama] Error insertMemoryToOrama:', err)
411
+ }
412
+ }
413
+
414
+ export async function updateMemoryInOrama(dbId, data) {
415
+ if (!memoryIndex) return
416
+ await deleteMemoryFromOrama(dbId)
417
+ await insertMemoryToOrama({ ...data, id: String(dbId), dbId: String(dbId) })
418
+ }
419
+
420
+ export async function deleteMemoryFromOrama(dbId) {
421
+ if (!memoryIndex || !dbId) return
422
+ try {
423
+ const res = await search(memoryIndex, { where: { dbId: String(dbId) } })
424
+ if (res.hits.length > 0) {
425
+ for (let h of res.hits) {
426
+ await remove(memoryIndex, h.id)
427
+ }
428
+ }
429
+ } catch (err) {
430
+ console.error('[Orama] Error deleteMemoryFromOrama:', err)
431
+ }
432
+ }
433
+
434
+ export async function findSimilarMemoryClusters(threshold = 0.60) {
435
+ if (!memoryIndex) {
436
+ console.warn('[Orama Groomer] memoryIndex belum siap!')
437
+ return []
438
+ }
439
+ try {
440
+ console.log('[Orama Groomer] Memulai scanning cluster memori di Orama dengan threshold:', threshold)
441
+ const results = await search(memoryIndex, {
442
+ term: '',
443
+ limit: 1000
444
+ })
445
+ let memories = results.hits
446
+ .map(hit => ({
447
+ ...hit.document,
448
+ id: hit.document.dbId
449
+ }))
450
+ .filter(m => m.type === 'profile' || m.type === 'preference')
451
+
452
+ const visited = new Set()
453
+ const clusters = []
454
+ let groupCount = 1
455
+
456
+ for (const mem of memories) {
457
+ if (visited.has(mem.id)) continue
458
+ if (!mem.vector || !Array.isArray(mem.vector)) {
459
+ visited.add(mem.id)
460
+ continue
461
+ }
462
+
463
+ const simResults = await search(memoryIndex, {
464
+ term: mem.memory,
465
+ mode: 'hybrid',
466
+ vector: { value: mem.vector, property: 'vector' },
467
+ similarity: threshold,
468
+ limit: 20
469
+ })
470
+
471
+ const similarHits = simResults.hits
472
+ .map(hit => ({
473
+ ...hit.document,
474
+ id: hit.document.dbId,
475
+ score: hit.score
476
+ }))
477
+ .filter(
478
+ h =>
479
+ (h.type === 'profile' || h.type === 'preference') &&
480
+ h.score >= threshold &&
481
+ !visited.has(h.id)
482
+ )
483
+
484
+ if (similarHits.length >= 2) {
485
+ similarHits.forEach(h => visited.add(h.id))
486
+ clusters.push({
487
+ group: groupCount++,
488
+ items: similarHits.map(h => ({
489
+ id: h.id,
490
+ type: h.type,
491
+ memory: h.memory,
492
+ timestamp: h.timestamp
493
+ }))
494
+ })
495
+ } else {
496
+ visited.add(mem.id)
497
+ }
498
+ }
499
+
500
+ console.log(`[Orama Groomer] Ditemukan ${clusters.length} cluster dari total ${memories.length} memori profile/preference.`)
501
+ return clusters
502
+ } catch (err) {
503
+ console.error('[Orama] Error in findSimilarMemoryClusters:', err)
504
+ return []
505
+ }
506
+ }
507
+
508
+ // On-the-fly Orama Hybrid Vector Search for read-document
509
+ export async function searchDocumentWithOrama(rawText, searchQuery, limit = 5) {
510
+ try {
511
+ if (!rawText || !searchQuery) return []
512
+
513
+ // 1. Chunk text (500 chars with 50 overlap)
514
+ const chunks = []
515
+ let start = 0
516
+ const chunkSize = 500
517
+ const overlap = 50
518
+ while (start < rawText.length) {
519
+ const end = Math.min(start + chunkSize, rawText.length)
520
+ const chunkStr = rawText.slice(start, end).trim()
521
+ if (chunkStr) chunks.push(chunkStr)
522
+ start += chunkSize - overlap
523
+ }
524
+
525
+ if (chunks.length === 0) return []
526
+
527
+ // 2. Pre-filter candidate chunks to avoid CPU freeze (Max 20 chunks)
528
+ const terms = searchQuery.toLowerCase().split(/\s+/).filter((t) => t.length > 2)
529
+ let candidateChunks = chunks
530
+ if (chunks.length > 20) {
531
+ if (terms.length > 0) {
532
+ const scored = chunks.map((c) => {
533
+ const lower = c.toLowerCase()
534
+ let score = 0
535
+ for (const term of terms) {
536
+ if (lower.includes(term)) score += 1
537
+ }
538
+ return { chunk: c, score }
539
+ })
540
+ scored.sort((a, b) => b.score - a.score)
541
+ candidateChunks = scored.slice(0, 20).map((s) => s.chunk)
542
+ } else {
543
+ candidateChunks = chunks.slice(0, 20)
544
+ }
545
+ }
546
+
547
+ // 3. Generate query vector
548
+ const queryVector = await generateVector(searchQuery)
549
+ if (!queryVector || queryVector.length !== VECTOR_SIZE) {
550
+ return candidateChunks.slice(0, limit)
551
+ }
552
+
553
+ // 4. Create ephemeral in-memory Orama index
554
+ const tempIndex = await create({
555
+ schema: {
556
+ content: 'string',
557
+ vector: `vector[${VECTOR_SIZE}]`
558
+ }
559
+ })
560
+
561
+ // 5. Generate embeddings for candidate chunks
562
+ const chunkVectors = await Promise.all(
563
+ candidateChunks.map(async (c) => {
564
+ const v = await generateVector(c)
565
+ return { content: c, vector: v }
566
+ })
567
+ )
568
+
569
+ const validDocs = chunkVectors.filter((d) => d.vector && d.vector.length === VECTOR_SIZE)
570
+ if (validDocs.length === 0) return candidateChunks.slice(0, limit)
571
+
572
+ await insertMultiple(tempIndex, validDocs)
573
+
574
+ // 6. Execute Hybrid Search
575
+ const searchRes = await search(tempIndex, {
576
+ term: searchQuery,
577
+ mode: 'hybrid',
578
+ vector: { value: queryVector, property: 'vector' },
579
+ similarity: 0.25,
580
+ limit
581
+ })
582
+
583
+ if (searchRes.hits && searchRes.hits.length > 0) {
584
+ return searchRes.hits.map((h) => h.document.content)
585
+ }
586
+
587
+ return candidateChunks.slice(0, limit)
588
+ } catch (err) {
589
+ console.error('[Orama] Ephemeral searchDocumentWithOrama error:', err)
590
+ return []
591
+ }
592
+ }
@@ -0,0 +1,87 @@
1
+ import { generateVector } from './vectorMemory'
2
+ import { bulkInsertDocuments, deleteDocumentByName, getAllDocuments } from './db'
3
+ import { insertDocumentChunksToOrama, deleteDocumentFromOrama } from './oramaStore'
4
+ import { webApi } from './web-bridge'
5
+
6
+ function splitTextIntoChunks(text, chunkSize = 500, overlap = 50) {
7
+ const chunks = []
8
+ let start = 0
9
+ while (start < text.length) {
10
+ const end = Math.min(start + chunkSize, text.length)
11
+ chunks.push(text.slice(start, end))
12
+ start += chunkSize - overlap
13
+ }
14
+ return chunks
15
+ }
16
+
17
+ export async function ingestDocument(file, onProgress) {
18
+ // 0. Validasi ukuran (Max 50MB)
19
+ const MAX_SIZE = 50 * 1024 * 1024
20
+ if (file.size > MAX_SIZE) {
21
+ throw new Error('Ukuran file terlalu besar. Maksimal 50MB.')
22
+ }
23
+
24
+ // 0.5. Handling duplikat
25
+ const existingDocs = await getAllDocuments()
26
+ const isDuplicate = existingDocs.some(d => d.docName === file.name)
27
+
28
+ if (isDuplicate) {
29
+ // Hapus dokumen lama dulu
30
+ await deleteDocumentByName(file.name)
31
+ await deleteDocumentFromOrama(file.name)
32
+ }
33
+
34
+ // 1. Ekstrak teks
35
+ let rawText = ''
36
+
37
+ if (file.name.endsWith('.pdf')) {
38
+ const buf = await file.arrayBuffer()
39
+ rawText = await webApi.parseDocument(buf, false)
40
+ } else if (file.name.endsWith('.docx')) {
41
+ const buf = await file.arrayBuffer()
42
+ rawText = await webApi.parseDocument(buf, true)
43
+ } else {
44
+ rawText = await file.text()
45
+ }
46
+
47
+ if (!rawText || !rawText.trim()) {
48
+ throw new Error('Dokumen kosong atau tidak terbaca.')
49
+ }
50
+
51
+ // 2. Chunking
52
+ const chunks = splitTextIntoChunks(rawText, 500, 50)
53
+
54
+ // 3. Embed + Simpan (Database & Orama)
55
+ const dbRecords = []
56
+
57
+ for (let i = 0; i < chunks.length; i++) {
58
+ const vector = await generateVector(chunks[i])
59
+ if (!vector) continue
60
+
61
+ const record = {
62
+ docName: file.name,
63
+ chunkIndex: i,
64
+ content: chunks[i],
65
+ timestamp: Date.now(),
66
+ vector
67
+ }
68
+ dbRecords.push(record)
69
+
70
+ if (onProgress) {
71
+ onProgress(Math.round(((i + 1) / chunks.length) * 100))
72
+ }
73
+ }
74
+
75
+ if (dbRecords.length === 0) {
76
+ throw new Error('Gagal mengekstrak vektor dari dokumen.')
77
+ }
78
+
79
+ // Bulk insert ke SQLite Database
80
+ const ids = await bulkInsertDocuments(dbRecords)
81
+
82
+ // Bulk insert ke Orama (dengan dbId)
83
+ const oramaData = dbRecords.map((r, i) => ({ ...r, dbId: String(ids[i]) }))
84
+ await insertDocumentChunksToOrama(oramaData)
85
+
86
+ return { fileName: file.name, totalChunks: chunks.length, totalCharacters: rawText.length }
87
+ }