@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.
- package/LICENSE +29 -0
- package/README.md +209 -0
- package/bin/mark.js +27 -0
- package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
- package/out/renderer/assets/index-G2fADWO-.js +4768 -0
- package/out/renderer/assets/index-f3mVse3c.css +1 -0
- package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
- package/out/renderer/favicon.ico +0 -0
- package/out/renderer/icon-256.png +0 -0
- package/out/renderer/icon.svg +1 -0
- package/out/renderer/index.html +25 -0
- package/out/renderer/manifest.json +25 -0
- package/out/renderer/robot-simulator.html +1640 -0
- package/package.json +106 -0
- package/src/cli/index.js +332 -0
- package/src/cli/monitor.js +413 -0
- package/src/cli/theme.js +347 -0
- package/src/launcher/MarkTray.cs +227 -0
- package/src/main/browser-agent.js +1132 -0
- package/src/main/git-service.js +90 -0
- package/src/main/google/google-calendar.js +69 -0
- package/src/main/google/google-drive.js +263 -0
- package/src/main/google/google-gmail.js +131 -0
- package/src/main/google/google-service.js +241 -0
- package/src/main/node-tools.js +23 -0
- package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
- package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
- package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
- package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
- package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
- package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
- package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
- package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
- package/src/main/plugins/plugin-loader.js +306 -0
- package/src/main/skills/skill-manager.js +265 -0
- package/src/main/syntax-validator.js +455 -0
- package/src/main/task-daemon.js +117 -0
- package/src/main/telegram/telegram-service.js +835 -0
- package/src/main/tools/browser-tools.js +516 -0
- package/src/main/tools/file-tools.js +1126 -0
- package/src/main/tools/git-tools.js +79 -0
- package/src/main/tools/google-tools.js +566 -0
- package/src/main/tools/index.js +36 -0
- package/src/main/tools/media-tools.js +42 -0
- package/src/main/tools/system-tools.js +295 -0
- package/src/main/tools/task-tools.js +73 -0
- package/src/main/tools/telegram-tools.js +101 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/public/favicon.ico +0 -0
- package/src/renderer/public/icon-256.png +0 -0
- package/src/renderer/public/icon.svg +1 -0
- package/src/renderer/public/manifest.json +25 -0
- package/src/renderer/public/robot-simulator.html +1640 -0
- package/src/renderer/src/App.jsx +304 -0
- package/src/renderer/src/api/ai/awareness.js +294 -0
- package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
- package/src/renderer/src/api/ai/contextCompactor.js +129 -0
- package/src/renderer/src/api/ai/contextManager.js +1130 -0
- package/src/renderer/src/api/ai/core.js +167 -0
- package/src/renderer/src/api/ai/index.js +4 -0
- package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
- package/src/renderer/src/api/ai/persona.js +115 -0
- package/src/renderer/src/api/ai/planning.js +317 -0
- package/src/renderer/src/api/ai/relationship.js +177 -0
- package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
- package/src/renderer/src/api/ai/tools.js +167 -0
- package/src/renderer/src/api/ai/utils.js +359 -0
- package/src/renderer/src/api/db.js +1013 -0
- package/src/renderer/src/api/oramaStore.js +592 -0
- package/src/renderer/src/api/ragPipeline.js +87 -0
- package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
- package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
- package/src/renderer/src/api/subagent/subagentStore.js +173 -0
- package/src/renderer/src/api/taskExecutor.js +18 -0
- package/src/renderer/src/api/taskStore.js +315 -0
- package/src/renderer/src/api/tools/core-tools.js +707 -0
- package/src/renderer/src/api/tools/index.js +151 -0
- package/src/renderer/src/api/turnPairMigrator.js +200 -0
- package/src/renderer/src/api/vectorMemory.js +136 -0
- package/src/renderer/src/api/wakeWord.js +101 -0
- package/src/renderer/src/api/web-bridge.js +885 -0
- package/src/renderer/src/api/webSpeech.js +178 -0
- package/src/renderer/src/api/workspaceRag.js +76 -0
- package/src/renderer/src/assets/electron.svg +10 -0
- package/src/renderer/src/assets/icon.svg +1 -0
- package/src/renderer/src/assets/main.css +589 -0
- package/src/renderer/src/assets/music-cover.png +0 -0
- package/src/renderer/src/assets/wavy-lines.svg +25 -0
- package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
- package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
- package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
- package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
- package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
- package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
- package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
- package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
- package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
- package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
- package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
- package/src/renderer/src/components/Chat/index.js +11 -0
- package/src/renderer/src/components/ChatList.jsx +434 -0
- package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
- package/src/renderer/src/components/Versions.jsx +15 -0
- package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
- package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
- package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
- package/src/renderer/src/components/config/CameraSection.jsx +100 -0
- package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
- package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
- package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
- package/src/renderer/src/components/config/index.js +5 -0
- package/src/renderer/src/components/core/Avatar.jsx +2197 -0
- package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
- package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
- package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
- package/src/renderer/src/components/core/HoloCard.jsx +64 -0
- package/src/renderer/src/components/core/InputBar.jsx +1346 -0
- package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
- package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
- package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
- package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
- package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
- package/src/renderer/src/components/core/native-skills.js +43 -0
- package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
- package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
- package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
- package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
- package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
- package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
- package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
- package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
- package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
- package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
- package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
- package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
- package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
- package/src/renderer/src/contexts/ChatContext.jsx +61 -0
- package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
- package/src/renderer/src/data/faqData.js +202 -0
- package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
- package/src/renderer/src/hooks/agent/index.js +4 -0
- package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
- package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
- package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
- package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
- package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
- package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
- package/src/renderer/src/hooks/useAwareness.js +289 -0
- package/src/renderer/src/hooks/useChatArchiver.js +52 -0
- package/src/renderer/src/hooks/useConfirm.jsx +68 -0
- package/src/renderer/src/hooks/useMarkAgent.js +454 -0
- package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
- package/src/renderer/src/hooks/useVAD.js +511 -0
- package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
- package/src/renderer/src/main.jsx +12 -0
- package/src/renderer/src/pages/ChatStudio.jsx +922 -0
- package/src/renderer/src/pages/Configuration.jsx +360 -0
- package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
- package/src/renderer/src/pages/Guidebook.jsx +917 -0
- package/src/renderer/src/pages/Knowledge.jsx +241 -0
- package/src/renderer/src/pages/MarkHome.jsx +491 -0
- package/src/renderer/src/pages/NeuralCore.jsx +786 -0
- package/src/renderer/src/pages/Plugins.jsx +630 -0
- package/src/renderer/src/pages/SkillEditor.jsx +380 -0
- package/src/renderer/src/pages/Skills.jsx +219 -0
- package/src/renderer/src/pages/Subagents.jsx +342 -0
- package/src/renderer/src/pages/TelegramBot.jsx +343 -0
- package/src/renderer/src/utils/chatContentParser.js +203 -0
- package/src/renderer/src/utils/toolClusters.js +201 -0
- package/src/server/bin/sha3_wasm_bg.wasm +0 -0
- package/src/server/config-manager.js +57 -0
- package/src/server/index.js +195 -0
- package/src/server/launcher.js +87 -0
- package/src/server/memory/db-store.js +758 -0
- package/src/server/memory/orama-store.js +177 -0
- package/src/server/memory/vector-engine.js +109 -0
- package/src/server/routes/ai.routes.js +280 -0
- package/src/server/routes/awareness.routes.js +62 -0
- package/src/server/routes/chat.routes.js +61 -0
- package/src/server/routes/config.routes.js +41 -0
- package/src/server/routes/index.js +35 -0
- package/src/server/routes/integrations.routes.js +116 -0
- package/src/server/routes/memory.routes.js +256 -0
- package/src/server/routes/plugins.routes.js +72 -0
- package/src/server/routes/skills.routes.js +139 -0
- package/src/server/routes/subagents.routes.js +70 -0
- package/src/server/routes/tasks.routes.js +175 -0
- package/src/server/routes/workspace.routes.js +127 -0
- package/src/server/services/ai/ai-utils.js +161 -0
- package/src/server/services/ai/openai-provider.js +757 -0
- package/src/server/services/ai/web-provider.js +729 -0
- package/src/server/services/ai-bridge.js +46 -0
- package/src/server/services/deepseek-web.js +956 -0
- package/src/server/services/gemini-web.js +170 -0
- package/src/server/services/notification-service.js +91 -0
- package/src/server/services/updater.js +125 -0
- package/src/server/tools/awareness-tracker.js +292 -0
- package/src/server/tools/group-tools.js +1164 -0
- package/src/server/tools/media-tools.js +244 -0
- package/src/server/tools/pc-agent.js +327 -0
- package/src/server/tools/screen-service.js +51 -0
- package/src/server/ws-hub.js +168 -0
- 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
|
+
}
|