codex-lens 0.1.12 → 0.1.14

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,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Codex Viewer</title>
7
- <script type="module" crossorigin src="./assets/main-4W6-SFMa.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/main-DA0U-HuE.css">
7
+ <script type="module" crossorigin src="./assets/main-gWFaVAuJ.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/main-0MulWSMb.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-lens",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "A visualization tool for Codex that monitors API requests and file system changes with task snapshot rollback",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/aggregator.js CHANGED
@@ -7,7 +7,6 @@ import { createProxyServer } from './proxy.js';
7
7
  import { FileWatcher, scanDirectory } from './watcher.js';
8
8
  import { createLogger } from './lib/logger.js';
9
9
  import { spawnCodex, writeToPty, resizePty, killPty, onPtyData, onPtyExit, getPtyState, getOutputBuffer } from './pty-manager.js';
10
- import { SnapshotManager } from './snapshot-manager.js';
11
10
  import path from 'path';
12
11
  import { fileURLToPath } from 'url';
13
12
  import { readFileSync, existsSync } from 'fs';
@@ -57,9 +56,6 @@ class Aggregator {
57
56
  this.proxyServer = null;
58
57
  this.fileWatcher = null;
59
58
  this.ptyProcess = null;
60
- this.snapshotManager = new SnapshotManager();
61
- this.currentTaskId = null;
62
- this.taskStatus = 'idle';
63
59
  }
64
60
 
65
61
  async start(proxyPort) {
@@ -251,121 +247,9 @@ class Aggregator {
251
247
  }
252
248
  }));
253
249
  }
254
- } else if (data.type === 'start_task') {
255
- this.handleStartTask(ws);
256
- } else if (data.type === 'rollback_task') {
257
- this.handleRollbackTask(ws);
258
- } else if (data.type === 'complete_task') {
259
- this.handleCompleteTask(ws);
260
- } else if (data.type === 'get_task_status') {
261
- ws.send(JSON.stringify({
262
- type: 'task_status',
263
- data: {
264
- status: this.taskStatus,
265
- taskId: this.currentTaskId
266
- }
267
- }));
268
250
  }
269
251
  }
270
252
 
271
- async handleStartTask(ws) {
272
- if (this.taskStatus === 'running') {
273
- ws.send(JSON.stringify({ type: 'error', message: 'Task already running' }));
274
- return;
275
- }
276
-
277
- this.currentTaskId = Date.now().toString();
278
- this.taskStatus = 'running';
279
-
280
- const result = await this.snapshotManager.createSnapshot(this.projectRoot, this.currentTaskId);
281
-
282
- if (result.success) {
283
- logger.info(`Task started: ${this.currentTaskId}, ${result.filesCount} files snapshotted`);
284
- this.broadcast({
285
- type: 'task_status',
286
- data: {
287
- status: this.taskStatus,
288
- taskId: this.currentTaskId,
289
- filesCount: result.filesCount
290
- }
291
- });
292
- ws.send(JSON.stringify({
293
- type: 'task_started',
294
- data: {
295
- taskId: this.currentTaskId,
296
- filesCount: result.filesCount
297
- }
298
- }));
299
- } else {
300
- this.taskStatus = 'idle';
301
- this.currentTaskId = null;
302
- ws.send(JSON.stringify({ type: 'error', message: 'Failed to create snapshot: ' + result.error }));
303
- }
304
- }
305
-
306
- async handleRollbackTask(ws) {
307
- if (!this.currentTaskId || this.taskStatus !== 'running') {
308
- ws.send(JSON.stringify({ type: 'error', message: 'No active task to rollback' }));
309
- return;
310
- }
311
-
312
- const taskId = this.currentTaskId;
313
- const result = await this.snapshotManager.restoreSnapshot(taskId);
314
-
315
- if (result.success) {
316
- await this.snapshotManager.deleteSnapshot(taskId);
317
- logger.info(`Task rolled back: ${taskId}, ${result.restoredCount} files restored`);
318
-
319
- this.taskStatus = 'idle';
320
- this.currentTaskId = null;
321
-
322
- this.broadcast({
323
- type: 'task_status',
324
- data: {
325
- status: this.taskStatus,
326
- taskId: null
327
- }
328
- });
329
-
330
- ws.send(JSON.stringify({
331
- type: 'task_rolled_back',
332
- data: {
333
- restoredCount: result.restoredCount
334
- }
335
- }));
336
- } else {
337
- ws.send(JSON.stringify({ type: 'error', message: 'Failed to rollback: ' + result.error }));
338
- }
339
- }
340
-
341
- async handleCompleteTask(ws) {
342
- if (!this.currentTaskId || this.taskStatus !== 'running') {
343
- ws.send(JSON.stringify({ type: 'error', message: 'No active task to complete' }));
344
- return;
345
- }
346
-
347
- const taskId = this.currentTaskId;
348
- await this.snapshotManager.deleteSnapshot(taskId);
349
-
350
- logger.info(`Task completed: ${taskId}`);
351
-
352
- this.taskStatus = 'idle';
353
- this.currentTaskId = null;
354
-
355
- this.broadcast({
356
- type: 'task_status',
357
- data: {
358
- status: this.taskStatus,
359
- taskId: null
360
- }
361
- });
362
-
363
- ws.send(JSON.stringify({
364
- type: 'task_completed',
365
- data: {}
366
- }));
367
- }
368
-
369
253
  broadcast(event) {
370
254
  const message = JSON.stringify(event);
371
255
  for (const client of this.clients) {
@@ -5,12 +5,8 @@ export function App() {
5
5
  const [files, setFiles] = useState([]);
6
6
  const [tabs, setTabs] = useState([]);
7
7
  const [activeTabId, setActiveTabId] = useState(null);
8
- const [recentChanges, setRecentChanges] = useState([]);
9
8
  const [wsStatus, setWsStatus] = useState('disconnected');
10
9
  const [contextMenu, setContextMenu] = useState(null);
11
- const [taskStatus, setTaskStatus] = useState('idle');
12
- const [taskId, setTaskId] = useState(null);
13
- const [showRollbackConfirm, setShowRollbackConfirm] = useState(false);
14
10
  const [version, setVersion] = useState(null);
15
11
  const [latestVersion, setLatestVersion] = useState(null);
16
12
  const [hasUpdate, setHasUpdate] = useState(false);
@@ -52,24 +48,6 @@ export function App() {
52
48
  }
53
49
 
54
50
  function handleKeyDown(e) {
55
- if (e.ctrlKey && e.shiftKey && e.key === 'Enter') {
56
- e.preventDefault();
57
- handleStartTask();
58
- return;
59
- }
60
-
61
- if (taskStatus === 'running' && e.ctrlKey && e.key === 'Enter') {
62
- e.preventDefault();
63
- handleCompleteTask();
64
- return;
65
- }
66
-
67
- if (taskStatus === 'running' && e.ctrlKey && e.key === 'z') {
68
- e.preventDefault();
69
- setShowRollbackConfirm(true);
70
- return;
71
- }
72
-
73
51
  if (!activeTabId) return;
74
52
 
75
53
  if (e.ctrlKey && e.key === 'w') {
@@ -94,13 +72,12 @@ export function App() {
94
72
  const ws = new WebSocket(wsUrl);
95
73
 
96
74
  ws.onopen = () => {
97
- console.log('Connected to Codex Viewer');
75
+ console.log('Connected to Codex Lens');
98
76
  setWsStatus('connected');
99
- ws.send(JSON.stringify({ type: 'get_task_status' }));
100
77
  };
101
78
 
102
79
  ws.onclose = () => {
103
- console.log('Disconnected from Codex Viewer');
80
+ console.log('Disconnected from Codex Lens');
104
81
  setWsStatus('disconnected');
105
82
  setTimeout(connectWebSocket, 3000);
106
83
  };
@@ -132,28 +109,6 @@ export function App() {
132
109
  case 'file_content':
133
110
  openFileInTab(msg.data);
134
111
  break;
135
- case 'task_status':
136
- setTaskStatus(msg.data.status);
137
- setTaskId(msg.data.taskId);
138
- break;
139
- case 'task_started':
140
- setTaskStatus('running');
141
- setTaskId(msg.data.taskId);
142
- alert(`任务已创建,快照包含 ${msg.data.filesCount} 个文件`);
143
- break;
144
- case 'task_rolled_back':
145
- setTaskStatus('idle');
146
- setTaskId(null);
147
- setTabs([]);
148
- setActiveTabId(null);
149
- alert(`已撤回 ${msg.data.restoredCount} 个文件`);
150
- setShowRollbackConfirm(false);
151
- break;
152
- case 'task_completed':
153
- setTaskStatus('idle');
154
- setTaskId(null);
155
- alert('任务已完成,修改已保留');
156
- break;
157
112
  case 'connected':
158
113
  console.log('Server confirmed connection');
159
114
  break;
@@ -162,27 +117,6 @@ export function App() {
162
117
  }
163
118
  }
164
119
 
165
- function handleStartTask() {
166
- if (taskStatus === 'running') return;
167
- if (wsRef.current?.readyState === WebSocket.OPEN) {
168
- wsRef.current.send(JSON.stringify({ type: 'start_task' }));
169
- }
170
- }
171
-
172
- function handleRollbackTask() {
173
- if (taskStatus !== 'running') return;
174
- if (wsRef.current?.readyState === WebSocket.OPEN) {
175
- wsRef.current.send(JSON.stringify({ type: 'rollback_task' }));
176
- }
177
- }
178
-
179
- function handleCompleteTask() {
180
- if (taskStatus !== 'running') return;
181
- if (wsRef.current?.readyState === WebSocket.OPEN) {
182
- wsRef.current.send(JSON.stringify({ type: 'complete_task' }));
183
- }
184
- }
185
-
186
120
  function openFileInTab(data) {
187
121
  const fileName = data.path.split(/[/\\]/).pop();
188
122
 
@@ -209,22 +143,28 @@ export function App() {
209
143
 
210
144
  function handleFileChange(data) {
211
145
  const fileName = data.path.split(/[/\\]/).pop();
212
- const existingTab = tabs.find(t => t.path === data.path);
213
-
214
- if (existingTab) {
215
- setTabs(prev => prev.map(t =>
216
- t.path === data.path
217
- ? { ...t, content: data.newContent, diff: data.diff, isDiff: true }
218
- : t
219
- ));
220
- if (activeTabId === existingTab.id) {
146
+
147
+ setTabs(prevTabs => {
148
+ const existingTab = prevTabs.find(t => t.path === data.path);
149
+
150
+ if (existingTab) {
151
+ const updatedTab = { ...existingTab, content: data.newContent, diff: data.diff, isDiff: true };
221
152
  setActiveTabId(existingTab.id);
153
+ return prevTabs.map(t =>
154
+ t.path === data.path ? updatedTab : t
155
+ );
156
+ } else {
157
+ const newTab = {
158
+ id: Date.now().toString(),
159
+ path: data.path,
160
+ name: fileName,
161
+ content: data.newContent,
162
+ diff: data.diff,
163
+ isDiff: true
164
+ };
165
+ setActiveTabId(newTab.id);
166
+ return [...prevTabs, newTab];
222
167
  }
223
- }
224
-
225
- setRecentChanges(prev => {
226
- const filtered = prev.filter(c => c.path !== data.path);
227
- return [{ path: data.path, time: Date.now(), diff: data.diff }, ...filtered].slice(0, 10);
228
168
  });
229
169
  }
230
170
 
@@ -240,28 +180,19 @@ export function App() {
240
180
  }
241
181
 
242
182
  function closeTab(tabId) {
243
- const tabIndex = tabs.findIndex(t => t.id === tabId);
244
- const newTabs = tabs.filter(t => t.id !== tabId);
245
-
246
- if (tabs.length === 1) {
247
- setTabs([]);
248
- setActiveTabId(null);
249
- } else if (activeTabId === tabId) {
250
- if (tabIndex >= newTabs.length) {
251
- setActiveTabId(newTabs[newTabs.length - 1].id);
252
- } else {
253
- setActiveTabId(newTabs[tabIndex].id);
183
+ setTabs(prev => {
184
+ const newTabs = prev.filter(t => t.id !== tabId);
185
+ if (activeTabId === tabId) {
186
+ const newActiveId = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
187
+ setActiveTabId(newActiveId);
254
188
  }
255
- setTabs(newTabs);
256
- } else {
257
- setTabs(newTabs);
258
- }
189
+ return newTabs;
190
+ });
259
191
  }
260
192
 
261
- function closeOtherTabs(keepTabId) {
262
- const keepTab = tabs.find(t => t.id === keepTabId);
263
- setTabs(keepTab ? [keepTab] : []);
264
- setActiveTabId(keepTabId);
193
+ function closeOtherTabs(tabId) {
194
+ setTabs(prev => prev.filter(t => t.id === tabId));
195
+ setActiveTabId(tabId);
265
196
  }
266
197
 
267
198
  function closeAllTabs() {
@@ -269,27 +200,31 @@ export function App() {
269
200
  setActiveTabId(null);
270
201
  }
271
202
 
203
+ function clearAllDiff() {
204
+ setTabs(prevTabs => prevTabs.map(tab => ({ ...tab, diff: null, isDiff: false })));
205
+ }
206
+
272
207
  function switchToNextTab() {
273
- if (tabs.length <= 1) return;
274
208
  const currentIndex = tabs.findIndex(t => t.id === activeTabId);
275
- const nextIndex = (currentIndex + 1) % tabs.length;
276
- setActiveTabId(tabs[nextIndex].id);
209
+ if (currentIndex < tabs.length - 1) {
210
+ setActiveTabId(tabs[currentIndex + 1].id);
211
+ } else if (tabs.length > 0) {
212
+ setActiveTabId(tabs[0].id);
213
+ }
277
214
  }
278
215
 
279
216
  function switchToPrevTab() {
280
- if (tabs.length <= 1) return;
281
217
  const currentIndex = tabs.findIndex(t => t.id === activeTabId);
282
- const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
283
- setActiveTabId(tabs[prevIndex].id);
218
+ if (currentIndex > 0) {
219
+ setActiveTabId(tabs[currentIndex - 1].id);
220
+ } else if (tabs.length > 0) {
221
+ setActiveTabId(tabs[tabs.length - 1].id);
222
+ }
284
223
  }
285
224
 
286
225
  function handleContextMenu(e, tabId) {
287
226
  e.preventDefault();
288
- setContextMenu({
289
- x: e.clientX,
290
- y: e.clientY,
291
- tabId
292
- });
227
+ setContextMenu({ x: e.clientX, y: e.clientY, tabId });
293
228
  }
294
229
 
295
230
  const activeTab = tabs.find(t => t.id === activeTabId);
@@ -298,17 +233,15 @@ export function App() {
298
233
  <div className="app-container">
299
234
  <LeftPanel
300
235
  files={files}
301
- recentChanges={recentChanges}
302
236
  activeFile={activeTab?.path || null}
303
237
  onFileClick={handleFileClick}
304
238
  />
305
239
  <div className="panel middle-panel">
306
- <TaskBar
307
- taskStatus={taskStatus}
308
- onStartTask={handleStartTask}
309
- onRollback={() => setShowRollbackConfirm(true)}
310
- onComplete={handleCompleteTask}
311
- />
240
+ <div className="task-bar">
241
+ <button className="task-btn task-btn-clear" onClick={clearAllDiff} title="清空所有 diff 显示">
242
+ 清空 diff
243
+ </button>
244
+ </div>
312
245
  <TabBar
313
246
  tabs={tabs}
314
247
  activeTabId={activeTabId}
@@ -334,24 +267,6 @@ export function App() {
334
267
  <pre className="code-content">{activeTab.content}</pre>
335
268
  )}
336
269
  </div>
337
- {showRollbackConfirm && (
338
- <div className="modal-overlay" onClick={() => setShowRollbackConfirm(false)}>
339
- <div className="modal-content" onClick={e => e.stopPropagation()}>
340
- <div className="modal-title">确认撤回</div>
341
- <div className="modal-body">
342
- 确定要撤回所有修改吗?此操作将把项目恢复到任务开始前的状态,且无法撤销。
343
- </div>
344
- <div className="modal-buttons">
345
- <button className="modal-btn modal-btn-cancel" onClick={() => setShowRollbackConfirm(false)}>
346
- 取消
347
- </button>
348
- <button className="modal-btn modal-btn-danger" onClick={handleRollbackTask}>
349
- 确认撤回
350
- </button>
351
- </div>
352
- </div>
353
- </div>
354
- )}
355
270
  </div>
356
271
  {contextMenu && (
357
272
  <ContextMenu
@@ -393,32 +308,6 @@ export function App() {
393
308
  );
394
309
  }
395
310
 
396
- function TaskBar({ taskStatus, onStartTask, onRollback, onComplete }) {
397
- return (
398
- <div className="task-bar">
399
- <div className={`task-status ${taskStatus}`}>
400
- {taskStatus === 'idle' ? '空闲' : '任务进行中'}
401
- </div>
402
- <div className="task-buttons">
403
- {taskStatus === 'idle' ? (
404
- <button className="task-btn task-btn-start" disabled title="快照功能暂时禁用">
405
- 开始任务
406
- </button>
407
- ) : (
408
- <>
409
- <button className="task-btn task-btn-rollback" onClick={onRollback} title="Ctrl+Z">
410
- 撤回
411
- </button>
412
- <button className="task-btn task-btn-complete" onClick={onComplete} title="Ctrl+Enter">
413
- 完成
414
- </button>
415
- </>
416
- )}
417
- </div>
418
- </div>
419
- );
420
- }
421
-
422
311
  function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
423
312
  return (
424
313
  <div className="tab-bar">
@@ -447,15 +336,15 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
447
336
 
448
337
  function ContextMenu({ x, y, onClose, onCloseTab, onCloseOtherTabs, onCloseAllTabs }) {
449
338
  return (
450
- <div className="context-menu" style={{ left: x, top: y }} onClick={e => e.stopPropagation()}>
451
- <div className="context-menu-item" onClick={onCloseTab}>关闭标签页</div>
452
- <div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他标签页</div>
453
- <div className="context-menu-item" onClick={onCloseAllTabs}>关闭全部标签页</div>
339
+ <div className="context-menu" style={{ left: x, top: y }} onClick={(e) => e.stopPropagation()}>
340
+ <div className="context-menu-item" onClick={onCloseTab}>关闭</div>
341
+ <div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他</div>
342
+ <div className="context-menu-item" onClick={onCloseAllTabs}>关闭所有</div>
454
343
  </div>
455
344
  );
456
345
  }
457
346
 
458
- function LeftPanel({ files, recentChanges, activeFile, onFileClick }) {
347
+ function LeftPanel({ files, activeFile, onFileClick }) {
459
348
  const [expandedDirs, setExpandedDirs] = useState({});
460
349
  const [contextMenu, setContextMenu] = useState(null);
461
350
 
@@ -532,27 +421,10 @@ function LeftPanel({ files, recentChanges, activeFile, onFileClick }) {
532
421
  文件浏览器
533
422
  </div>
534
423
  <div className="panel-content">
535
- {recentChanges.length > 0 && (
536
- <div className="section">
537
- <div className="section-title">最近修改</div>
538
- {recentChanges.map((change, i) => (
539
- <div
540
- key={i}
541
- className={`file-item ${activeFile === change.path ? 'active' : ''}`}
542
- onDoubleClick={() => onFileClick(change.path)}
543
- >
544
- <span className="file-icon">📝</span>
545
- <span className="file-name">{change.path.split(/[/\\]/).pop()}</span>
546
- </div>
547
- ))}
548
- </div>
549
- )}
550
424
  <div className="section">
551
425
  <div className="section-title">项目文件</div>
552
426
  {files.length === 0 ? (
553
- <div className="empty-state">
554
- {recentChanges.length === 0 ? '等待文件变化...' : '暂无其他文件'}
555
- </div>
427
+ <div className="empty-state">等待文件变化...</div>
556
428
  ) : (
557
429
  renderFileTree(files)
558
430
  )}
@@ -577,11 +449,25 @@ function LeftPanel({ files, recentChanges, activeFile, onFileClick }) {
577
449
  }
578
450
 
579
451
  function getFileIcon(type) {
580
- switch (type) {
581
- case 'directory': return '📁';
582
- case 'file': return '📄';
583
- default: return '📄';
584
- }
452
+ const icons = {
453
+ '.js': '📜',
454
+ '.jsx': '⚛️',
455
+ '.ts': '📘',
456
+ '.tsx': '⚛️',
457
+ '.py': '🐍',
458
+ '.json': '📋',
459
+ '.css': '🎨',
460
+ '.html': '🌐',
461
+ '.md': '📝',
462
+ '.txt': '📄',
463
+ '.yml': '⚙️',
464
+ '.yaml': '⚙️',
465
+ '.toml': '⚙️',
466
+ '.ini': '⚙️',
467
+ '.env': '🔐',
468
+ '.gitignore': '🙈',
469
+ '.dockerignore': '🐳',
470
+ 'default': '📄'
471
+ };
472
+ return icons[type] || icons['default'];
585
473
  }
586
-
587
- export default App;
package/src/global.css CHANGED
@@ -553,46 +553,14 @@ body {
553
553
  transform: translateY(0);
554
554
  }
555
555
 
556
- .task-btn-start {
557
- background: var(--accent-color);
558
- color: white;
559
- box-shadow: var(--shadow-sm);
560
- }
561
-
562
- .task-btn-start:hover {
563
- background: var(--accent-hover);
564
- box-shadow: var(--shadow-glow);
565
- }
566
-
567
- .task-btn-start:disabled {
556
+ .task-btn-clear {
568
557
  background: var(--bg-tertiary);
569
- color: var(--text-muted);
570
- cursor: not-allowed;
571
- box-shadow: none;
572
- }
573
-
574
- .task-btn-start:disabled:hover {
575
- transform: none;
576
- }
577
-
578
- .task-btn-rollback {
579
- background: var(--danger-color);
580
- color: white;
581
- }
582
-
583
- .task-btn-rollback:hover {
584
- background: var(--danger-hover);
585
- box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
586
- }
587
-
588
- .task-btn-complete {
589
- background: var(--success-color);
590
- color: white;
558
+ color: var(--text-primary);
559
+ border: 1px solid var(--border-color);
591
560
  }
592
561
 
593
- .task-btn-complete:hover {
594
- background: var(--success-hover);
595
- box-shadow: 0 4px 12px rgba(34, 197, 94, 0.3);
562
+ .task-btn-clear:hover {
563
+ background: var(--border-color);
596
564
  }
597
565
 
598
566
  .modal-overlay {