@aj-archipelago/cortex 1.3.5 → 1.3.7

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 (94) hide show
  1. package/helper-apps/cortex-autogen/agents.py +31 -2
  2. package/helper-apps/cortex-realtime-voice-server/.env.sample +6 -0
  3. package/helper-apps/cortex-realtime-voice-server/README.md +22 -0
  4. package/helper-apps/cortex-realtime-voice-server/bun.lockb +0 -0
  5. package/helper-apps/cortex-realtime-voice-server/client/bun.lockb +0 -0
  6. package/helper-apps/cortex-realtime-voice-server/client/index.html +12 -0
  7. package/helper-apps/cortex-realtime-voice-server/client/package.json +65 -0
  8. package/helper-apps/cortex-realtime-voice-server/client/postcss.config.js +6 -0
  9. package/helper-apps/cortex-realtime-voice-server/client/public/favicon.ico +0 -0
  10. package/helper-apps/cortex-realtime-voice-server/client/public/index.html +43 -0
  11. package/helper-apps/cortex-realtime-voice-server/client/public/logo192.png +0 -0
  12. package/helper-apps/cortex-realtime-voice-server/client/public/logo512.png +0 -0
  13. package/helper-apps/cortex-realtime-voice-server/client/public/manifest.json +25 -0
  14. package/helper-apps/cortex-realtime-voice-server/client/public/robots.txt +3 -0
  15. package/helper-apps/cortex-realtime-voice-server/client/public/sounds/connect.mp3 +0 -0
  16. package/helper-apps/cortex-realtime-voice-server/client/public/sounds/disconnect.mp3 +0 -0
  17. package/helper-apps/cortex-realtime-voice-server/client/src/App.test.tsx +9 -0
  18. package/helper-apps/cortex-realtime-voice-server/client/src/App.tsx +126 -0
  19. package/helper-apps/cortex-realtime-voice-server/client/src/SettingsModal.tsx +207 -0
  20. package/helper-apps/cortex-realtime-voice-server/client/src/chat/Chat.tsx +553 -0
  21. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatBubble.tsx +22 -0
  22. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatBubbleLeft.tsx +22 -0
  23. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatBubbleRight.tsx +21 -0
  24. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatMessage.tsx +27 -0
  25. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatMessageInput.tsx +74 -0
  26. package/helper-apps/cortex-realtime-voice-server/client/src/chat/ChatTile.tsx +211 -0
  27. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/SoundEffects.ts +56 -0
  28. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/WavPacker.ts +112 -0
  29. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/WavRecorder.ts +571 -0
  30. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/WavStreamPlayer.ts +290 -0
  31. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/analysis/AudioAnalysis.ts +186 -0
  32. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/analysis/constants.ts +59 -0
  33. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/worklets/AudioProcessor.ts +214 -0
  34. package/helper-apps/cortex-realtime-voice-server/client/src/chat/audio/worklets/StreamProcessor.ts +183 -0
  35. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/AudioVisualizer.tsx +151 -0
  36. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/CopyButton.tsx +32 -0
  37. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/ImageOverlay.tsx +166 -0
  38. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/MicrophoneVisualizer.tsx +95 -0
  39. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/ScreenshotCapture.tsx +116 -0
  40. package/helper-apps/cortex-realtime-voice-server/client/src/chat/hooks/useWindowResize.ts +27 -0
  41. package/helper-apps/cortex-realtime-voice-server/client/src/chat/utils/audio.ts +33 -0
  42. package/helper-apps/cortex-realtime-voice-server/client/src/index.css +20 -0
  43. package/helper-apps/cortex-realtime-voice-server/client/src/index.tsx +19 -0
  44. package/helper-apps/cortex-realtime-voice-server/client/src/logo.svg +1 -0
  45. package/helper-apps/cortex-realtime-voice-server/client/src/react-app-env.d.ts +1 -0
  46. package/helper-apps/cortex-realtime-voice-server/client/src/reportWebVitals.ts +15 -0
  47. package/helper-apps/cortex-realtime-voice-server/client/src/setupTests.ts +5 -0
  48. package/helper-apps/cortex-realtime-voice-server/client/src/utils/logger.ts +45 -0
  49. package/helper-apps/cortex-realtime-voice-server/client/tailwind.config.js +14 -0
  50. package/helper-apps/cortex-realtime-voice-server/client/tsconfig.json +30 -0
  51. package/helper-apps/cortex-realtime-voice-server/client/vite.config.ts +22 -0
  52. package/helper-apps/cortex-realtime-voice-server/index.ts +19 -0
  53. package/helper-apps/cortex-realtime-voice-server/package.json +28 -0
  54. package/helper-apps/cortex-realtime-voice-server/src/ApiServer.ts +35 -0
  55. package/helper-apps/cortex-realtime-voice-server/src/SocketServer.ts +737 -0
  56. package/helper-apps/cortex-realtime-voice-server/src/Tools.ts +520 -0
  57. package/helper-apps/cortex-realtime-voice-server/src/cortex/expert.ts +29 -0
  58. package/helper-apps/cortex-realtime-voice-server/src/cortex/image.ts +29 -0
  59. package/helper-apps/cortex-realtime-voice-server/src/cortex/memory.ts +91 -0
  60. package/helper-apps/cortex-realtime-voice-server/src/cortex/reason.ts +29 -0
  61. package/helper-apps/cortex-realtime-voice-server/src/cortex/search.ts +30 -0
  62. package/helper-apps/cortex-realtime-voice-server/src/cortex/style.ts +31 -0
  63. package/helper-apps/cortex-realtime-voice-server/src/cortex/utils.ts +95 -0
  64. package/helper-apps/cortex-realtime-voice-server/src/cortex/vision.ts +34 -0
  65. package/helper-apps/cortex-realtime-voice-server/src/realtime/client.ts +499 -0
  66. package/helper-apps/cortex-realtime-voice-server/src/realtime/realtimeTypes.ts +279 -0
  67. package/helper-apps/cortex-realtime-voice-server/src/realtime/socket.ts +27 -0
  68. package/helper-apps/cortex-realtime-voice-server/src/realtime/transcription.ts +75 -0
  69. package/helper-apps/cortex-realtime-voice-server/src/realtime/utils.ts +33 -0
  70. package/helper-apps/cortex-realtime-voice-server/src/utils/logger.ts +45 -0
  71. package/helper-apps/cortex-realtime-voice-server/src/utils/prompt.ts +81 -0
  72. package/helper-apps/cortex-realtime-voice-server/tsconfig.json +28 -0
  73. package/package.json +1 -1
  74. package/pathways/basePathway.js +3 -1
  75. package/pathways/system/entity/memory/sys_memory_manager.js +3 -0
  76. package/pathways/system/entity/memory/sys_memory_update.js +44 -45
  77. package/pathways/system/entity/memory/sys_read_memory.js +86 -6
  78. package/pathways/system/entity/memory/sys_search_memory.js +66 -0
  79. package/pathways/system/entity/shared/sys_entity_constants.js +2 -2
  80. package/pathways/system/entity/sys_entity_continue.js +2 -1
  81. package/pathways/system/entity/sys_entity_start.js +10 -0
  82. package/pathways/system/entity/sys_generator_expert.js +0 -2
  83. package/pathways/system/entity/sys_generator_memory.js +31 -0
  84. package/pathways/system/entity/sys_generator_voice_sample.js +36 -0
  85. package/pathways/system/entity/sys_router_tool.js +13 -10
  86. package/pathways/system/sys_parse_numbered_object_list.js +1 -1
  87. package/server/pathwayResolver.js +41 -31
  88. package/server/plugins/azureVideoTranslatePlugin.js +28 -16
  89. package/server/plugins/claude3VertexPlugin.js +0 -9
  90. package/server/plugins/gemini15ChatPlugin.js +18 -5
  91. package/server/plugins/modelPlugin.js +27 -6
  92. package/server/plugins/openAiChatPlugin.js +10 -8
  93. package/server/plugins/openAiVisionPlugin.js +56 -0
  94. package/tests/memoryfunction.test.js +73 -1
@@ -0,0 +1,166 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ const FADE_DURATION = 300; // milliseconds
4
+ const DISPLAY_DURATION = 10000; // milliseconds (10 seconds)
5
+
6
+ type ImageOverlayProps = {
7
+ imageUrls: string[];
8
+ onComplete?: () => void;
9
+ isAudioPlaying?: boolean;
10
+ };
11
+
12
+ export function ImageOverlay({ imageUrls, onComplete, isAudioPlaying = false }: ImageOverlayProps) {
13
+ const [currentIndex, setCurrentIndex] = useState(-1);
14
+ const [shownImages, setShownImages] = useState<Set<string>>(new Set());
15
+ const [isVisible, setIsVisible] = useState(false);
16
+ const [showControls, setShowControls] = useState(false);
17
+ const previousAudioPlaying = useRef(isAudioPlaying);
18
+ const timerRef = useRef<NodeJS.Timeout | null>(null);
19
+ const displayStartTimeRef = useRef<number | null>(null);
20
+
21
+ const clearTimer = useCallback(() => {
22
+ if (timerRef.current) {
23
+ clearTimeout(timerRef.current);
24
+ timerRef.current = null;
25
+ }
26
+ }, []);
27
+
28
+ // Start display timer when image becomes visible
29
+ useEffect(() => {
30
+ if (isVisible && currentIndex >= 0) {
31
+ displayStartTimeRef.current = Date.now();
32
+ }
33
+ }, [isVisible, currentIndex]);
34
+
35
+ // Handle image transitions when audio stops and minimum time has passed
36
+ useEffect(() => {
37
+ if (previousAudioPlaying.current && !isAudioPlaying && currentIndex >= 0) {
38
+ const timeElapsed = displayStartTimeRef.current ? Date.now() - displayStartTimeRef.current : 0;
39
+ const remainingTime = Math.max(0, DISPLAY_DURATION - timeElapsed);
40
+
41
+ clearTimer();
42
+
43
+ if (remainingTime > 0) {
44
+ // If minimum display time hasn't elapsed, wait for the remaining time
45
+ timerRef.current = setTimeout(() => {
46
+ const currentUrl = imageUrls[currentIndex];
47
+ setIsVisible(false);
48
+ setTimeout(() => {
49
+ setShownImages(prev => new Set(Array.from(prev).concat([currentUrl])));
50
+ setCurrentIndex(-1);
51
+ }, FADE_DURATION);
52
+ }, remainingTime);
53
+ } else {
54
+ // If minimum time has elapsed, start fade immediately
55
+ const currentUrl = imageUrls[currentIndex];
56
+ setIsVisible(false);
57
+ setTimeout(() => {
58
+ setShownImages(prev => new Set(Array.from(prev).concat([currentUrl])));
59
+ setCurrentIndex(-1);
60
+ }, FADE_DURATION);
61
+ }
62
+ }
63
+ previousAudioPlaying.current = isAudioPlaying;
64
+ }, [isAudioPlaying, currentIndex, imageUrls, clearTimer]);
65
+
66
+ // Start timer when new image is shown
67
+ useEffect(() => {
68
+ if (currentIndex >= 0 && !isAudioPlaying) {
69
+ clearTimer();
70
+ timerRef.current = setTimeout(() => {
71
+ const currentUrl = imageUrls[currentIndex];
72
+ setIsVisible(false);
73
+ setTimeout(() => {
74
+ setShownImages(prev => new Set(Array.from(prev).concat([currentUrl])));
75
+ setCurrentIndex(-1);
76
+ }, FADE_DURATION);
77
+ }, DISPLAY_DURATION);
78
+
79
+ return () => clearTimer();
80
+ }
81
+ }, [currentIndex, imageUrls, isAudioPlaying, clearTimer]);
82
+
83
+ // Handle image transitions
84
+ useEffect(() => {
85
+ if (currentIndex === -1) {
86
+ const nextIndex = imageUrls.findIndex(url => !shownImages.has(url));
87
+ if (nextIndex !== -1) {
88
+ setCurrentIndex(nextIndex);
89
+ setIsVisible(true);
90
+ } else if (shownImages.size > 0) {
91
+ onComplete?.();
92
+ }
93
+ }
94
+ }, [imageUrls, shownImages, currentIndex, onComplete]);
95
+
96
+ const handleImageSelect = (index: number) => {
97
+ clearTimer();
98
+ displayStartTimeRef.current = Date.now();
99
+ setCurrentIndex(index);
100
+ // Start with image invisible for manual selection too
101
+ setIsVisible(false);
102
+ setTimeout(() => setIsVisible(true), 50);
103
+ };
104
+
105
+ // Cleanup on unmount
106
+ useEffect(() => {
107
+ return () => clearTimer();
108
+ }, [clearTimer]);
109
+
110
+ if (!imageUrls.length) {
111
+ return null;
112
+ }
113
+
114
+ return (
115
+ <div
116
+ className="absolute inset-0 flex items-center justify-center"
117
+ onMouseEnter={() => {
118
+ setShowControls(true);
119
+ }}
120
+ onMouseLeave={() => {
121
+ setShowControls(false);
122
+ }}
123
+ >
124
+ {currentIndex !== -1 && (
125
+ <img
126
+ src={imageUrls[currentIndex]}
127
+ alt="Generated content"
128
+ style={{
129
+ opacity: isVisible ? 1 : 0,
130
+ transition: 'opacity 500ms ease-in-out'
131
+ }}
132
+ className="max-w-full max-h-full object-contain"
133
+ />
134
+ )}
135
+
136
+ {/* Image selection controls */}
137
+ <div
138
+ className={`absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2 p-2 bg-black/50 rounded-full transition-opacity duration-200 ${
139
+ showControls ? 'opacity-100' : 'opacity-0'
140
+ }`}
141
+ >
142
+ <button
143
+ onClick={() => {
144
+ clearTimer();
145
+ setCurrentIndex(-1);
146
+ setIsVisible(false);
147
+ }}
148
+ className={`w-3 h-3 rounded-full transition-all ${
149
+ currentIndex === -1 ? 'bg-white scale-110' : 'bg-white/50 hover:bg-white/75'
150
+ }`}
151
+ title="Hide all images"
152
+ />
153
+ {imageUrls.map((_, index) => (
154
+ <button
155
+ key={index}
156
+ onClick={() => handleImageSelect(index)}
157
+ className={`w-3 h-3 rounded-full transition-all ${
158
+ currentIndex === index ? 'bg-white scale-110' : 'bg-white/50 hover:bg-white/75'
159
+ }`}
160
+ title={`Show image ${index + 1}`}
161
+ />
162
+ ))}
163
+ </div>
164
+ </div>
165
+ );
166
+ }
@@ -0,0 +1,95 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ type MicrophoneVisualizerProps = {
4
+ audioContext: AudioContext | null;
5
+ sourceNode: MediaStreamAudioSourceNode | null;
6
+ size?: 'small' | 'large';
7
+ };
8
+
9
+ export const MicrophoneVisualizer = ({
10
+ audioContext,
11
+ sourceNode,
12
+ size = 'large'
13
+ }: MicrophoneVisualizerProps) => {
14
+ const canvasRef = useRef<HTMLCanvasElement>(null);
15
+ const analyzerRef = useRef<AnalyserNode | null>(null);
16
+ const animationRef = useRef<number>();
17
+
18
+ const dimensions = size === 'small' ? 48 : 64;
19
+ const ringRadius = size === 'small' ? 21 : 28;
20
+
21
+ useEffect(() => {
22
+ if (!audioContext || !sourceNode || !canvasRef.current) return;
23
+
24
+ const analyzer = audioContext.createAnalyser();
25
+ analyzerRef.current = analyzer;
26
+
27
+ analyzer.fftSize = 256;
28
+ analyzer.smoothingTimeConstant = 0.7;
29
+
30
+ sourceNode.connect(analyzer);
31
+
32
+ const draw = () => {
33
+ const canvas = canvasRef.current;
34
+ if (!canvas || !analyzer) return;
35
+
36
+ const ctx = canvas.getContext('2d');
37
+ if (!ctx) return;
38
+
39
+ const bufferLength = analyzer.frequencyBinCount;
40
+ const dataArray = new Uint8Array(bufferLength);
41
+ analyzer.getByteFrequencyData(dataArray);
42
+
43
+ // Calculate average volume
44
+ const average = dataArray.reduce((a, b) => a + b) / bufferLength;
45
+ const normalizedVolume = Math.min(average / 128, 1);
46
+
47
+ // Clear canvas
48
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
49
+
50
+ // Draw background ring
51
+ ctx.beginPath();
52
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
53
+ ctx.lineWidth = size === 'small' ? 3 : 4;
54
+ ctx.arc(canvas.width / 2, canvas.height / 2, ringRadius, 0, Math.PI * 2);
55
+ ctx.stroke();
56
+
57
+ // Draw volume indicator with increased brightness
58
+ ctx.beginPath();
59
+ ctx.strokeStyle = `hsla(${210 + normalizedVolume * 30}, 100%, 90%, 1.0)`;
60
+ ctx.lineWidth = size === 'small' ? 3 : 4;
61
+ ctx.arc(canvas.width / 2, canvas.height / 2, ringRadius, 0, Math.PI * 2 * normalizedVolume);
62
+ ctx.stroke();
63
+
64
+ // Subtle glow effect
65
+ ctx.shadowBlur = 10;
66
+ ctx.shadowColor = `hsla(${210 + normalizedVolume * 30}, 100%, 70%, ${0.6})`;
67
+
68
+ // Simpler second pass
69
+ ctx.beginPath();
70
+ ctx.strokeStyle = `hsla(${210 + normalizedVolume * 30}, 100%, 95%, ${0.4 + normalizedVolume * 0.4})`;
71
+ ctx.lineWidth = 2;
72
+ ctx.arc(canvas.width / 2, canvas.height / 2, ringRadius + 1, 0, Math.PI * 2 * normalizedVolume);
73
+ ctx.stroke();
74
+
75
+ animationRef.current = requestAnimationFrame(draw);
76
+ };
77
+
78
+ draw();
79
+
80
+ return () => {
81
+ if (animationRef.current) {
82
+ cancelAnimationFrame(animationRef.current);
83
+ }
84
+ };
85
+ }, [audioContext, sourceNode, size, ringRadius]);
86
+
87
+ return (
88
+ <canvas
89
+ ref={canvasRef}
90
+ width={dimensions}
91
+ height={dimensions}
92
+ className={size === 'small' ? 'w-12 h-12' : 'w-16 h-16'}
93
+ />
94
+ );
95
+ };
@@ -0,0 +1,116 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { Socket } from 'socket.io-client';
3
+ import { ClientToServerEvents, ServerToClientEvents } from '../../../../src/realtime/socket';
4
+ import { logger } from '../../utils/logger';
5
+
6
+ type ScreenshotCaptureProps = {
7
+ socket: Socket<ServerToClientEvents, ClientToServerEvents>;
8
+ };
9
+
10
+ export const ScreenshotCapture = ({ socket }: ScreenshotCaptureProps) => {
11
+ const activeStreamRef = useRef<MediaStream | null>(null);
12
+
13
+ const startScreenCapture = useCallback(async () => {
14
+ logger.log('Starting screen capture...');
15
+ try {
16
+ // Request screen capture with friendly message and whole screen preference
17
+ logger.log('Requesting display media...');
18
+ const stream = await navigator.mediaDevices.getDisplayMedia({
19
+ video: {
20
+ frameRate: 1,
21
+ displaySurface: 'monitor', // Prefer whole screen
22
+ cursor: 'never' // Don't need cursor
23
+ } as MediaTrackConstraints
24
+ });
25
+
26
+ // Store the stream reference
27
+ activeStreamRef.current = stream;
28
+
29
+ // Handle stream ending (user clicks "Stop Sharing")
30
+ stream.getVideoTracks()[0].onended = () => {
31
+ logger.log('Screen sharing stopped by user');
32
+ activeStreamRef.current = null;
33
+ };
34
+
35
+ return stream;
36
+ } catch (error) {
37
+ logger.error('Error starting screen capture:', error);
38
+ activeStreamRef.current = null;
39
+ throw error;
40
+ }
41
+ }, []);
42
+
43
+ const captureFrame = useCallback(async (stream: MediaStream) => {
44
+ logger.log('Capturing frame from stream...');
45
+ const track = stream.getVideoTracks()[0];
46
+
47
+ // Create video element to capture frame
48
+ const video = document.createElement('video');
49
+ video.srcObject = stream;
50
+
51
+ // Wait for video to load
52
+ await new Promise<void>((resolve) => {
53
+ video.onloadedmetadata = () => {
54
+ logger.log('Video metadata loaded, playing...');
55
+ video.play();
56
+ resolve();
57
+ };
58
+ });
59
+
60
+ // Create canvas and draw video frame
61
+ const canvas = document.createElement('canvas');
62
+ canvas.width = video.videoWidth;
63
+ canvas.height = video.videoHeight;
64
+ const ctx = canvas.getContext('2d');
65
+
66
+ if (!ctx) {
67
+ throw new Error('Could not get canvas context');
68
+ }
69
+
70
+ // Draw the video frame
71
+ ctx.drawImage(video, 0, 0);
72
+
73
+ // Convert to base64
74
+ const imageData = canvas.toDataURL('image/png');
75
+
76
+ // Clean up
77
+ video.remove();
78
+ canvas.remove();
79
+
80
+ return imageData;
81
+ }, []);
82
+
83
+ const handleScreenshotRequest = useCallback(async () => {
84
+ try {
85
+ // Use existing stream or request new one
86
+ const stream = activeStreamRef.current || await startScreenCapture();
87
+
88
+ // Capture frame from stream
89
+ const imageData = await captureFrame(stream);
90
+
91
+ logger.log('Sending screenshot data to server...');
92
+ socket.emit('screenshotCaptured', imageData);
93
+
94
+ } catch (error) {
95
+ logger.error('Error handling screenshot request:', error);
96
+ socket.emit('screenshotError', error instanceof Error ? error.message : 'Failed to capture screenshot');
97
+ }
98
+ }, [socket, startScreenCapture, captureFrame]);
99
+
100
+ useEffect(() => {
101
+ logger.log('Setting up screenshot request listener');
102
+ socket.on('requestScreenshot', handleScreenshotRequest);
103
+
104
+ return () => {
105
+ logger.log('Cleaning up screenshot request listener');
106
+ socket.off('requestScreenshot');
107
+ // Clean up stream if component unmounts
108
+ if (activeStreamRef.current) {
109
+ activeStreamRef.current.getTracks().forEach(track => track.stop());
110
+ activeStreamRef.current = null;
111
+ }
112
+ };
113
+ }, [socket, handleScreenshotRequest]);
114
+
115
+ return null; // This is a non-visual component
116
+ };
@@ -0,0 +1,27 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ export const useWindowResize = () => {
4
+ const [size, setSize] = useState({
5
+ width: 0,
6
+ height: 0,
7
+ });
8
+
9
+ useEffect(() => {
10
+ const handleResize = () => {
11
+ setSize({
12
+ width: window.innerWidth,
13
+ height: window.innerHeight,
14
+ });
15
+ };
16
+
17
+ handleResize();
18
+
19
+ window.addEventListener("resize", handleResize);
20
+
21
+ return () => {
22
+ window.removeEventListener("resize", handleResize);
23
+ };
24
+ }, []);
25
+
26
+ return size;
27
+ };
@@ -0,0 +1,33 @@
1
+ export function arrayBufferToBase64(
2
+ arrayBuffer: ArrayBuffer | Int16Array,
3
+ ): string {
4
+ let buffer: ArrayBuffer;
5
+ if (arrayBuffer instanceof ArrayBuffer) {
6
+ buffer = arrayBuffer;
7
+ } else {
8
+ buffer = arrayBuffer.buffer as ArrayBuffer;
9
+ }
10
+
11
+ const bytes = new Uint8Array(buffer);
12
+ const chunkSize = 0x80_00; // 32KB chunk size
13
+ let binary = '';
14
+
15
+ for (let i = 0; i < bytes.length; i += chunkSize) {
16
+ const chunk = bytes.subarray(i, i + chunkSize);
17
+ binary += String.fromCharCode.apply(null, chunk as any);
18
+ }
19
+
20
+ return btoa(binary);
21
+ }
22
+
23
+ export function base64ToArrayBuffer(base64: string): ArrayBuffer {
24
+ const binaryString = atob(base64)
25
+ const len = binaryString.length
26
+ const bytes = new Uint8Array(len)
27
+
28
+ for (let i = 0; i < len; i++) {
29
+ bytes[i] = binaryString.charCodeAt(i)
30
+ }
31
+
32
+ return bytes.buffer as ArrayBuffer;
33
+ }
@@ -0,0 +1,20 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ html, body {
6
+ background: rgb(17, 24, 39); /* matches from-gray-900 */
7
+ min-height: 100%;
8
+ overscroll-behavior: none; /* prevents bounce on some browsers */
9
+ }
10
+
11
+ /* For Safari/iOS */
12
+ @supports (-webkit-overflow-scrolling: touch) {
13
+ body {
14
+ position: fixed;
15
+ width: 100%;
16
+ height: 100%;
17
+ overflow-y: auto;
18
+ -webkit-overflow-scrolling: touch;
19
+ }
20
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import './index.css';
4
+ import App from './App';
5
+ import reportWebVitals from './reportWebVitals';
6
+
7
+ const root = ReactDOM.createRoot(
8
+ document.getElementById('root') as HTMLElement
9
+ );
10
+ root.render(
11
+ <React.StrictMode>
12
+ <App />
13
+ </React.StrictMode>
14
+ );
15
+
16
+ // If you want to start measuring performance in your app, pass a function
17
+ // to log results (for example: reportWebVitals(console.log))
18
+ // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19
+ reportWebVitals();
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
@@ -0,0 +1 @@
1
+ /// <reference types="react-scripts" />
@@ -0,0 +1,15 @@
1
+ import { ReportHandler } from 'web-vitals';
2
+
3
+ const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4
+ if (onPerfEntry && onPerfEntry instanceof Function) {
5
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6
+ getCLS(onPerfEntry);
7
+ getFID(onPerfEntry);
8
+ getFCP(onPerfEntry);
9
+ getLCP(onPerfEntry);
10
+ getTTFB(onPerfEntry);
11
+ });
12
+ }
13
+ };
14
+
15
+ export default reportWebVitals;
@@ -0,0 +1,5 @@
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom';
@@ -0,0 +1,45 @@
1
+ // Logger utility for centralized logging control
2
+
3
+ // Environment-based logging control
4
+ const isProduction = process.env.NODE_ENV === 'production';
5
+ let isLoggingEnabled = !isProduction;
6
+
7
+ export const logger = {
8
+ enable: () => {
9
+ isLoggingEnabled = true;
10
+ },
11
+
12
+ disable: () => {
13
+ isLoggingEnabled = false;
14
+ },
15
+
16
+ log: (...args: any[]) => {
17
+ if (isLoggingEnabled) {
18
+ console.log(...args);
19
+ }
20
+ },
21
+
22
+ // Additional logging levels if needed
23
+ debug: (...args: any[]) => {
24
+ if (isLoggingEnabled) {
25
+ console.debug(...args);
26
+ }
27
+ },
28
+
29
+ error: (...args: any[]) => {
30
+ // Always log errors, even in production
31
+ console.error(...args);
32
+ },
33
+
34
+ warn: (...args: any[]) => {
35
+ if (isLoggingEnabled) {
36
+ console.warn(...args);
37
+ }
38
+ },
39
+
40
+ info: (...args: any[]) => {
41
+ if (isLoggingEnabled) {
42
+ console.info(...args);
43
+ }
44
+ }
45
+ };
@@ -0,0 +1,14 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,jsx,ts,tsx}"
6
+ ],
7
+ theme: {
8
+ extend: {},
9
+ },
10
+ plugins: [
11
+ require('@tailwindcss/typography'),
12
+ ],
13
+ }
14
+
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "module": "esnext",
17
+ "moduleResolution": "node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "noEmit": true,
21
+ "jsx": "react-jsx"
22
+ },
23
+ "include": [
24
+ "src"
25
+ ],
26
+ "exclude": [
27
+ "node_modules",
28
+ "dist"
29
+ ]
30
+ }
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ watch: {
8
+ usePolling: true
9
+ },
10
+ host: true,
11
+ proxy: {
12
+ '/api': {
13
+ target: 'http://localhost:8081',
14
+ changeOrigin: true
15
+ },
16
+ '/socket.io': {
17
+ target: 'http://localhost:8081',
18
+ ws: true
19
+ }
20
+ }
21
+ }
22
+ })
@@ -0,0 +1,19 @@
1
+ import {SocketServer} from './src/SocketServer';
2
+ import {ApiServer} from "./src/ApiServer";
3
+
4
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
5
+ const CORS_HOSTS = process.env.CORS_HOSTS ? JSON.parse(process.env.CORS_HOSTS) : 'http://localhost:5173';
6
+ const PORT = process.env.PORT ? parseInt(process.env.PORT) : 8081;
7
+
8
+ if (!OPENAI_API_KEY) {
9
+ console.error(
10
+ `Environment variable "OPENAI_API_KEY" is required.\n` +
11
+ `Please set it in your .env file.`
12
+ );
13
+ process.exit(1);
14
+ }
15
+
16
+ const apiServer = new ApiServer(OPENAI_API_KEY, CORS_HOSTS);
17
+ apiServer.initServer();
18
+ const server = new SocketServer(OPENAI_API_KEY, CORS_HOSTS);
19
+ server.listen(apiServer.getServer(), PORT);
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "cortex-realtime-voice",
3
+ "module": "index.ts",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "concurrently \"cd client && bun run dev\" \"bun --watch run index.ts\"",
7
+ "dev:server": "bun --watch run index.ts",
8
+ "dev:client": "cd client && bun run dev",
9
+ "start": "bun run index.ts",
10
+ "start:test": "NODE_ENV=test bun run index.ts",
11
+ "start:prod": "NODE_ENV=production bun run index.ts"
12
+ },
13
+ "dependencies": {
14
+ "@hono/node-server": "1.13.7",
15
+ "@paralleldrive/cuid2": "2.2.2",
16
+ "hono": "4.6.13",
17
+ "socket.io": "4.8.1"
18
+ },
19
+ "devDependencies": {
20
+ "@types/bun": "1.1.14",
21
+ "@types/node": "22.10.1",
22
+ "bun-types": "^1.1.38",
23
+ "concurrently": "^8.2.2"
24
+ },
25
+ "peerDependencies": {
26
+ "typescript": "5.7.2"
27
+ }
28
+ }