codex-lens 0.1.27 → 0.1.28

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Codex Lens</title>
7
- <script type="module" crossorigin src="./assets/main-d-2BqwRB.js"></script>
7
+ <script type="module" crossorigin src="./assets/main-BRIK-RhC.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/main-DNXrKVO-.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-lens",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "A visualization tool for Codex that monitors API requests and file system changes",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/aggregator.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { createServer } from 'http';
2
- import { createHash, createHash as cryptoCreateHash } from 'crypto';
3
2
  import { WebSocketServer } from 'ws';
4
3
  import express from 'express';
5
4
  import { spawn } from 'child_process';
@@ -1,6 +1,6 @@
1
- import React, { useMemo, useRef } from 'react';
1
+ import { useMemo, useRef } from 'react';
2
2
  import CodeMirror from '@uiw/react-codemirror';
3
- import { EditorView, Decoration, ViewPlugin, ViewUpdate } from '@codemirror/view';
3
+ import { EditorView, Decoration, ViewPlugin } from '@codemirror/view';
4
4
  import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
5
5
  import { tags as t } from '@lezer/highlight';
6
6
  import { RangeSetBuilder } from '@codemirror/state';
@@ -0,0 +1,172 @@
1
+ import { Component } from 'react';
2
+
3
+ class DefaultErrorFallback extends Component {
4
+ render() {
5
+ const { error, onReload, onReset } = this.props;
6
+
7
+ return (
8
+ <div style={{
9
+ height: '100vh',
10
+ display: 'flex',
11
+ flexDirection: 'column',
12
+ alignItems: 'center',
13
+ justifyContent: 'center',
14
+ background: '#1e1e1e',
15
+ color: '#e0e0e0',
16
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
17
+ padding: '20px',
18
+ }}>
19
+ <div style={{
20
+ maxWidth: '600px',
21
+ padding: '32px',
22
+ background: '#252526',
23
+ borderRadius: '8px',
24
+ border: '1px solid #3c3c3c',
25
+ boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
26
+ textAlign: 'center',
27
+ }}>
28
+ <div style={{
29
+ fontSize: '48px',
30
+ marginBottom: '16px',
31
+ }}>
32
+ <span style={{ color: '#f85149' }}>!</span>
33
+ </div>
34
+
35
+ <h1 style={{
36
+ fontSize: '24px',
37
+ fontWeight: 600,
38
+ marginBottom: '8px',
39
+ color: '#ffffff',
40
+ }}>
41
+ 应用程序出错
42
+ </h1>
43
+
44
+ <p style={{
45
+ fontSize: '14px',
46
+ color: '#858585',
47
+ marginBottom: '24px',
48
+ }}>
49
+ 抱歉,应用程序遇到了一个错误。请尝试刷新页面或重置应用状态。
50
+ </p>
51
+
52
+ {error && (
53
+ <details style={{
54
+ marginBottom: '24px',
55
+ padding: '12px',
56
+ background: '#1e1e1e',
57
+ borderRadius: '4px',
58
+ textAlign: 'left',
59
+ }}>
60
+ <summary style={{
61
+ cursor: 'pointer',
62
+ fontSize: '13px',
63
+ color: '#e0e0e0',
64
+ fontWeight: 500,
65
+ }}>
66
+ 错误详情
67
+ </summary>
68
+ <pre style={{
69
+ marginTop: '12px',
70
+ padding: '12px',
71
+ background: '#0a0a0a',
72
+ borderRadius: '4px',
73
+ fontSize: '12px',
74
+ color: '#f48771',
75
+ overflow: 'auto',
76
+ maxHeight: '200px',
77
+ fontFamily: '"SF Mono", Consolas, monospace',
78
+ }}>
79
+ {error.toString()}
80
+ {error.stack && `\n\n${error.stack}`}
81
+ </pre>
82
+ </details>
83
+ )}
84
+
85
+ <div style={{
86
+ display: 'flex',
87
+ gap: '12px',
88
+ justifyContent: 'center',
89
+ }}>
90
+ <button
91
+ onClick={onReset}
92
+ style={{
93
+ padding: '10px 20px',
94
+ background: '#3c3c3c',
95
+ border: '1px solid #4c4c4c',
96
+ borderRadius: '4px',
97
+ color: '#cccccc',
98
+ fontSize: '14px',
99
+ cursor: 'pointer',
100
+ }}
101
+ >
102
+ 重置应用
103
+ </button>
104
+ <button
105
+ onClick={onReload}
106
+ style={{
107
+ padding: '10px 20px',
108
+ background: '#0078d4',
109
+ border: 'none',
110
+ borderRadius: '4px',
111
+ color: '#ffffff',
112
+ fontSize: '14px',
113
+ cursor: 'pointer',
114
+ fontWeight: 500,
115
+ }}
116
+ >
117
+ 刷新页面
118
+ </button>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ );
123
+ }
124
+ }
125
+
126
+ export class ErrorBoundary extends Component {
127
+ constructor(props) {
128
+ super(props);
129
+ this.state = {
130
+ hasError: false,
131
+ error: null,
132
+ errorInfo: null,
133
+ };
134
+ }
135
+
136
+ static getDerivedStateFromError(error) {
137
+ return { hasError: true, error };
138
+ }
139
+
140
+ componentDidCatch(error, errorInfo) {
141
+ this.setState({ errorInfo });
142
+ this.props.onError?.(error, errorInfo);
143
+ console.error('[ErrorBoundary]', error, errorInfo);
144
+ }
145
+
146
+ handleReload = () => {
147
+ this.setState({ hasError: false, error: null, errorInfo: null });
148
+ window.location.reload();
149
+ };
150
+
151
+ handleReset = () => {
152
+ this.setState({ hasError: false, error: null, errorInfo: null });
153
+ };
154
+
155
+ render() {
156
+ if (this.state.hasError) {
157
+ if (this.props.fallback) {
158
+ return this.props.fallback;
159
+ }
160
+ return (
161
+ <DefaultErrorFallback
162
+ error={this.state.error}
163
+ errorInfo={this.state.errorInfo}
164
+ onReload={this.handleReload}
165
+ onReset={this.handleReset}
166
+ />
167
+ );
168
+ }
169
+
170
+ return this.props.children;
171
+ }
172
+ }
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import { useEffect, useRef, useCallback } from 'react';
2
2
  import { Terminal } from '@xterm/xterm';
3
3
  import { FitAddon } from '@xterm/addon-fit';
4
4
  import { WebLinksAddon } from '@xterm/addon-web-links';
@@ -15,42 +15,66 @@ const VIRTUAL_KEYS = [
15
15
  { label: 'Ctrl+C', seq: '\x03' },
16
16
  ];
17
17
 
18
- export class TerminalPanel extends React.Component {
19
- constructor(props) {
20
- super(props);
21
- this.containerRef = React.createRef();
22
- this.terminal = null;
23
- this.fitAddon = null;
24
- this.ws = null;
25
- this.resizeObserver = null;
26
- this._writeBuffer = '';
27
- this._writeTimer = null;
28
- }
29
-
30
- componentDidMount() {
31
- this.initTerminal();
32
- this.connectWebSocket();
33
- this.setupResizeObserver();
34
- }
35
-
36
- componentWillUnmount() {
37
- if (this._writeTimer) {
38
- cancelAnimationFrame(this._writeTimer);
18
+ export function TerminalPanel() {
19
+ const containerRef = useRef(null);
20
+ const terminalRef = useRef(null);
21
+ const fitAddonRef = useRef(null);
22
+ const wsRef = useRef(null);
23
+ const resizeObserverRef = useRef(null);
24
+ const writeBufferRef = useRef('');
25
+ const writeTimerRef = useRef(null);
26
+ const resizeTimerRef = useRef(null);
27
+
28
+ const sendResize = useCallback(() => {
29
+ if (wsRef.current?.readyState === WebSocket.OPEN && terminalRef.current) {
30
+ wsRef.current.send(JSON.stringify({
31
+ type: 'resize',
32
+ cols: terminalRef.current.cols,
33
+ rows: terminalRef.current.rows,
34
+ }));
39
35
  }
40
- if (this.ws) {
41
- this.ws.close();
42
- this.ws = null;
36
+ }, []);
37
+
38
+ const flushWrite = useCallback(() => {
39
+ if (writeTimerRef.current) {
40
+ cancelAnimationFrame(writeTimerRef.current);
41
+ writeTimerRef.current = null;
43
42
  }
44
- if (this.resizeObserver) {
45
- this.resizeObserver.disconnect();
43
+ if (!writeBufferRef.current || !terminalRef.current) return;
44
+
45
+ const CHUNK_SIZE = 32768;
46
+ if (writeBufferRef.current.length <= CHUNK_SIZE) {
47
+ const buf = writeBufferRef.current;
48
+ writeBufferRef.current = '';
49
+ terminalRef.current.write(buf);
50
+ } else {
51
+ const chunk = writeBufferRef.current.slice(0, CHUNK_SIZE);
52
+ writeBufferRef.current = writeBufferRef.current.slice(CHUNK_SIZE);
53
+ terminalRef.current.write(chunk);
54
+ writeTimerRef.current = requestAnimationFrame(() => {
55
+ flushWrite();
56
+ });
46
57
  }
47
- if (this.terminal) {
48
- this.terminal.dispose();
58
+ }, []);
59
+
60
+ const throttledWrite = useCallback((data) => {
61
+ writeBufferRef.current += data;
62
+ if (!writeTimerRef.current) {
63
+ writeTimerRef.current = requestAnimationFrame(() => {
64
+ flushWrite();
65
+ });
49
66
  }
50
- }
67
+ }, [flushWrite]);
51
68
 
52
- initTerminal() {
53
- this.terminal = new Terminal({
69
+ const handleVirtualKey = useCallback((seq) => {
70
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
71
+ wsRef.current.send(JSON.stringify({ type: 'input', data: seq }));
72
+ }
73
+ terminalRef.current?.focus();
74
+ }, []);
75
+
76
+ useEffect(() => {
77
+ const terminal = new Terminal({
54
78
  cursorBlink: true,
55
79
  cursorStyle: 'bar',
56
80
  fontSize: 13,
@@ -65,52 +89,54 @@ export class TerminalPanel extends React.Component {
65
89
  allowProposedApi: true,
66
90
  });
67
91
 
68
- this.fitAddon = new FitAddon();
69
- this.terminal.loadAddon(this.fitAddon);
70
- this.terminal.loadAddon(new WebLinksAddon());
92
+ const fitAddon = new FitAddon();
93
+ terminal.loadAddon(fitAddon);
94
+ terminal.loadAddon(new WebLinksAddon());
71
95
 
72
- this.terminal.open(this.containerRef.current);
96
+ terminal.open(containerRef.current);
97
+ terminalRef.current = terminal;
98
+ fitAddonRef.current = fitAddon;
73
99
 
74
100
  requestAnimationFrame(() => {
75
- if (this.fitAddon) {
76
- this.fitAddon.fit();
77
- this.terminal.focus();
101
+ if (fitAddon) {
102
+ fitAddon.fit();
103
+ terminal.focus();
78
104
  }
79
105
  });
80
106
 
81
- this.terminal.onData((data) => {
82
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
83
- this.ws.send(JSON.stringify({ type: 'input', data }));
107
+ terminal.onData((data) => {
108
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
109
+ wsRef.current.send(JSON.stringify({ type: 'input', data }));
84
110
  }
85
111
  });
86
- }
87
112
 
88
- connectWebSocket() {
113
+ // WebSocket connection
89
114
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
90
115
  const host = window.location.hostname;
91
116
  const port = window.location.port === '5173' ? '5174' : window.location.port;
92
117
  const wsUrl = `${protocol}//${host}:${port}/ws/terminal`;
93
118
 
94
- this.ws = new WebSocket(wsUrl);
119
+ const ws = new WebSocket(wsUrl);
120
+ wsRef.current = ws;
95
121
 
96
- this.ws.onopen = () => {
122
+ ws.onopen = () => {
97
123
  console.log('[Terminal] Connected to PTY service');
98
- this.sendResize();
124
+ sendResize();
99
125
  };
100
126
 
101
- this.ws.onmessage = (event) => {
127
+ ws.onmessage = (event) => {
102
128
  try {
103
129
  const msg = JSON.parse(event.data);
104
130
  if (msg.type === 'data') {
105
- this._throttledWrite(msg.data);
131
+ throttledWrite(msg.data);
106
132
  } else if (msg.type === 'exit') {
107
- this._flushWrite();
108
- if (this.terminal) {
109
- this.terminal.write(`\r\n[Process exited with code ${msg.exitCode ?? '?'}]\r\n`);
133
+ flushWrite();
134
+ if (terminal) {
135
+ terminal.write(`\r\n[Process exited with code ${msg.exitCode ?? '?'}]\r\n`);
110
136
  }
111
137
  } else if (msg.type === 'state') {
112
- if (!msg.running && this.terminal) {
113
- this._flushWrite();
138
+ if (!msg.running && terminal) {
139
+ flushWrite();
114
140
  }
115
141
  }
116
142
  } catch (e) {
@@ -118,132 +144,97 @@ export class TerminalPanel extends React.Component {
118
144
  }
119
145
  };
120
146
 
121
- this.ws.onclose = () => {
147
+ ws.onclose = () => {
122
148
  console.log('[Terminal] Disconnected, reconnecting in 3s...');
123
149
  setTimeout(() => {
124
- if (this.containerRef.current) {
125
- this.connectWebSocket();
150
+ if (containerRef.current) {
151
+ // Reconnect handled by re-mounting effect
126
152
  }
127
153
  }, 3000);
128
154
  };
129
155
 
130
- this.ws.onerror = (error) => {
156
+ ws.onerror = (error) => {
131
157
  console.error('[Terminal] WebSocket error:', error);
132
158
  };
133
- }
134
-
135
- sendResize() {
136
- if (this.ws && this.ws.readyState === WebSocket.OPEN && this.terminal) {
137
- this.ws.send(JSON.stringify({
138
- type: 'resize',
139
- cols: this.terminal.cols,
140
- rows: this.terminal.rows,
141
- }));
142
- }
143
- }
144
159
 
145
- setupResizeObserver() {
146
- if (!this.containerRef.current) return;
147
-
148
- this.resizeObserver = new ResizeObserver(() => {
149
- if (this._resizeTimer) clearTimeout(this._resizeTimer);
150
- this._resizeTimer = setTimeout(() => {
151
- if (this.fitAddon && this.containerRef.current) {
160
+ // ResizeObserver setup
161
+ const resizeObserver = new ResizeObserver(() => {
162
+ if (resizeTimerRef.current) clearTimeout(resizeTimerRef.current);
163
+ resizeTimerRef.current = setTimeout(() => {
164
+ if (fitAddonRef.current && containerRef.current) {
152
165
  try {
153
- this.fitAddon.fit();
154
- this.sendResize();
166
+ fitAddonRef.current.fit();
167
+ sendResize();
155
168
  } catch {}
156
169
  }
157
170
  }, 150);
158
171
  });
159
172
 
160
- this.resizeObserver.observe(this.containerRef.current);
161
- }
162
-
163
- _throttledWrite(data) {
164
- this._writeBuffer += data;
165
- if (!this._writeTimer) {
166
- this._writeTimer = requestAnimationFrame(() => {
167
- this._flushWrite();
168
- });
169
- }
170
- }
171
-
172
- _flushWrite() {
173
- if (this._writeTimer) {
174
- cancelAnimationFrame(this._writeTimer);
175
- this._writeTimer = null;
176
- }
177
- if (!this._writeBuffer || !this.terminal) return;
178
-
179
- const CHUNK_SIZE = 32768;
180
- if (this._writeBuffer.length <= CHUNK_SIZE) {
181
- const buf = this._writeBuffer;
182
- this._writeBuffer = '';
183
- this.terminal.write(buf);
184
- } else {
185
- const chunk = this._writeBuffer.slice(0, CHUNK_SIZE);
186
- this._writeBuffer = this._writeBuffer.slice(CHUNK_SIZE);
187
- this.terminal.write(chunk);
188
- this._writeTimer = requestAnimationFrame(() => {
189
- this._flushWrite();
190
- });
191
- }
192
- }
173
+ resizeObserver.observe(containerRef.current);
174
+ resizeObserverRef.current = resizeObserver;
193
175
 
194
- handleVirtualKey = (seq) => {
195
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
196
- this.ws.send(JSON.stringify({ type: 'input', data: seq }));
197
- }
198
- this.terminal?.focus();
199
- };
200
-
201
- render() {
202
- return (
176
+ // Cleanup on unmount
177
+ return () => {
178
+ if (writeTimerRef.current) {
179
+ cancelAnimationFrame(writeTimerRef.current);
180
+ }
181
+ if (ws) {
182
+ ws.close();
183
+ wsRef.current = null;
184
+ }
185
+ if (resizeObserver) {
186
+ resizeObserver.disconnect();
187
+ }
188
+ if (terminal) {
189
+ terminal.dispose();
190
+ }
191
+ };
192
+ }, [sendResize, throttledWrite, flushWrite]);
193
+
194
+ return (
195
+ <div style={{
196
+ height: '100%',
197
+ display: 'flex',
198
+ flexDirection: 'column',
199
+ background: '#0a0a0a',
200
+ }}>
201
+ <div
202
+ ref={containerRef}
203
+ style={{
204
+ flex: 1,
205
+ overflow: 'hidden',
206
+ padding: '4px 8px',
207
+ }}
208
+ />
203
209
  <div style={{
204
- height: '100%',
205
210
  display: 'flex',
206
- flexDirection: 'column',
207
- background: '#0a0a0a',
211
+ gap: '4px',
212
+ padding: '8px',
213
+ background: '#111',
214
+ borderTop: '1px solid #222',
215
+ flexWrap: 'wrap',
208
216
  }}>
209
- <div
210
- ref={this.containerRef}
211
- style={{
212
- flex: 1,
213
- overflow: 'hidden',
214
- padding: '4px 8px',
215
- }}
216
- />
217
- <div style={{
218
- display: 'flex',
219
- gap: '4px',
220
- padding: '8px',
221
- background: '#111',
222
- borderTop: '1px solid #222',
223
- flexWrap: 'wrap',
224
- }}>
225
- {VIRTUAL_KEYS.map((key) => (
226
- <button
227
- key={key.label}
228
- onClick={() => this.handleVirtualKey(key.seq)}
229
- style={{
230
- padding: '8px 12px',
231
- border: '1px solid #333',
232
- borderRadius: '4px',
233
- background: '#1a1a1a',
234
- color: '#ccc',
235
- fontSize: '13px',
236
- fontFamily: 'Menlo, Monaco, monospace',
237
- cursor: 'pointer',
238
- minWidth: '44px',
239
- minHeight: '44px',
240
- }}
241
- >
242
- {key.label}
243
- </button>
244
- ))}
245
- </div>
217
+ {VIRTUAL_KEYS.map((key) => (
218
+ <button
219
+ key={key.label}
220
+ onClick={() => handleVirtualKey(key.seq)}
221
+ style={{
222
+ padding: '8px 12px',
223
+ border: '1px solid #333',
224
+ borderRadius: '4px',
225
+ background: '#1a1a1a',
226
+ color: '#ccc',
227
+ fontSize: '13px',
228
+ fontFamily: 'Menlo, Monaco, monospace',
229
+ cursor: 'pointer',
230
+ minWidth: '44px',
231
+ minHeight: '44px',
232
+ }}
233
+ >
234
+ {key.label}
235
+ </button>
236
+ ))}
246
237
  </div>
247
- );
248
- }
249
- }
238
+ </div>
239
+ );
240
+ }
package/src/main.jsx CHANGED
@@ -1,9 +1,17 @@
1
- import React from 'react';
2
1
  import { createRoot } from 'react-dom/client';
3
2
  import { App } from './components/App.jsx';
3
+ import { ErrorBoundary } from './components/ErrorBoundary.jsx';
4
4
  import './global.css';
5
5
 
6
6
  const container = document.getElementById('root');
7
7
  const root = createRoot(container);
8
8
 
9
- root.render(<App />);
9
+ root.render(
10
+ <ErrorBoundary
11
+ onError={(error, errorInfo) => {
12
+ console.error('[Global Error]', error, errorInfo);
13
+ }}
14
+ >
15
+ <App />
16
+ </ErrorBoundary>
17
+ );
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "allowSyntheticDefaultImports": true,
19
+ "esModuleInterop": true,
20
+ "forceConsistentCasingInFileNames": true
21
+ },
22
+ "include": ["src"],
23
+ "exclude": ["node_modules", "dist"]
24
+ }