@meechi-ai/core 1.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 +624 -0
- package/README.md +59 -0
- package/dist/components/CalendarView.d.ts +3 -0
- package/dist/components/CalendarView.js +72 -0
- package/dist/components/ChatInterface.d.ts +6 -0
- package/dist/components/ChatInterface.js +105 -0
- package/dist/components/FileExplorer.d.ts +9 -0
- package/dist/components/FileExplorer.js +757 -0
- package/dist/components/Icon.d.ts +9 -0
- package/dist/components/Icon.js +44 -0
- package/dist/components/SourceEditor.d.ts +13 -0
- package/dist/components/SourceEditor.js +50 -0
- package/dist/components/ThemeProvider.d.ts +5 -0
- package/dist/components/ThemeProvider.js +105 -0
- package/dist/components/ThemeSwitcher.d.ts +1 -0
- package/dist/components/ThemeSwitcher.js +16 -0
- package/dist/components/voice/VoiceInputArea.d.ts +14 -0
- package/dist/components/voice/VoiceInputArea.js +190 -0
- package/dist/components/voice/VoiceOverlay.d.ts +7 -0
- package/dist/components/voice/VoiceOverlay.js +71 -0
- package/dist/hooks/useMeechi.d.ts +16 -0
- package/dist/hooks/useMeechi.js +461 -0
- package/dist/hooks/useSync.d.ts +8 -0
- package/dist/hooks/useSync.js +87 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +22 -0
- package/dist/lib/ai/embeddings.d.ts +15 -0
- package/dist/lib/ai/embeddings.js +128 -0
- package/dist/lib/ai/gpu-lock.d.ts +19 -0
- package/dist/lib/ai/gpu-lock.js +43 -0
- package/dist/lib/ai/llm.worker.d.ts +1 -0
- package/dist/lib/ai/llm.worker.js +7 -0
- package/dist/lib/ai/local-llm.d.ts +30 -0
- package/dist/lib/ai/local-llm.js +211 -0
- package/dist/lib/ai/manager.d.ts +20 -0
- package/dist/lib/ai/manager.js +51 -0
- package/dist/lib/ai/parsing.d.ts +12 -0
- package/dist/lib/ai/parsing.js +56 -0
- package/dist/lib/ai/prompts.d.ts +2 -0
- package/dist/lib/ai/prompts.js +2 -0
- package/dist/lib/ai/providers/gemini.d.ts +6 -0
- package/dist/lib/ai/providers/gemini.js +88 -0
- package/dist/lib/ai/providers/groq.d.ts +6 -0
- package/dist/lib/ai/providers/groq.js +42 -0
- package/dist/lib/ai/registry.d.ts +29 -0
- package/dist/lib/ai/registry.js +52 -0
- package/dist/lib/ai/tools.d.ts +2 -0
- package/dist/lib/ai/tools.js +106 -0
- package/dist/lib/ai/types.d.ts +22 -0
- package/dist/lib/ai/types.js +1 -0
- package/dist/lib/ai/worker.d.ts +1 -0
- package/dist/lib/ai/worker.js +60 -0
- package/dist/lib/audio/input.d.ts +13 -0
- package/dist/lib/audio/input.js +121 -0
- package/dist/lib/audio/stt.d.ts +13 -0
- package/dist/lib/audio/stt.js +119 -0
- package/dist/lib/audio/tts.d.ts +12 -0
- package/dist/lib/audio/tts.js +128 -0
- package/dist/lib/audio/vad.d.ts +18 -0
- package/dist/lib/audio/vad.js +117 -0
- package/dist/lib/colors.d.ts +16 -0
- package/dist/lib/colors.js +67 -0
- package/dist/lib/extensions.d.ts +35 -0
- package/dist/lib/extensions.js +24 -0
- package/dist/lib/hooks/use-voice-loop.d.ts +13 -0
- package/dist/lib/hooks/use-voice-loop.js +313 -0
- package/dist/lib/mcp/McpClient.d.ts +19 -0
- package/dist/lib/mcp/McpClient.js +42 -0
- package/dist/lib/mcp/McpRegistry.d.ts +47 -0
- package/dist/lib/mcp/McpRegistry.js +117 -0
- package/dist/lib/mcp/native/GroqVoiceNative.d.ts +21 -0
- package/dist/lib/mcp/native/GroqVoiceNative.js +29 -0
- package/dist/lib/mcp/native/LocalSyncNative.d.ts +19 -0
- package/dist/lib/mcp/native/LocalSyncNative.js +26 -0
- package/dist/lib/mcp/native/LocalVoiceNative.d.ts +19 -0
- package/dist/lib/mcp/native/LocalVoiceNative.js +27 -0
- package/dist/lib/mcp/native/MeechiNativeCore.d.ts +25 -0
- package/dist/lib/mcp/native/MeechiNativeCore.js +209 -0
- package/dist/lib/mcp/native/index.d.ts +10 -0
- package/dist/lib/mcp/native/index.js +10 -0
- package/dist/lib/mcp/types.d.ts +35 -0
- package/dist/lib/mcp/types.js +1 -0
- package/dist/lib/pdf.d.ts +10 -0
- package/dist/lib/pdf.js +142 -0
- package/dist/lib/settings.d.ts +48 -0
- package/dist/lib/settings.js +87 -0
- package/dist/lib/storage/db.d.ts +57 -0
- package/dist/lib/storage/db.js +45 -0
- package/dist/lib/storage/local.d.ts +28 -0
- package/dist/lib/storage/local.js +534 -0
- package/dist/lib/storage/migrate.d.ts +3 -0
- package/dist/lib/storage/migrate.js +122 -0
- package/dist/lib/storage/types.d.ts +66 -0
- package/dist/lib/storage/types.js +1 -0
- package/dist/lib/sync/client-drive.d.ts +9 -0
- package/dist/lib/sync/client-drive.js +69 -0
- package/dist/lib/sync/engine.d.ts +18 -0
- package/dist/lib/sync/engine.js +517 -0
- package/dist/lib/sync/google-drive.d.ts +52 -0
- package/dist/lib/sync/google-drive.js +183 -0
- package/dist/lib/sync/merge.d.ts +1 -0
- package/dist/lib/sync/merge.js +68 -0
- package/dist/lib/yjs/YjsProvider.d.ts +11 -0
- package/dist/lib/yjs/YjsProvider.js +33 -0
- package/dist/lib/yjs/graph.d.ts +11 -0
- package/dist/lib/yjs/graph.js +7 -0
- package/dist/lib/yjs/hooks.d.ts +7 -0
- package/dist/lib/yjs/hooks.js +37 -0
- package/dist/lib/yjs/store.d.ts +4 -0
- package/dist/lib/yjs/store.js +19 -0
- package/dist/lib/yjs/syncGraph.d.ts +1 -0
- package/dist/lib/yjs/syncGraph.js +38 -0
- package/dist/providers/theme-provider.d.ts +3 -0
- package/dist/providers/theme-provider.js +18 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export class GoogleDriveClient {
|
|
2
|
+
constructor(accessToken) {
|
|
3
|
+
this.accessToken = accessToken;
|
|
4
|
+
}
|
|
5
|
+
async request(endpoint, options = {}) {
|
|
6
|
+
var _a;
|
|
7
|
+
const url = `https://www.googleapis.com/drive/v3/${endpoint}`;
|
|
8
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' }, (options.headers || {}));
|
|
9
|
+
console.log(`[DriveAPI] ${options.method || 'GET'} ${url}`);
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
const id = setTimeout(() => controller.abort(), 30000); // 30s Timeout
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(url, Object.assign(Object.assign({}, options), { headers, signal: controller.signal }));
|
|
14
|
+
clearTimeout(id);
|
|
15
|
+
if (res.status === 401) {
|
|
16
|
+
console.error(`[DriveAPI] 401 Unauthorized for ${url}`);
|
|
17
|
+
throw new Error("Unauthorized: Token expired or invalid");
|
|
18
|
+
}
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
const err = await res.json().catch(() => ({}));
|
|
21
|
+
console.error(`[DriveAPI] Error ${res.status} for ${url}:`, err);
|
|
22
|
+
throw new Error(`Drive API Error: ${((_a = err.error) === null || _a === void 0 ? void 0 : _a.message) || res.statusText}`);
|
|
23
|
+
}
|
|
24
|
+
return res;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
clearTimeout(id);
|
|
28
|
+
if (error.name === 'AbortError') {
|
|
29
|
+
console.error(`[DriveAPI] Timeout for ${url}`);
|
|
30
|
+
throw new Error("Drive API Timeout (30s)");
|
|
31
|
+
}
|
|
32
|
+
console.error(`[DriveAPI] Fetch Error for ${url}:`, error);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Search for files.
|
|
38
|
+
* query: e.g. "name = 'foo.txt' and trashed = false"
|
|
39
|
+
*/
|
|
40
|
+
async listFiles(query) {
|
|
41
|
+
const params = new URLSearchParams({
|
|
42
|
+
q: query,
|
|
43
|
+
fields: 'files(id, name, mimeType, parents, modifiedTime, version, trashed, capabilities, appProperties)',
|
|
44
|
+
pageSize: '1000',
|
|
45
|
+
});
|
|
46
|
+
const res = await this.request(`files?${params.toString()}`);
|
|
47
|
+
return (await res.json()).files;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get Sync Token for tracking changes
|
|
51
|
+
*/
|
|
52
|
+
async getStartPageToken() {
|
|
53
|
+
const res = await this.request('changes/startPageToken');
|
|
54
|
+
return (await res.json()).startPageToken;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* List changes since token
|
|
58
|
+
*/
|
|
59
|
+
async listChanges(pageToken) {
|
|
60
|
+
const params = new URLSearchParams({
|
|
61
|
+
pageToken,
|
|
62
|
+
fields: 'nextPageToken, newStartPageToken, changes(fileId, removed, file(id, name, mimeType, parents, modifiedTime, version, trashed, appProperties))',
|
|
63
|
+
});
|
|
64
|
+
const res = await this.request(`changes?${params.toString()}`);
|
|
65
|
+
return await res.json();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Download file content
|
|
69
|
+
*/
|
|
70
|
+
async downloadFile(fileId) {
|
|
71
|
+
const res = await this.request(`files/${fileId}?alt=media`);
|
|
72
|
+
return await res.text();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Download file content as Binary (ArrayBuffer)
|
|
76
|
+
*/
|
|
77
|
+
async downloadBinary(fileId) {
|
|
78
|
+
const res = await this.request(`files/${fileId}?alt=media`);
|
|
79
|
+
return await res.arrayBuffer();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get file metadata
|
|
83
|
+
*/
|
|
84
|
+
async getFileMetadata(fileId) {
|
|
85
|
+
const res = await this.request(`files/${fileId}?fields=id,name,parents,mimeType,modifiedTime,trashed,appProperties`);
|
|
86
|
+
return await res.json();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Update file content
|
|
90
|
+
*/
|
|
91
|
+
async updateMetadata(fileId, metadata) {
|
|
92
|
+
const url = new URL(`https://www.googleapis.com/drive/v3/files/${fileId}`);
|
|
93
|
+
if (metadata.addParents)
|
|
94
|
+
url.searchParams.append('addParents', metadata.addParents.join(','));
|
|
95
|
+
if (metadata.removeParents)
|
|
96
|
+
url.searchParams.append('removeParents', metadata.removeParents.join(',')); // Important: remove old parents for move
|
|
97
|
+
const body = {};
|
|
98
|
+
if (metadata.name)
|
|
99
|
+
body.name = metadata.name;
|
|
100
|
+
if (metadata.appProperties)
|
|
101
|
+
body.appProperties = metadata.appProperties;
|
|
102
|
+
const res = await this.request(url.toString().replace('https://www.googleapis.com/drive/v3/', ''), {
|
|
103
|
+
method: 'PATCH',
|
|
104
|
+
body: JSON.stringify(body)
|
|
105
|
+
});
|
|
106
|
+
return await res.json();
|
|
107
|
+
}
|
|
108
|
+
async updateFile(fileId, content) {
|
|
109
|
+
// Simple upload (media only) is easiest for updates, but multipart is robust.
|
|
110
|
+
// For updates, we often just want to update content.
|
|
111
|
+
// Google Drive API supports PATCHing content.
|
|
112
|
+
// Ref: https://developers.google.com/drive/api/guides/manage-uploads#simple
|
|
113
|
+
const blob = content instanceof Blob ? content : new Blob([content]);
|
|
114
|
+
const res = await fetch(`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`, {
|
|
115
|
+
method: 'PATCH',
|
|
116
|
+
headers: {
|
|
117
|
+
'Authorization': `Bearer ${this.accessToken}`,
|
|
118
|
+
'Content-Type': blob.type || 'application/octet-stream'
|
|
119
|
+
},
|
|
120
|
+
body: blob
|
|
121
|
+
});
|
|
122
|
+
if (!res.ok)
|
|
123
|
+
throw new Error("Update content failed");
|
|
124
|
+
return await res.json();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create file
|
|
128
|
+
*/
|
|
129
|
+
async createFile(name, folderId, content, appProperties) {
|
|
130
|
+
let mimeType = 'application/octet-stream';
|
|
131
|
+
if (name.endsWith('.md'))
|
|
132
|
+
mimeType = 'text/markdown';
|
|
133
|
+
else if (name.endsWith('.txt'))
|
|
134
|
+
mimeType = 'text/plain';
|
|
135
|
+
else if (name.endsWith('.pdf'))
|
|
136
|
+
mimeType = 'application/pdf';
|
|
137
|
+
const blob = content instanceof Blob ? content : new Blob([content], { type: mimeType });
|
|
138
|
+
const metadata = {
|
|
139
|
+
name,
|
|
140
|
+
parents: folderId ? [folderId] : [],
|
|
141
|
+
};
|
|
142
|
+
if (appProperties)
|
|
143
|
+
metadata.appProperties = appProperties;
|
|
144
|
+
const form = new FormData();
|
|
145
|
+
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
|
|
146
|
+
form.append('file', blob);
|
|
147
|
+
const res = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: {
|
|
150
|
+
'Authorization': `Bearer ${this.accessToken}`,
|
|
151
|
+
},
|
|
152
|
+
body: form
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok)
|
|
155
|
+
throw new Error("Create failed");
|
|
156
|
+
return await res.json();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Create Folder
|
|
160
|
+
*/
|
|
161
|
+
async createFolder(name, parentId) {
|
|
162
|
+
const metadata = {
|
|
163
|
+
name,
|
|
164
|
+
mimeType: 'application/vnd.google-apps.folder',
|
|
165
|
+
parents: parentId ? [parentId] : []
|
|
166
|
+
};
|
|
167
|
+
const res = await this.request('files', {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: JSON.stringify(metadata)
|
|
170
|
+
});
|
|
171
|
+
return await res.json();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Delete file (Trash)
|
|
175
|
+
*/
|
|
176
|
+
async deleteFile(fileId) {
|
|
177
|
+
const res = await this.request(`files/${fileId}`, {
|
|
178
|
+
method: 'DELETE'
|
|
179
|
+
});
|
|
180
|
+
if (!res.ok)
|
|
181
|
+
throw new Error("Delete failed");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mergeLogs(local: string, remote: string): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function mergeLogs(local, remote) {
|
|
2
|
+
const localEntries = parseEntries(local);
|
|
3
|
+
const remoteEntries = parseEntries(remote);
|
|
4
|
+
// Merge map: Key = "Timestamp|ContentHash" -> Entry
|
|
5
|
+
const merged = new Map();
|
|
6
|
+
// Add Remote first (Truth)
|
|
7
|
+
remoteEntries.forEach(entry => {
|
|
8
|
+
const key = generateKey(entry);
|
|
9
|
+
merged.set(key, entry.fullText);
|
|
10
|
+
});
|
|
11
|
+
// Add Local (Append/Fill gaps)
|
|
12
|
+
localEntries.forEach(entry => {
|
|
13
|
+
const key = generateKey(entry);
|
|
14
|
+
if (!merged.has(key)) {
|
|
15
|
+
merged.set(key, entry.fullText);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
// Sort by Timestamp
|
|
19
|
+
const sortedEntries = Array.from(merged.values()).sort((a, b) => {
|
|
20
|
+
const timeA = extractDate(a);
|
|
21
|
+
const timeB = extractDate(b);
|
|
22
|
+
return timeA.getTime() - timeB.getTime();
|
|
23
|
+
});
|
|
24
|
+
return sortedEntries.join('\n\n');
|
|
25
|
+
}
|
|
26
|
+
// Helpers
|
|
27
|
+
function parseEntries(log) {
|
|
28
|
+
if (!log)
|
|
29
|
+
return [];
|
|
30
|
+
return log.split('### ').filter(c => c.trim()).map(chunk => {
|
|
31
|
+
// Re-add delimiter for reconstruction
|
|
32
|
+
const fullText = '### ' + chunk.trim();
|
|
33
|
+
const lines = chunk.split('\n');
|
|
34
|
+
// Timestamp is the first line after ###
|
|
35
|
+
const timestampStr = lines[0].trim();
|
|
36
|
+
// We assume today's date for sorting relative to each other (since files are per day)
|
|
37
|
+
// Ideally we'd parse the full date if available, but time string works for daily logs
|
|
38
|
+
return {
|
|
39
|
+
timestampStr,
|
|
40
|
+
fullText
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function generateKey(entry) {
|
|
45
|
+
// Simple key: Time + First 20 chars of content (to distinguish different messages at same second)
|
|
46
|
+
const contentSignature = entry.fullText.replace(/\s/g, '').slice(0, 50);
|
|
47
|
+
return `${entry.timestampStr}|${contentSignature}`;
|
|
48
|
+
}
|
|
49
|
+
function extractDate(entryText) {
|
|
50
|
+
// Extract "12:00:00 PM"
|
|
51
|
+
const match = entryText.match(/### (.*?)(\n|$)/);
|
|
52
|
+
if (!match)
|
|
53
|
+
return new Date(0);
|
|
54
|
+
const timeStr = match[1];
|
|
55
|
+
// Parse Time String (assuming today context, but really we just need relative sort)
|
|
56
|
+
// "12:00:00 PM" -> Date
|
|
57
|
+
const d = new Date();
|
|
58
|
+
const [time, period] = timeStr.split(' ');
|
|
59
|
+
if (!time)
|
|
60
|
+
return new Date(0);
|
|
61
|
+
let [hours, minutes, seconds] = time.split(':').map(Number);
|
|
62
|
+
if (period === 'PM' && hours !== 12)
|
|
63
|
+
hours += 12;
|
|
64
|
+
if (period === 'AM' && hours === 12)
|
|
65
|
+
hours = 0;
|
|
66
|
+
d.setHours(hours || 0, minutes || 0, seconds || 0);
|
|
67
|
+
return d;
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as Y from 'yjs';
|
|
3
|
+
interface YjsContextType {
|
|
4
|
+
doc: Y.Doc;
|
|
5
|
+
synced: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function YjsProvider({ children }: {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare const useYjs: () => YjsContextType;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
4
|
+
import { doc, yProvider } from './store';
|
|
5
|
+
const YjsContext = createContext({
|
|
6
|
+
doc,
|
|
7
|
+
synced: false,
|
|
8
|
+
});
|
|
9
|
+
export function YjsProvider({ children }) {
|
|
10
|
+
const [synced, setSynced] = useState(false);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!yProvider) {
|
|
13
|
+
// Server-side or non-browser environment
|
|
14
|
+
setSynced(true);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const onSynced = () => setSynced(true);
|
|
18
|
+
// Check if already synced
|
|
19
|
+
if (yProvider.synced) {
|
|
20
|
+
setSynced(true);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
yProvider.on('synced', onSynced);
|
|
24
|
+
}
|
|
25
|
+
return () => {
|
|
26
|
+
if (yProvider) {
|
|
27
|
+
yProvider.off('synced', onSynced);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
return (_jsx(YjsContext.Provider, { value: { doc, synced }, children: children }));
|
|
32
|
+
}
|
|
33
|
+
export const useYjs = () => useContext(YjsContext);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useYjs } from './YjsProvider';
|
|
4
|
+
export function useYDoc() {
|
|
5
|
+
const { doc, synced } = useYjs();
|
|
6
|
+
return { doc, synced };
|
|
7
|
+
}
|
|
8
|
+
export function useYMap(name) {
|
|
9
|
+
const { doc } = useYjs();
|
|
10
|
+
const map = doc.getMap(name);
|
|
11
|
+
const [toJSON, setToJSON] = useState(map.toJSON());
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const onChange = () => {
|
|
14
|
+
setToJSON(map.toJSON());
|
|
15
|
+
};
|
|
16
|
+
map.observe(onChange);
|
|
17
|
+
return () => {
|
|
18
|
+
map.unobserve(onChange);
|
|
19
|
+
};
|
|
20
|
+
}, [map]);
|
|
21
|
+
return [map, toJSON];
|
|
22
|
+
}
|
|
23
|
+
export function useYArray(name) {
|
|
24
|
+
const { doc } = useYjs();
|
|
25
|
+
const array = doc.getArray(name);
|
|
26
|
+
const [toJSON, setToJSON] = useState(array.toArray());
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const onChange = () => {
|
|
29
|
+
setToJSON(array.toArray());
|
|
30
|
+
};
|
|
31
|
+
array.observe(onChange);
|
|
32
|
+
return () => {
|
|
33
|
+
array.unobserve(onChange);
|
|
34
|
+
};
|
|
35
|
+
}, [array]);
|
|
36
|
+
return [array, toJSON];
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { IndexeddbPersistence } from 'y-indexeddb';
|
|
3
|
+
// Create the global Yjs document
|
|
4
|
+
export const doc = new Y.Doc();
|
|
5
|
+
// Initialize the persistence layer (Browser only)
|
|
6
|
+
let persistence = null;
|
|
7
|
+
if (typeof window !== 'undefined') {
|
|
8
|
+
// 'meechi-database' matches our app name logic, but distinct from Dexie which is 'meechi-db' usually
|
|
9
|
+
persistence = new IndexeddbPersistence('meechi-yjs-store', doc);
|
|
10
|
+
persistence.on('synced', () => {
|
|
11
|
+
console.log('[Yjs] Content loaded from IndexedDB');
|
|
12
|
+
});
|
|
13
|
+
// Initialize Graph Sync (Yjs -> Dexie)
|
|
14
|
+
// We do this here to ensure it runs as a side-effect of the app starting
|
|
15
|
+
import('./syncGraph').then(({ initGraphSync }) => {
|
|
16
|
+
initGraphSync();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export const yProvider = persistence;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initGraphSync(): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { db } from '../storage/db';
|
|
2
|
+
import { getEdgesMap } from './graph';
|
|
3
|
+
// Sync Yjs Graph Edges -> Dexie 'edges' table
|
|
4
|
+
// This allows us to use Dexie for fast GraphRAG querying (e.g. "Find all neighbors of Ada")
|
|
5
|
+
// while keeping Yjs as the source of truth for sync.
|
|
6
|
+
export function initGraphSync() {
|
|
7
|
+
console.log('[GraphSync] Initializing...');
|
|
8
|
+
const edgesMap = getEdgesMap();
|
|
9
|
+
// 1. Initial Sync: Ensure specific indexes match if needed, or just rely on eventual consistency.
|
|
10
|
+
// For now, we trust the observe events or a full re-sync if strictly needed.
|
|
11
|
+
// Ideally, Yjs persistence loads the doc, and then we might want to dump to Dexie if Dexie is empty.
|
|
12
|
+
// But since Yjs persistence goes to IndexedDB anyway, this is "Derived Data".
|
|
13
|
+
// We observe changes.
|
|
14
|
+
edgesMap.observe((event) => {
|
|
15
|
+
// event.keysChanged is a Set of keys (Edge IDs) that changed
|
|
16
|
+
const changes = Array.from(event.keysChanged);
|
|
17
|
+
db.transaction('rw', db.edges, async () => {
|
|
18
|
+
for (const key of changes) {
|
|
19
|
+
const type = event.changes.keys.get(key);
|
|
20
|
+
// type.action is 'add', 'update', or 'delete'
|
|
21
|
+
if ((type === null || type === void 0 ? void 0 : type.action) === 'delete') {
|
|
22
|
+
// Deleted from Yjs -> Delete from Dexie
|
|
23
|
+
await db.edges.delete(key);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Added or Updated -> Put to Dexie
|
|
27
|
+
const edge = edgesMap.get(key);
|
|
28
|
+
if (edge) {
|
|
29
|
+
await db.edges.put(edge);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}).catch(err => {
|
|
34
|
+
console.error('[GraphSync] Error syncing to Dexie:', err);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
console.log('[GraphSync] Listening for changes.');
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
15
|
+
export function ThemeProvider(_a) {
|
|
16
|
+
var { children } = _a, props = __rest(_a, ["children"]);
|
|
17
|
+
return _jsx(NextThemesProvider, Object.assign({}, props, { children: children }));
|
|
18
|
+
}
|