@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,119 @@
1
+ // NOTE: @xenova/transformers is imported dynamically to prevent SWC parse errors at build time.
2
+ export class TranscriberService {
3
+ // Allow injecting key from outside (e.g. settings)
4
+ static setCloudProvider(apiKey) {
5
+ this.cloudKey = apiKey;
6
+ }
7
+ static async getInstance() {
8
+ if (!this.instance) {
9
+ console.log(`Loading Metadata for ${this.modelId}...`);
10
+ // Dynamically import @xenova/transformers
11
+ const { pipeline, env } = await import('@xenova/transformers');
12
+ env.allowLocalModels = false;
13
+ env.useBrowserCache = true;
14
+ this.instance = await pipeline('automatic-speech-recognition', this.modelId, {
15
+ // quantized: true, // Disable quantization to debug "empty text" issue
16
+ });
17
+ console.log("Transcriber Model Loaded");
18
+ }
19
+ return this.instance;
20
+ }
21
+ static async transcribe(audio) {
22
+ if (audio.length === 0)
23
+ return "";
24
+ // Pass 1: Find Peak
25
+ let max = 0;
26
+ for (let i = 0; i < audio.length; i++) {
27
+ const abs = Math.abs(audio[i]);
28
+ if (abs > max)
29
+ max = abs;
30
+ }
31
+ // Pass 2: Normalize to 0.95 (Boost volume)
32
+ const normalized = new Float32Array(audio.length);
33
+ const scale = max > 0 ? 0.95 / max : 1;
34
+ console.log(`[STT] Normalizing Audio. Peak: ${max.toFixed(4)} -> Scale: ${scale.toFixed(4)}`);
35
+ for (let i = 0; i < audio.length; i++) {
36
+ normalized[i] = audio[i] * scale;
37
+ }
38
+ // CLOUD FAST PATH (GROQ)
39
+ // If we have a cloud key, use Groq Whisper for 50x speedup
40
+ if (this.cloudKey || process.env.NEXT_PUBLIC_GROQ_API_KEY) {
41
+ const key = this.cloudKey || process.env.NEXT_PUBLIC_GROQ_API_KEY;
42
+ console.log("[STT] Using Cloud Whisper (Groq) for speed...");
43
+ try {
44
+ // Convert Float32Array to WAV Blob
45
+ const wavBlob = await this.encodeWAV(normalized);
46
+ const formData = new FormData();
47
+ formData.append('file', wavBlob, 'audio.wav');
48
+ formData.append('model', 'whisper-large-v3'); // Groq supports large-v3
49
+ formData.append('response_format', 'json');
50
+ const res = await fetch('https://api.groq.com/openai/v1/audio/transcriptions', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Authorization': `Bearer ${key}`
54
+ },
55
+ body: formData
56
+ });
57
+ if (!res.ok)
58
+ throw new Error(`Groq STT Error: ${res.status}`);
59
+ const data = await res.json();
60
+ console.log("[STT-Cloud] Result:", data.text);
61
+ return data.text.trim();
62
+ }
63
+ catch (cloudErr) {
64
+ console.warn("[STT] Cloud failed, falling back to local:", cloudErr);
65
+ }
66
+ }
67
+ // FALLBACK: Local Xenova
68
+ try {
69
+ const transcriber = await this.getInstance();
70
+ // Simplified options to reduce potential conflicts
71
+ const output = await transcriber(normalized, {
72
+ language: 'english',
73
+ task: 'transcribe',
74
+ });
75
+ console.log("[STT-Local] Raw Output:", JSON.stringify(output, null, 2));
76
+ return output.text.trim();
77
+ }
78
+ catch (e) {
79
+ console.error("Transcription Error:", e);
80
+ return "";
81
+ }
82
+ }
83
+ // Helper: Encode Float32 to WAV for API upload
84
+ static encodeWAV(samples, sampleRate = 16000) {
85
+ const buffer = new ArrayBuffer(44 + samples.length * 2);
86
+ const view = new DataView(buffer);
87
+ const writeString = (view, offset, string) => {
88
+ for (let i = 0; i < string.length; i++) {
89
+ view.setUint8(offset + i, string.charCodeAt(i));
90
+ }
91
+ };
92
+ // RIFF chunk descriptor
93
+ writeString(view, 0, 'RIFF');
94
+ view.setUint32(4, 36 + samples.length * 2, true);
95
+ writeString(view, 8, 'WAVE');
96
+ writeString(view, 12, 'fmt ');
97
+ view.setUint32(16, 16, true);
98
+ view.setUint16(20, 1, true); // PCM
99
+ view.setUint16(22, 1, true); // Mono
100
+ view.setUint32(24, sampleRate, true);
101
+ view.setUint32(28, sampleRate * 2, true);
102
+ view.setUint16(32, 2, true);
103
+ view.setUint16(34, 16, true);
104
+ writeString(view, 36, 'data');
105
+ view.setUint32(40, samples.length * 2, true);
106
+ // PCM Samples
107
+ let offset = 44;
108
+ for (let i = 0; i < samples.length; i++) {
109
+ let s = Math.max(-1, Math.min(1, samples[i]));
110
+ view.setInt16(offset, s * 0x7FFF, true);
111
+ offset += 2;
112
+ }
113
+ return new Blob([buffer], { type: 'audio/wav' });
114
+ }
115
+ }
116
+ TranscriberService.instance = null;
117
+ // Upgrade to base.en for better accuracy/robustness than tiny
118
+ TranscriberService.modelId = 'Xenova/whisper-base.en';
119
+ TranscriberService.cloudKey = null;
@@ -0,0 +1,12 @@
1
+ export declare class SynthesizerService {
2
+ private static tts;
3
+ private static modelId;
4
+ private static dtype;
5
+ private static cloudKey;
6
+ static setCloudProvider(apiKey: string | null): void;
7
+ static init(): Promise<void>;
8
+ static speak(text: string): Promise<{
9
+ audio: Float32Array;
10
+ sampling_rate: number;
11
+ } | null>;
12
+ }
@@ -0,0 +1,128 @@
1
+ // NOTE: KokoroTTS uses ONNX internally and @huggingface/transformers for env.
2
+ // We dynamically import from static vendor file to prevent SWC parse errors at build time.
3
+ export class SynthesizerService {
4
+ static setCloudProvider(apiKey) {
5
+ this.cloudKey = apiKey;
6
+ }
7
+ static async init() {
8
+ // If we have an OpenAI Key (or cloud key), we don't strictly NEED to load local TTS immediately.
9
+ // But we might want it as fallback.
10
+ // Let's lazy load: If speak() fails on cloud, it initializes local.
11
+ // So init() can be a no-op if cloud is ready, or run in background.
12
+ const hasCloud = !!(this.cloudKey || process.env.NEXT_PUBLIC_OPENAI_API_KEY);
13
+ if (!hasCloud) {
14
+ console.log("Initializing Local Kokoro TTS...");
15
+ if (!this.tts) {
16
+ // Dynamically import kokoro-js and configure env
17
+ const { KokoroTTS } = await import('kokoro-js');
18
+ // @ts-ignore - Load transformers from static vendor file
19
+ let transformers = globalThis.transformers;
20
+ if (!transformers) {
21
+ // @ts-ignore
22
+ transformers = await import(/* webpackIgnore: true */ '/vendor/transformers.js');
23
+ }
24
+ const { env } = transformers;
25
+ env.allowLocalModels = false;
26
+ env.useBrowserCache = true;
27
+ this.tts = await KokoroTTS.from_pretrained(this.modelId, {
28
+ dtype: this.dtype,
29
+ });
30
+ console.log("Kokoro TTS Initialized");
31
+ }
32
+ }
33
+ else {
34
+ console.log("Cloud TTS Key detected. Local TTS deferred.");
35
+ }
36
+ }
37
+ static async speak(text) {
38
+ // 1. CLOUD TURBO MODE (OpenAI / Groq)
39
+ // Check for specific keys in order: Injected -> OpenAI Env -> Groq Env
40
+ const key = this.cloudKey || process.env.NEXT_PUBLIC_OPENAI_API_KEY || process.env.NEXT_PUBLIC_GROQ_API_KEY;
41
+ if (key) {
42
+ const isGroq = key.startsWith('gsk_');
43
+ const url = isGroq
44
+ ? "https://api.groq.com/openai/v1/audio/speech"
45
+ : "https://api.openai.com/v1/audio/speech";
46
+ // Groq requires specific model names.
47
+ // 'playai-tts' is decommissioned. New default is 'canopylabs/orpheus-v1-english'.
48
+ const model = isGroq ? "canopylabs/orpheus-v1-english" : "tts-1";
49
+ console.log(`[TTS] Using Cloud TTS (${isGroq ? 'Groq' : 'OpenAI'})... Model: ${model}`);
50
+ // Groq (Orpheus) supports specific voices: autumn, diana, hannah, austin, daniel, troy
51
+ // OpenAI supports: alloy, echo, fable, etc.
52
+ const voice = isGroq ? "diana" : "alloy";
53
+ try {
54
+ const res = await fetch(url, {
55
+ method: "POST",
56
+ headers: {
57
+ "Authorization": `Bearer ${key}`,
58
+ "Content-Type": "application/json",
59
+ },
60
+ body: JSON.stringify({
61
+ model: model,
62
+ input: text,
63
+ voice: voice,
64
+ response_format: isGroq ? "wav" : "mp3",
65
+ speed: 1.15
66
+ })
67
+ });
68
+ if (!res.ok) {
69
+ const errText = await res.text();
70
+ throw new Error(`Cloud TTS Error (${res.status}): ${errText}`);
71
+ }
72
+ const blob = await res.blob();
73
+ const arrayBuffer = await blob.arrayBuffer();
74
+ // Decode MP3 to Float32
75
+ const ctx = new AudioContext();
76
+ const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
77
+ const pcm = audioBuffer.getChannelData(0); // Mono
78
+ // Copy to new float array
79
+ const pcmData = new Float32Array(pcm);
80
+ const sampleRate = audioBuffer.sampleRate;
81
+ ctx.close();
82
+ return {
83
+ audio: pcmData,
84
+ sampling_rate: sampleRate
85
+ };
86
+ }
87
+ catch (err) {
88
+ console.warn("[TTS] Cloud failed, attempting local fallback:", err);
89
+ }
90
+ }
91
+ // 2. LOCAL FALLBACK
92
+ if (!this.tts) {
93
+ console.log("Initializing Local Kokoro TTS (Fallback)...");
94
+ // Dynamically import kokoro-js
95
+ const { KokoroTTS } = await import('kokoro-js');
96
+ // @ts-ignore - Load transformers from static vendor file
97
+ let transformers = globalThis.transformers;
98
+ if (!transformers) {
99
+ // @ts-ignore
100
+ transformers = await import(/* webpackIgnore: true */ '/vendor/transformers.js');
101
+ }
102
+ const { env } = transformers;
103
+ env.allowLocalModels = false;
104
+ env.useBrowserCache = true;
105
+ this.tts = await KokoroTTS.from_pretrained(this.modelId, {
106
+ dtype: this.dtype,
107
+ });
108
+ }
109
+ if (!this.tts)
110
+ return null;
111
+ try {
112
+ // Default voice: af_bella
113
+ const audio = await this.tts.generate(text, {
114
+ voice: "af_bella",
115
+ });
116
+ return audio;
117
+ }
118
+ catch (e) {
119
+ console.error("TTS Synthesis Error:", e);
120
+ return null;
121
+ }
122
+ }
123
+ }
124
+ SynthesizerService.tts = null; // KokoroTTS
125
+ SynthesizerService.modelId = "onnx-community/Kokoro-82M-ONNX";
126
+ SynthesizerService.dtype = "fp32";
127
+ // Cloud API Key (Optional)
128
+ SynthesizerService.cloudKey = null;
@@ -0,0 +1,18 @@
1
+ export declare class VADService {
2
+ private session;
3
+ private h;
4
+ private c;
5
+ private sr;
6
+ private readonly SAMPLE_RATE;
7
+ private readonly WINDOW_SIZE_SAMPLES;
8
+ private readonly THRESHOLD_START;
9
+ private readonly THRESHOLD_END;
10
+ private isSpeaking;
11
+ constructor();
12
+ init(modelPath?: string): Promise<void>;
13
+ process(audioFrame: Float32Array): Promise<{
14
+ isSpeech: boolean;
15
+ probability: number;
16
+ }>;
17
+ reset(): void;
18
+ }
@@ -0,0 +1,117 @@
1
+ let ort = null;
2
+ export class VADService {
3
+ constructor() {
4
+ this.session = null; // ort.InferenceSession
5
+ this.h = null; // ort.Tensor
6
+ this.c = null; // ort.Tensor
7
+ this.sr = null; // ort.Tensor
8
+ this.SAMPLE_RATE = 16000;
9
+ this.WINDOW_SIZE_SAMPLES = 512; // 32ms at 16kHz
10
+ // Thresholds
11
+ this.THRESHOLD_START = 0.3; // Increased to avoid trigger on static
12
+ this.THRESHOLD_END = 0.15;
13
+ // State
14
+ this.isSpeaking = false;
15
+ }
16
+ async init(modelPath = '/models/silero_vad.onnx') {
17
+ try {
18
+ // Dynamically import onnxruntime-web to prevent SWC from parsing it at build time.
19
+ if (!ort) {
20
+ ort = await import('onnxruntime-web');
21
+ ort.env.logLevel = 'error'; // Silence benign warnings
22
+ }
23
+ // Initialize ONNX session
24
+ this.session = await ort.InferenceSession.create(modelPath, {
25
+ executionProviders: ['wasm'], // Force WASM for consistency
26
+ logSeverityLevel: 3, // 0:Verbose, 1:Info, 2:Warning, 3:Error, 4:Fatal
27
+ });
28
+ // Initialize states (2, 1, 64) float32
29
+ const dims = [2, 1, 64];
30
+ const size = 128;
31
+ this.h = new ort.Tensor('float32', new Float32Array(size).fill(0), dims);
32
+ this.c = new ort.Tensor('float32', new Float32Array(size).fill(0), dims);
33
+ // Sample rate tensor (Must be int64)
34
+ this.sr = new ort.Tensor('int64', BigInt64Array.from([BigInt(this.SAMPLE_RATE)]), [1]);
35
+ console.log("VADService initialized");
36
+ }
37
+ catch (e) {
38
+ console.error("Failed to initialize VAD:", e);
39
+ throw e;
40
+ }
41
+ }
42
+ async process(audioFrame) {
43
+ if (!this.session || !this.h || !this.c || !this.sr || !ort) {
44
+ return { isSpeech: false, probability: 0 };
45
+ }
46
+ try {
47
+ // Create Input Tensor: (1, N)
48
+ const input = new ort.Tensor('float32', audioFrame, [1, audioFrame.length]);
49
+ // Check input names to support both v4 and v5
50
+ const inputNames = this.session.inputNames;
51
+ const feeds = {
52
+ input: input,
53
+ sr: this.sr,
54
+ };
55
+ if (inputNames.includes('state')) {
56
+ // Silero v5: Combined state [2, 1, 128]
57
+ const targetShape = [2, 1, 128];
58
+ const targetSize = 256;
59
+ // 1. Validate 'h' existence and size
60
+ if (!this.h || this.h.data.length !== targetSize) {
61
+ this.h = new ort.Tensor('float32', new Float32Array(targetSize).fill(0), targetShape);
62
+ }
63
+ // 2. Validate 'h' dimensions (Reshape if needed)
64
+ else if (this.h.dims.length !== 3 || this.h.dims[0] !== 2 || this.h.dims[1] !== 1 || this.h.dims[2] !== 128) {
65
+ // Reshape keeping data
66
+ this.h = new ort.Tensor('float32', this.h.data, targetShape);
67
+ }
68
+ feeds['state'] = this.h;
69
+ }
70
+ else {
71
+ // ... v4 logic ...
72
+ feeds['h'] = this.h;
73
+ feeds['c'] = this.c;
74
+ }
75
+ const results = await this.session.run(feeds);
76
+ const probability = results.output.data[0];
77
+ // Update states
78
+ if (inputNames.includes('state')) {
79
+ const rawState = results.stateN || results.state;
80
+ // Bug Fix: Silero V5 might Output weird shapes (e.g. {1,1,1,128,8}) in some envs
81
+ // We force reshape back to [2, 1, 128] for consistency in the next loop.
82
+ if (rawState) {
83
+ const targetShape = [2, 1, 128];
84
+ if (rawState.dims.length !== 3 || rawState.dims[0] !== 2) {
85
+ this.h = new ort.Tensor('float32', rawState.data, targetShape);
86
+ }
87
+ else {
88
+ this.h = rawState;
89
+ }
90
+ }
91
+ }
92
+ else {
93
+ this.h = results.hn;
94
+ this.c = results.cn;
95
+ }
96
+ // Trigger logic
97
+ if (probability > this.THRESHOLD_START && !this.isSpeaking) {
98
+ this.isSpeaking = true;
99
+ }
100
+ else if (probability < this.THRESHOLD_END && this.isSpeaking) {
101
+ this.isSpeaking = false;
102
+ }
103
+ return { isSpeech: this.isSpeaking, probability };
104
+ }
105
+ catch (e) {
106
+ console.error("VAD Inference Error:", e);
107
+ return { isSpeech: false, probability: 0 };
108
+ }
109
+ }
110
+ reset() {
111
+ // Reset states
112
+ // We set to null so they re-init on next process() call with correct v4/v5 logic
113
+ this.h = null;
114
+ this.c = null;
115
+ this.isSpeaking = false;
116
+ }
117
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Color Utilities for Meechi
3
+ * Implements Hex -> OKLCH conversion for the dynamic theme engine.
4
+ *
5
+ * Flow: Hex -> RGB -> Linear RGB -> OKLAB -> OKLCH
6
+ */
7
+ /**
8
+ * Main function: Converts Hex to OKLCH string
9
+ * Returns an object with the components for CSS variables
10
+ */
11
+ export declare function getOklch(hex: string): {
12
+ l: string;
13
+ c: string;
14
+ h: string;
15
+ cssValue: string;
16
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Color Utilities for Meechi
3
+ * Implements Hex -> OKLCH conversion for the dynamic theme engine.
4
+ *
5
+ * Flow: Hex -> RGB -> Linear RGB -> OKLAB -> OKLCH
6
+ */
7
+ // 1. Hex to sRGB (0-1)
8
+ function hexToRgb(hex) {
9
+ hex = hex.replace(/^#/, '');
10
+ if (hex.length === 3)
11
+ hex = hex.split('').map(c => c + c).join('');
12
+ const bigint = parseInt(hex, 16);
13
+ const r = ((bigint >> 16) & 255) / 255;
14
+ const g = ((bigint >> 8) & 255) / 255;
15
+ const b = (bigint & 255) / 255;
16
+ return { r, g, b };
17
+ }
18
+ // 2. sRGB to Linear RGB
19
+ // sRGB transfer function removal
20
+ function linearize(c) {
21
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
22
+ }
23
+ // 3. Linear RGB to OKLAB
24
+ // Approximate matrices from Okhwb/Okyz specifications
25
+ function rgbToOklab(rLin, gLin, bLin) {
26
+ const l = 0.4122214708 * rLin + 0.5363325363 * gLin + 0.0514459929 * bLin;
27
+ const m = 0.2119034982 * rLin + 0.6806995451 * gLin + 0.1073969566 * bLin;
28
+ const s = 0.0883024619 * rLin + 0.2817188376 * gLin + 0.6299787005 * bLin;
29
+ const l_ = Math.cbrt(l);
30
+ const m_ = Math.cbrt(m);
31
+ const s_ = Math.cbrt(s);
32
+ const L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
33
+ const a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
34
+ const b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
35
+ return { L, a, b };
36
+ }
37
+ // 4. OKLAB to OKLCH
38
+ function oklabToOklch(L, a, b) {
39
+ const C = Math.sqrt(a * a + b * b);
40
+ let h = Math.atan2(b, a) * (180 / Math.PI);
41
+ if (h < 0)
42
+ h += 360;
43
+ return { l: L, c: C, h };
44
+ }
45
+ /**
46
+ * Main function: Converts Hex to OKLCH string
47
+ * Returns an object with the components for CSS variables
48
+ */
49
+ export function getOklch(hex) {
50
+ const { r, g, b } = hexToRgb(hex);
51
+ const rLin = linearize(r);
52
+ const gLin = linearize(g);
53
+ const bLin = linearize(b);
54
+ const { L, a, b: bLab } = rgbToOklab(rLin, gLin, bLin);
55
+ const { l, c, h } = oklabToOklch(L, a, bLab);
56
+ // Format for CSS
57
+ // Round for clean output
58
+ const lStr = l.toFixed(3);
59
+ const cStr = c.toFixed(3);
60
+ const hStr = h.toFixed(2);
61
+ return {
62
+ l: lStr,
63
+ c: cStr,
64
+ h: hStr,
65
+ cssValue: `oklch(${lStr} ${cStr} ${hStr})`
66
+ };
67
+ }
@@ -0,0 +1,35 @@
1
+ import { LucideIcon } from 'lucide-react';
2
+ import React from 'react';
3
+ /**
4
+ * MEECHI EXTENSION SYSTEM (Core)
5
+ * ----------------------------
6
+ * This file provides the generic slots for the application.
7
+ * In Core, these registries are empty.
8
+ * Parent applications can populate these registries at runtime/init.
9
+ */
10
+ export interface SettingsTab {
11
+ id: string;
12
+ label: string;
13
+ icon: LucideIcon;
14
+ component: React.ComponentType;
15
+ }
16
+ export interface FileContext {
17
+ storage: any;
18
+ }
19
+ export interface FileAction {
20
+ id: string;
21
+ label: string;
22
+ icon?: LucideIcon;
23
+ handler: (file: any, context: FileContext) => Promise<void>;
24
+ shouldShow?: (file: any) => boolean;
25
+ }
26
+ declare class ExtensionRegistry {
27
+ private settingsTabs;
28
+ private fileActions;
29
+ registerSettingsTab(tab: SettingsTab): void;
30
+ getSettingsTabs(): SettingsTab[];
31
+ registerFileAction(action: FileAction): void;
32
+ getFileActions(): FileAction[];
33
+ }
34
+ export declare const extensions: ExtensionRegistry;
35
+ export {};
@@ -0,0 +1,24 @@
1
+ class ExtensionRegistry {
2
+ constructor() {
3
+ this.settingsTabs = [];
4
+ this.fileActions = [];
5
+ }
6
+ registerSettingsTab(tab) {
7
+ if (!this.settingsTabs.find(t => t.id === tab.id)) {
8
+ this.settingsTabs.push(tab);
9
+ }
10
+ }
11
+ getSettingsTabs() {
12
+ return [...this.settingsTabs];
13
+ }
14
+ // File Explorer Slots
15
+ registerFileAction(action) {
16
+ if (!this.fileActions.find(a => a.id === action.id)) {
17
+ this.fileActions.push(action);
18
+ }
19
+ }
20
+ getFileActions() {
21
+ return [...this.fileActions];
22
+ }
23
+ }
24
+ export const extensions = new ExtensionRegistry();
@@ -0,0 +1,13 @@
1
+ type VoiceState = 'idle' | 'listening' | 'processing' | 'speaking';
2
+ export declare function useVoiceLoop(sendMessage: (text: string, onToken?: (chunk: string) => void) => Promise<string | void>): {
3
+ start: () => Promise<void>;
4
+ stop: () => void;
5
+ stopPlayback: () => void;
6
+ state: VoiceState;
7
+ vadProb: number;
8
+ transcript: string;
9
+ playResponse: (text: string) => Promise<void>;
10
+ isPlaying: boolean;
11
+ getAnalyser: () => AnalyserNode | null;
12
+ };
13
+ export {};