@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.
Files changed (116) hide show
  1. package/LICENSE +624 -0
  2. package/README.md +59 -0
  3. package/dist/components/CalendarView.d.ts +3 -0
  4. package/dist/components/CalendarView.js +72 -0
  5. package/dist/components/ChatInterface.d.ts +6 -0
  6. package/dist/components/ChatInterface.js +105 -0
  7. package/dist/components/FileExplorer.d.ts +9 -0
  8. package/dist/components/FileExplorer.js +757 -0
  9. package/dist/components/Icon.d.ts +9 -0
  10. package/dist/components/Icon.js +44 -0
  11. package/dist/components/SourceEditor.d.ts +13 -0
  12. package/dist/components/SourceEditor.js +50 -0
  13. package/dist/components/ThemeProvider.d.ts +5 -0
  14. package/dist/components/ThemeProvider.js +105 -0
  15. package/dist/components/ThemeSwitcher.d.ts +1 -0
  16. package/dist/components/ThemeSwitcher.js +16 -0
  17. package/dist/components/voice/VoiceInputArea.d.ts +14 -0
  18. package/dist/components/voice/VoiceInputArea.js +190 -0
  19. package/dist/components/voice/VoiceOverlay.d.ts +7 -0
  20. package/dist/components/voice/VoiceOverlay.js +71 -0
  21. package/dist/hooks/useMeechi.d.ts +16 -0
  22. package/dist/hooks/useMeechi.js +461 -0
  23. package/dist/hooks/useSync.d.ts +8 -0
  24. package/dist/hooks/useSync.js +87 -0
  25. package/dist/index.d.ts +14 -0
  26. package/dist/index.js +22 -0
  27. package/dist/lib/ai/embeddings.d.ts +15 -0
  28. package/dist/lib/ai/embeddings.js +128 -0
  29. package/dist/lib/ai/gpu-lock.d.ts +19 -0
  30. package/dist/lib/ai/gpu-lock.js +43 -0
  31. package/dist/lib/ai/llm.worker.d.ts +1 -0
  32. package/dist/lib/ai/llm.worker.js +7 -0
  33. package/dist/lib/ai/local-llm.d.ts +30 -0
  34. package/dist/lib/ai/local-llm.js +211 -0
  35. package/dist/lib/ai/manager.d.ts +20 -0
  36. package/dist/lib/ai/manager.js +51 -0
  37. package/dist/lib/ai/parsing.d.ts +12 -0
  38. package/dist/lib/ai/parsing.js +56 -0
  39. package/dist/lib/ai/prompts.d.ts +2 -0
  40. package/dist/lib/ai/prompts.js +2 -0
  41. package/dist/lib/ai/providers/gemini.d.ts +6 -0
  42. package/dist/lib/ai/providers/gemini.js +88 -0
  43. package/dist/lib/ai/providers/groq.d.ts +6 -0
  44. package/dist/lib/ai/providers/groq.js +42 -0
  45. package/dist/lib/ai/registry.d.ts +29 -0
  46. package/dist/lib/ai/registry.js +52 -0
  47. package/dist/lib/ai/tools.d.ts +2 -0
  48. package/dist/lib/ai/tools.js +106 -0
  49. package/dist/lib/ai/types.d.ts +22 -0
  50. package/dist/lib/ai/types.js +1 -0
  51. package/dist/lib/ai/worker.d.ts +1 -0
  52. package/dist/lib/ai/worker.js +60 -0
  53. package/dist/lib/audio/input.d.ts +13 -0
  54. package/dist/lib/audio/input.js +121 -0
  55. package/dist/lib/audio/stt.d.ts +13 -0
  56. package/dist/lib/audio/stt.js +119 -0
  57. package/dist/lib/audio/tts.d.ts +12 -0
  58. package/dist/lib/audio/tts.js +128 -0
  59. package/dist/lib/audio/vad.d.ts +18 -0
  60. package/dist/lib/audio/vad.js +117 -0
  61. package/dist/lib/colors.d.ts +16 -0
  62. package/dist/lib/colors.js +67 -0
  63. package/dist/lib/extensions.d.ts +35 -0
  64. package/dist/lib/extensions.js +24 -0
  65. package/dist/lib/hooks/use-voice-loop.d.ts +13 -0
  66. package/dist/lib/hooks/use-voice-loop.js +313 -0
  67. package/dist/lib/mcp/McpClient.d.ts +19 -0
  68. package/dist/lib/mcp/McpClient.js +42 -0
  69. package/dist/lib/mcp/McpRegistry.d.ts +47 -0
  70. package/dist/lib/mcp/McpRegistry.js +117 -0
  71. package/dist/lib/mcp/native/GroqVoiceNative.d.ts +21 -0
  72. package/dist/lib/mcp/native/GroqVoiceNative.js +29 -0
  73. package/dist/lib/mcp/native/LocalSyncNative.d.ts +19 -0
  74. package/dist/lib/mcp/native/LocalSyncNative.js +26 -0
  75. package/dist/lib/mcp/native/LocalVoiceNative.d.ts +19 -0
  76. package/dist/lib/mcp/native/LocalVoiceNative.js +27 -0
  77. package/dist/lib/mcp/native/MeechiNativeCore.d.ts +25 -0
  78. package/dist/lib/mcp/native/MeechiNativeCore.js +209 -0
  79. package/dist/lib/mcp/native/index.d.ts +10 -0
  80. package/dist/lib/mcp/native/index.js +10 -0
  81. package/dist/lib/mcp/types.d.ts +35 -0
  82. package/dist/lib/mcp/types.js +1 -0
  83. package/dist/lib/pdf.d.ts +10 -0
  84. package/dist/lib/pdf.js +142 -0
  85. package/dist/lib/settings.d.ts +48 -0
  86. package/dist/lib/settings.js +87 -0
  87. package/dist/lib/storage/db.d.ts +57 -0
  88. package/dist/lib/storage/db.js +45 -0
  89. package/dist/lib/storage/local.d.ts +28 -0
  90. package/dist/lib/storage/local.js +534 -0
  91. package/dist/lib/storage/migrate.d.ts +3 -0
  92. package/dist/lib/storage/migrate.js +122 -0
  93. package/dist/lib/storage/types.d.ts +66 -0
  94. package/dist/lib/storage/types.js +1 -0
  95. package/dist/lib/sync/client-drive.d.ts +9 -0
  96. package/dist/lib/sync/client-drive.js +69 -0
  97. package/dist/lib/sync/engine.d.ts +18 -0
  98. package/dist/lib/sync/engine.js +517 -0
  99. package/dist/lib/sync/google-drive.d.ts +52 -0
  100. package/dist/lib/sync/google-drive.js +183 -0
  101. package/dist/lib/sync/merge.d.ts +1 -0
  102. package/dist/lib/sync/merge.js +68 -0
  103. package/dist/lib/yjs/YjsProvider.d.ts +11 -0
  104. package/dist/lib/yjs/YjsProvider.js +33 -0
  105. package/dist/lib/yjs/graph.d.ts +11 -0
  106. package/dist/lib/yjs/graph.js +7 -0
  107. package/dist/lib/yjs/hooks.d.ts +7 -0
  108. package/dist/lib/yjs/hooks.js +37 -0
  109. package/dist/lib/yjs/store.d.ts +4 -0
  110. package/dist/lib/yjs/store.js +19 -0
  111. package/dist/lib/yjs/syncGraph.d.ts +1 -0
  112. package/dist/lib/yjs/syncGraph.js +38 -0
  113. package/dist/providers/theme-provider.d.ts +3 -0
  114. package/dist/providers/theme-provider.js +18 -0
  115. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  116. package/package.json +69 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * NATIVE SERVER: LocalVoiceNative
3
+ *
4
+ * Experimental Local Voice capability.
5
+ * Uses client-side ONNX models (Whisper/Kokoro) for private, offline interaction.
6
+ *
7
+ * This is a "capability" native - it exposes UI features rather than AI tools.
8
+ */
9
+ export class LocalVoiceNative {
10
+ constructor() {
11
+ this.id = "native-local-voice";
12
+ this.name = "Native Local Voice (Experimental)";
13
+ this.description = "Privacy-first local voice interaction using browser-based STT (Whisper) and TTS (Kokoro). Experimental.";
14
+ this.isPermanent = false;
15
+ }
16
+ async getTools() {
17
+ // Local voice exposes internal capabilities through standard audio services.
18
+ return [];
19
+ }
20
+ async executeTool(name, args) {
21
+ throw new Error(`Tool ${name} not implemented in LocalVoiceNative`);
22
+ }
23
+ // Indicates this server is capable of providing voice services
24
+ isVoiceCapable() {
25
+ return true;
26
+ }
27
+ }
@@ -0,0 +1,25 @@
1
+ import { McpTool } from '../types';
2
+ import { McpConnector } from '../McpRegistry';
3
+ /**
4
+ * NATIVE SERVER: MeechiNativeCore
5
+ *
6
+ * The permanent, mandatory internal tool provider.
7
+ * Contains core file management and knowledge tools.
8
+ *
9
+ * This is a "Native" server - an in-process TypeScript class that follows
10
+ * the MCP interface pattern but does NOT use the actual MCP protocol
11
+ * (no JSON-RPC, no IPC). It's part of the same runtime as the host.
12
+ */
13
+ export declare class MeechiNativeCore implements McpConnector {
14
+ id: string;
15
+ name: string;
16
+ description: string;
17
+ isPermanent: boolean;
18
+ private tools;
19
+ private storage;
20
+ constructor();
21
+ private registerTools;
22
+ private addTool;
23
+ getTools(): Promise<McpTool[]>;
24
+ executeTool(name: string, args: any): Promise<any>;
25
+ }
@@ -0,0 +1,209 @@
1
+ import { LocalStorageProvider } from '../../storage/local';
2
+ import { extractTextFromPdf } from '../../pdf';
3
+ /**
4
+ * NATIVE SERVER: MeechiNativeCore
5
+ *
6
+ * The permanent, mandatory internal tool provider.
7
+ * Contains core file management and knowledge tools.
8
+ *
9
+ * This is a "Native" server - an in-process TypeScript class that follows
10
+ * the MCP interface pattern but does NOT use the actual MCP protocol
11
+ * (no JSON-RPC, no IPC). It's part of the same runtime as the host.
12
+ */
13
+ export class MeechiNativeCore {
14
+ constructor() {
15
+ this.id = "native-core";
16
+ this.name = "Native Core";
17
+ this.description = "Permanent system tools for file management, PDF reading, and settings.";
18
+ this.isPermanent = true;
19
+ this.tools = new Map();
20
+ this.storage = new LocalStorageProvider();
21
+ this.registerTools();
22
+ }
23
+ registerTools() {
24
+ this.addTool({
25
+ name: "create_file",
26
+ description: "Create a new file in the knowledge base. ONLY use this when the user EXPLICITLY asks to save, create, or store something. NEVER use this spontaneously.",
27
+ inputSchema: {
28
+ type: "object",
29
+ properties: {
30
+ filePath: { type: "string" },
31
+ content: { type: "string" }
32
+ },
33
+ required: ["filePath", "content"]
34
+ },
35
+ handler: async (args) => {
36
+ await this.storage.init();
37
+ let cleanPath = args.filePath.replace(/^[/\\]+/, '').replace(/\\/g, '/');
38
+ if (!cleanPath.startsWith('misc/') && !cleanPath.startsWith('history/')) {
39
+ cleanPath = `misc/${cleanPath}`;
40
+ }
41
+ if (cleanPath.endsWith('.source.md')) {
42
+ throw new Error("Safety Block: Source files are immutable.");
43
+ }
44
+ await this.storage.saveFile(cleanPath, args.content);
45
+ return { success: true, message: `Created ${cleanPath}` };
46
+ }
47
+ });
48
+ this.addTool({
49
+ name: "update_file",
50
+ description: "Update or overwrite an existing file. ONLY use this when the user EXPLICITLY asks to update or modify a file. NEVER use this spontaneously.",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ filePath: { type: "string" },
55
+ newContent: { type: "string" }
56
+ },
57
+ required: ["filePath", "newContent"]
58
+ },
59
+ handler: async (args) => {
60
+ await this.storage.init();
61
+ let cleanPath = args.filePath.replace(/^[/\\]+/, '').replace(/\\/g, '/');
62
+ if (!cleanPath.startsWith('misc/') && !cleanPath.startsWith('history/')) {
63
+ cleanPath = `misc/${cleanPath}`;
64
+ }
65
+ if (cleanPath.endsWith('.source.md')) {
66
+ throw new Error("Safety Block: Source files are immutable.");
67
+ }
68
+ await this.storage.updateFile(cleanPath, args.newContent);
69
+ return { success: true, message: `Updated ${cleanPath}` };
70
+ }
71
+ });
72
+ this.addTool({
73
+ name: "move_file",
74
+ description: "Move or rename a file. ONLY use this when the user EXPLICITLY asks to move or rename a file.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ sourcePath: { type: "string" },
79
+ destinationPath: { type: "string" }
80
+ },
81
+ required: ["sourcePath", "destinationPath"]
82
+ },
83
+ handler: async (args) => {
84
+ await this.storage.init();
85
+ let source = args.sourcePath.replace(/^[/\\]+/, '').replace(/\\/g, '/');
86
+ let dest = args.destinationPath.replace(/^[/\\]+/, '').replace(/\\/g, '/');
87
+ if (!source.startsWith('misc/') && !source.startsWith('history/'))
88
+ source = `misc/${source}`;
89
+ if (!dest.startsWith('misc/') && !dest.startsWith('history/'))
90
+ dest = `misc/${dest}`;
91
+ const content = await this.storage.readFile(source);
92
+ if (!content)
93
+ throw new Error(`Source ${source} not found`);
94
+ await this.storage.saveFile(dest, content);
95
+ await this.storage.deleteFile(source);
96
+ return { success: true, message: `Moved to ${dest}` };
97
+ }
98
+ });
99
+ this.addTool({
100
+ name: "read_pdf",
101
+ description: "Extract text from a PDF file. ONLY use this for files ending in .pdf.",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: { filePath: { type: "string" } },
105
+ required: ["filePath"]
106
+ },
107
+ handler: async (args) => {
108
+ await this.storage.init();
109
+ const fileData = await this.storage.readFile(args.filePath);
110
+ if (!fileData)
111
+ throw new Error("File not found");
112
+ let buffer;
113
+ if (fileData instanceof ArrayBuffer)
114
+ buffer = fileData;
115
+ else if (fileData instanceof Blob)
116
+ buffer = await fileData.arrayBuffer();
117
+ else
118
+ throw new Error("Format error");
119
+ const text = await extractTextFromPdf(buffer);
120
+ return { success: true, data: text.substring(0, 50000) };
121
+ }
122
+ });
123
+ this.addTool({
124
+ name: "read_file",
125
+ description: "Read the content of a file (markdown, text, code, etc). Use this if you need to read a file, but check if the user already provided it via citation first.",
126
+ inputSchema: {
127
+ type: "object",
128
+ properties: { filePath: { type: "string" } },
129
+ required: ["filePath"]
130
+ },
131
+ handler: async (args) => {
132
+ await this.storage.init();
133
+ const content = await this.storage.readFile(args.filePath);
134
+ if (content === null)
135
+ throw new Error("File not found");
136
+ if (typeof content !== 'string')
137
+ throw new Error("File is binary, use read_pdf if it is a PDF");
138
+ return { success: true, data: content };
139
+ }
140
+ });
141
+ this.addTool({
142
+ name: "fetch_html",
143
+ description: "Fetch a URL and return its text content. ONLY use this when the user explicitly provides a URL or asks to read a specific link. NEVER use this spontaneously.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: { url: { type: "string" } },
147
+ required: ["url"]
148
+ },
149
+ handler: async (args) => {
150
+ // Modified: Use Proxy to avoid CORS errors
151
+ try {
152
+ const res = await fetch('/api/proxy', {
153
+ method: 'POST',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({ url: args.url })
156
+ });
157
+ if (!res.ok) {
158
+ try {
159
+ const errObj = await res.json();
160
+ // If the API returns a specific error (like 404), tell the LLM
161
+ if (errObj.error) {
162
+ // Add explicit instruction to not retry identically
163
+ return { success: false, error: `Could not fetch page (${res.status}). Error: ${errObj.error}. DO NOT RETRY THIS EXACT URL.` };
164
+ }
165
+ }
166
+ catch (e) { }
167
+ return { success: false, error: `Could not fetch page. Status: ${res.status} ${res.statusText}. It may be blocked. DO NOT RETRY THIS EXACT URL.` };
168
+ }
169
+ const data = await res.json();
170
+ return { success: true, data: data.content, title: data.title };
171
+ }
172
+ catch (e) {
173
+ console.error("[NativeCore] Fetch Proxy Failed:", e);
174
+ // Return error to LLM so it can try another link
175
+ return { success: false, error: `Failed to read URL (${e.message}). Try a different source.` };
176
+ }
177
+ }
178
+ });
179
+ // query_rag, query_graph, and summarize_file are in the application layer
180
+ this.addTool({
181
+ name: "update_user_settings",
182
+ description: "Update the user's profile settings (name, tone). ONLY use this when the user EXPLICITLY asks to change their name or how the AI speaks (tone). NEVER use this to 'update' the user's mood or feelings.",
183
+ inputSchema: {
184
+ type: "object",
185
+ properties: {
186
+ name: { type: "string" },
187
+ tone: { type: "string" }
188
+ }
189
+ },
190
+ handler: async (args) => {
191
+ const { settingsManager } = await import('../../settings');
192
+ await settingsManager.updateIdentity({ name: args.name, tone: args.tone });
193
+ return { success: true, message: "Settings updated" };
194
+ }
195
+ });
196
+ }
197
+ addTool(tool) {
198
+ this.tools.set(tool.name, tool);
199
+ }
200
+ async getTools() {
201
+ return Array.from(this.tools.values());
202
+ }
203
+ async executeTool(name, args) {
204
+ const tool = this.tools.get(name);
205
+ if (!tool)
206
+ throw new Error(`Tool ${name} not found in MeechiNativeCore`);
207
+ return await tool.handler(args);
208
+ }
209
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Native Servers Barrel Export
3
+ *
4
+ * All built-in "native" tool providers that run in-process.
5
+ * These follow the MCP interface pattern but don't use the actual protocol.
6
+ */
7
+ export { MeechiNativeCore } from './MeechiNativeCore';
8
+ export { GroqVoiceNative } from './GroqVoiceNative';
9
+ export { LocalVoiceNative } from './LocalVoiceNative';
10
+ export { LocalSyncNative } from './LocalSyncNative';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Native Servers Barrel Export
3
+ *
4
+ * All built-in "native" tool providers that run in-process.
5
+ * These follow the MCP interface pattern but don't use the actual protocol.
6
+ */
7
+ export { MeechiNativeCore } from './MeechiNativeCore';
8
+ export { GroqVoiceNative } from './GroqVoiceNative';
9
+ export { LocalVoiceNative } from './LocalVoiceNative';
10
+ export { LocalSyncNative } from './LocalSyncNative';
@@ -0,0 +1,35 @@
1
+ export interface McpTool {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: Record<string, any>;
7
+ required?: string[];
8
+ };
9
+ handler: (args: any) => Promise<any>;
10
+ }
11
+ export interface McpResource {
12
+ uri: string;
13
+ name: string;
14
+ mimeType?: string;
15
+ description?: string;
16
+ read: () => Promise<{
17
+ content: string;
18
+ mimeType: string;
19
+ }>;
20
+ }
21
+ export interface McpRequest {
22
+ method: string;
23
+ params?: any;
24
+ id?: string | number;
25
+ }
26
+ export interface McpResponse {
27
+ jsonrpc: "2.0";
28
+ result?: any;
29
+ error?: {
30
+ code: number;
31
+ message: string;
32
+ data?: any;
33
+ };
34
+ id?: string | number;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Extracts text content from a PDF ArrayBuffer with basic styling and layout preservation.
3
+ *
4
+ * Improvements:
5
+ * - Detects Headers based on font size relative to body text.
6
+ * - Detects Bold/Italic based on font name.
7
+ * - Preserves Paragraphs (double newline) based on vertical gaps.
8
+ * - Preserves Line Breaks (single newline).
9
+ */
10
+ export declare function extractTextFromPdf(data: ArrayBuffer): Promise<string>;
@@ -0,0 +1,142 @@
1
+ // Configure PDF.js worker
2
+ const PDFJS_VERSION = '4.9.155';
3
+ /**
4
+ * Extracts text content from a PDF ArrayBuffer with basic styling and layout preservation.
5
+ *
6
+ * Improvements:
7
+ * - Detects Headers based on font size relative to body text.
8
+ * - Detects Bold/Italic based on font name.
9
+ * - Preserves Paragraphs (double newline) based on vertical gaps.
10
+ * - Preserves Line Breaks (single newline).
11
+ */
12
+ export async function extractTextFromPdf(data) {
13
+ // Dynamic import to avoid SSR crashes with DOMMatrix/Canvas
14
+ const pdfjsLib = await import('pdfjs-dist');
15
+ // Use local worker for offline support and speed
16
+ pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs';
17
+ try {
18
+ const loadingTask = pdfjsLib.getDocument({ data });
19
+ const pdf = await loadingTask.promise;
20
+ let fullText = "";
21
+ for (let i = 1; i <= pdf.numPages; i++) {
22
+ const page = await pdf.getPage(i);
23
+ const textContent = await page.getTextContent();
24
+ const items = textContent.items;
25
+ if (items.length === 0)
26
+ continue;
27
+ // --- Pass 1: Statistics to identify Body Text ---
28
+ // We assume the most common font height is the body text.
29
+ const heightCounts = {};
30
+ for (const item of items) {
31
+ const h = Math.round(item.height); // Round to avoid precision noise
32
+ if (h > 0) {
33
+ heightCounts[h] = (heightCounts[h] || 0) + item.str.length; // Weight by character count
34
+ }
35
+ }
36
+ let bodyHeight = 0;
37
+ let maxCount = 0;
38
+ for (const [h, count] of Object.entries(heightCounts)) {
39
+ if (count > maxCount) {
40
+ maxCount = count;
41
+ bodyHeight = Number(h);
42
+ }
43
+ }
44
+ // --- Pass 2: Reconstruction ---
45
+ let pageOutput = "";
46
+ let lastY = -1;
47
+ let lastX = -1;
48
+ // Sort items by Y (descending for top-to-bottom) then X (ascending)
49
+ // Note: PDF coordinates usually start from bottom-left, so higher Y is higher on page.
50
+ // However, pdfjs-dist usually returns items in reading order.
51
+ // We rely on order but check coordinates for gaps.
52
+ for (let j = 0; j < items.length; j++) {
53
+ const item = items[j];
54
+ const text = item.str;
55
+ // Skip empty strings if they don't have significant structure implications
56
+ if (!text.trim())
57
+ continue;
58
+ // PDF uses a bottom-up coordinate system (0,0 is bottom-left usually),
59
+ // but sometimes it's flipped depending on the matrix.
60
+ // Let's rely on transform[5] which is TranslateY.
61
+ const curY = item.transform[5];
62
+ const curX = item.transform[4];
63
+ const curHeight = item.height;
64
+ // Detect Layout Changes
65
+ if (lastY !== -1) {
66
+ const yDiff = Math.abs(curY - lastY);
67
+ // Simple heuristic:
68
+ // If yDiff is small (essentially same line), check X gap.
69
+ // If yDiff is medium (next line), add \n.
70
+ // If yDiff is large (paragraph gap), add \n\n.
71
+ if (yDiff < curHeight * 0.5) {
72
+ // Same line
73
+ // Check if we need a space (simple heuristic)
74
+ // If there is a gap > 5px (arbitrary, depends on scale), add space found in PDF logic usually
75
+ // But item.str sometimes contains the space.
76
+ // Let's just trust implicit spaces or add one if previous didn't end with one?
77
+ if (lastX !== -1 && curX > lastX + item.width && !pageOutput.endsWith(" ")) {
78
+ // This checks "end of last item" vs "start of current".
79
+ // Wait, logic is: lastX + lastWidth vs curX.
80
+ // Simplifying: just add space if not already there, assuming distinct items act as words
81
+ if (pageOutput.length > 0 && !pageOutput.endsWith(" ") && !text.startsWith(" ")) {
82
+ pageOutput += " ";
83
+ }
84
+ }
85
+ }
86
+ else if (yDiff > bodyHeight * 1.5) {
87
+ // Likely a Paragraph break
88
+ pageOutput += "\n\n";
89
+ }
90
+ else {
91
+ // Standard line break
92
+ pageOutput += "\n";
93
+ }
94
+ }
95
+ else {
96
+ // First item on page
97
+ }
98
+ // Detect Styling
99
+ let chunk = text;
100
+ const isBold = /Bold|Bol/i.test(item.fontName);
101
+ const isItalic = /Italic|Ita|Oblique/i.test(item.fontName);
102
+ // Apply Markdown Styling
103
+ if (isBold)
104
+ chunk = `**${chunk}**`;
105
+ if (isItalic)
106
+ chunk = `_${chunk}_`;
107
+ // Detect Headers
108
+ // If it's effectively distinct line (we just added \n or \n\n or it's start)
109
+ // and font is significantly larger than body.
110
+ // Note: This logic applies the header tag *inline*.
111
+ // Ideally headers are block elements.
112
+ // We'll prepend # if it looks solely like a header line.
113
+ // A simpler approach for robust output: Just check size and prepend #.
114
+ // Markdown allows headers to be single lines.
115
+ if (curHeight > bodyHeight * 1.1) {
116
+ if (curHeight > bodyHeight * 1.5) {
117
+ // H1 or H2
118
+ // Only prefix if we are at start of line
119
+ if (pageOutput.endsWith("\n") || pageOutput === "") {
120
+ chunk = `## ${chunk}`;
121
+ }
122
+ }
123
+ else {
124
+ // H3
125
+ if (pageOutput.endsWith("\n") || pageOutput === "") {
126
+ chunk = `### ${chunk}`;
127
+ }
128
+ }
129
+ }
130
+ pageOutput += chunk;
131
+ lastY = curY;
132
+ lastX = curX; // logic for spaces would need accumulated width, skipping for now to reduce risk
133
+ }
134
+ fullText += `--- Page ${i} ---\n${pageOutput}\n\n`;
135
+ }
136
+ return fullText.trim();
137
+ }
138
+ catch (error) {
139
+ console.error("PDF Extraction Error:", error);
140
+ throw new Error("Failed to extract text from PDF");
141
+ }
142
+ }
@@ -0,0 +1,48 @@
1
+ import { StorageProvider } from './storage/types';
2
+ export interface AIProviderConfig {
3
+ id: string;
4
+ name: string;
5
+ enabled: boolean;
6
+ apiKey?: string;
7
+ baseUrl?: string;
8
+ model?: string;
9
+ }
10
+ export interface UserIdentity {
11
+ name: string;
12
+ tone: string;
13
+ }
14
+ export interface LocalAIConfig {
15
+ enabled: boolean;
16
+ model: string;
17
+ }
18
+ export interface AppearanceConfig {
19
+ fontFamily: string;
20
+ accentColor: string;
21
+ backgroundColor?: string;
22
+ surfaceColor?: string;
23
+ foregroundColor?: string;
24
+ secondaryColor?: string;
25
+ radius: string;
26
+ iconLibrary: 'lucide' | 'material' | 'custom';
27
+ }
28
+ export interface AppConfig {
29
+ identity: UserIdentity;
30
+ providers: AIProviderConfig[];
31
+ activeProviderId: string;
32
+ localAI: LocalAIConfig;
33
+ theme: 'light' | 'dark' | 'system';
34
+ appearance: AppearanceConfig;
35
+ storage: {
36
+ primarySync: string;
37
+ };
38
+ }
39
+ export declare class SettingsManager {
40
+ private storage;
41
+ constructor(storage: StorageProvider);
42
+ getConfig(): Promise<AppConfig>;
43
+ saveConfig(config: AppConfig): Promise<void>;
44
+ updateIdentity(identity: Partial<UserIdentity>): Promise<void>;
45
+ updateProvider(providerId: string, updates: Partial<AIProviderConfig>): Promise<void>;
46
+ setActiveProvider(providerId: string): Promise<void>;
47
+ }
48
+ export declare const settingsManager: SettingsManager;
@@ -0,0 +1,87 @@
1
+ import { LocalStorageProvider } from './storage/local';
2
+ const DEFAULT_CONFIG = {
3
+ identity: {
4
+ name: "Traveler",
5
+ tone: "Casual, positive, and concise"
6
+ },
7
+ providers: [
8
+ {
9
+ id: 'groq',
10
+ name: 'Groq',
11
+ enabled: true,
12
+ model: 'llama-3.3-70b-versatile'
13
+ }
14
+ ],
15
+ activeProviderId: 'groq',
16
+ localAI: {
17
+ enabled: true,
18
+ model: 'Llama-3.2-1B-Instruct-q4f16_1-MLC' // Default to 1B (Stable)
19
+ },
20
+ theme: 'system',
21
+ appearance: {
22
+ fontFamily: 'Lora',
23
+ accentColor: '#6B8E6B', // Sage
24
+ // Colors left undefined to allow CSS variables (Light/Dark mode) to take precedence.
25
+ // User can still override them in settings.
26
+ radius: '0.5rem',
27
+ iconLibrary: 'lucide'
28
+ },
29
+ storage: {
30
+ primarySync: 'local'
31
+ }
32
+ };
33
+ const CONFIG_PATH = 'core/config.json';
34
+ export class SettingsManager {
35
+ constructor(storage) {
36
+ this.storage = storage;
37
+ }
38
+ async getConfig() {
39
+ try {
40
+ const content = await this.storage.readFile(CONFIG_PATH);
41
+ if (!content || typeof content !== 'string') {
42
+ return DEFAULT_CONFIG;
43
+ }
44
+ const parsed = JSON.parse(content);
45
+ // Merge with default to ensure new fields are present
46
+ return Object.assign(Object.assign(Object.assign({}, DEFAULT_CONFIG), parsed), { identity: Object.assign(Object.assign({}, DEFAULT_CONFIG.identity), parsed.identity), localAI: Object.assign(Object.assign({}, DEFAULT_CONFIG.localAI), parsed.localAI) });
47
+ }
48
+ catch (e) {
49
+ console.warn("Failed to load config, returning default", e);
50
+ return DEFAULT_CONFIG;
51
+ }
52
+ }
53
+ async saveConfig(config) {
54
+ const content = JSON.stringify(config, null, 2);
55
+ await this.storage.saveFile(CONFIG_PATH, content);
56
+ }
57
+ async updateIdentity(identity) {
58
+ const config = await this.getConfig();
59
+ config.identity = Object.assign(Object.assign({}, config.identity), identity);
60
+ await this.saveConfig(config);
61
+ }
62
+ async updateProvider(providerId, updates) {
63
+ const config = await this.getConfig();
64
+ const index = config.providers.findIndex(p => p.id === providerId);
65
+ if (index >= 0) {
66
+ config.providers[index] = Object.assign(Object.assign({}, config.providers[index]), updates);
67
+ }
68
+ else {
69
+ // Add if not exists (allows adding generic/custom providers via this generic method if needed)
70
+ // But usually we want strict registration. For now, let's just update if exists.
71
+ console.warn(`Provider ${providerId} not found in config.`);
72
+ }
73
+ await this.saveConfig(config);
74
+ }
75
+ async setActiveProvider(providerId) {
76
+ const config = await this.getConfig();
77
+ // Verify it exists and is enabled?
78
+ const provider = config.providers.find(p => p.id === providerId);
79
+ if (provider) {
80
+ config.activeProviderId = providerId;
81
+ await this.saveConfig(config);
82
+ }
83
+ }
84
+ }
85
+ // Singleton helper for client-side usage if needed,
86
+ // though usually we instantiate this with the specific storage instance.
87
+ export const settingsManager = new SettingsManager(new LocalStorageProvider());
@@ -0,0 +1,57 @@
1
+ import Dexie, { Table } from 'dexie';
2
+ export interface FileMetadata {
3
+ isSource?: boolean;
4
+ originalContent?: string;
5
+ summary?: string;
6
+ comments?: Array<{
7
+ id: string;
8
+ text: string;
9
+ range?: any;
10
+ timestamp: number;
11
+ }>;
12
+ [key: string]: any;
13
+ }
14
+ export interface FileRecord {
15
+ path: string;
16
+ content: string | Blob | ArrayBuffer;
17
+ updatedAt: number;
18
+ remoteId?: string;
19
+ type: 'file' | 'folder' | 'source';
20
+ dirty?: number;
21
+ deleted?: number;
22
+ tags?: string[];
23
+ metadata?: FileMetadata;
24
+ }
25
+ export interface SettingRecord {
26
+ key: string;
27
+ value: any;
28
+ }
29
+ export interface FileChunk {
30
+ id?: number;
31
+ filePath: string;
32
+ content: string;
33
+ embedding: number[];
34
+ }
35
+ export interface JournalEntry {
36
+ id?: number;
37
+ content: string;
38
+ createdAt: Date;
39
+ }
40
+ export interface GraphEdgeRecord {
41
+ id: string;
42
+ source: string;
43
+ target: string;
44
+ relation: string;
45
+ weight?: number;
46
+ createdAt: number;
47
+ metadata?: any;
48
+ }
49
+ export declare class MeechiDB extends Dexie {
50
+ files: Table<FileRecord>;
51
+ settings: Table<SettingRecord>;
52
+ chunks: Table<FileChunk>;
53
+ journal: Table<JournalEntry>;
54
+ edges: Table<GraphEdgeRecord>;
55
+ constructor();
56
+ }
57
+ export declare const db: MeechiDB;