antigravity-mobile-proxy 0.1.6 → 0.1.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 (113) hide show
  1. package/.next/standalone/.next/app-path-routes-manifest.json +2 -0
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/routes-manifest.json +12 -0
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  9. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  10. package/.next/standalone/.next/server/app/_not-found/page.js +5 -6
  11. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js +5 -1
  14. package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/api/v1/changes/active/route/app-paths-manifest.json +3 -0
  16. package/.next/standalone/.next/server/app/api/v1/changes/active/route/build-manifest.json +11 -0
  17. package/.next/standalone/.next/server/app/api/v1/changes/active/route/server-reference-manifest.json +4 -0
  18. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js +11 -0
  19. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.map +5 -0
  20. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.nft.json +1 -0
  21. package/.next/standalone/.next/server/app/api/v1/changes/active/route_client-reference-manifest.js +2 -0
  22. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/app-paths-manifest.json +3 -0
  23. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/build-manifest.json +11 -0
  24. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/server-reference-manifest.json +4 -0
  25. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js +7 -0
  26. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.map +5 -0
  27. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.nft.json +1 -0
  28. package/.next/standalone/.next/server/app/api/v1/changes/diff/route_client-reference-manifest.js +2 -0
  29. package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js +1 -1
  30. package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/v1/windows/cdp-start/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/v1/windows/cdp-status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/v1/windows/close/route.js +1 -1
  34. package/.next/standalone/.next/server/app/api/v1/windows/close/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/v1/windows/open/route.js +2 -2
  36. package/.next/standalone/.next/server/app/api/v1/windows/open/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/debug/page/build-manifest.json +3 -3
  38. package/.next/standalone/.next/server/app/debug/page.js +5 -5
  39. package/.next/standalone/.next/server/app/debug/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/debug/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  42. package/.next/standalone/.next/server/app/page.js +5 -5
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app-paths-manifest.json +2 -0
  46. package/.next/standalone/.next/server/chunks/[root-of-the-server]__26662154._.js +1 -1
  47. package/.next/standalone/.next/server/chunks/[root-of-the-server]__53c4f34d._.js +1 -1
  48. package/.next/standalone/.next/server/chunks/[root-of-the-server]__851f6b5a._.js +3 -0
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__94275f7f._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9a1969e6._.js +3 -0
  51. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c34d50c8._.js +1 -1
  52. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c696771d._.js +1 -1
  53. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d13bbe3c._.js +3 -0
  54. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d172e6aa._.js +3 -0
  55. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_active_route_actions_1bb9fc18.js +3 -0
  56. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_diff_route_actions_65d9ee16.js +3 -0
  57. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__d080cb3d._.js → [root-of-the-server]__012405ac._.js} +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a457c799._.js +3 -0
  59. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__f62d412e._.js → [root-of-the-server]__b9356576._.js} +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__52af585c._.js → [root-of-the-server]__ce78239f._.js} +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__ae6d24d9._.js → [root-of-the-server]__f47dc36d._.js} +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/{_524b2348._.js → _657ecbe9._.js} +3 -3
  63. package/.next/standalone/.next/server/chunks/ssr/{_fe4475aa._.js → _939145a4._.js} +3 -3
  64. package/.next/standalone/.next/server/chunks/ssr/app_layout_tsx_271801d7._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/app_not-found_tsx_ef35050a._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/components_chat-container_tsx_fcbc457f._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_ece394eb.js +3 -0
  68. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_39f173ba.js → node_modules_next_dist_esm_build_templates_app-page_7f45f9bf.js} +3 -3
  69. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_f183c70b._.js → node_modules_next_dist_f21d913a._.js} +2 -2
  70. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  71. package/.next/standalone/.next/server/pages/500.html +2 -2
  72. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  74. package/.next/standalone/app/api/v1/artifacts/active/[filename]/route.ts +73 -23
  75. package/.next/standalone/app/api/v1/artifacts/active/route.ts +103 -52
  76. package/.next/standalone/app/api/v1/changes/active/route.ts +27 -0
  77. package/.next/standalone/app/api/v1/changes/diff/route.ts +119 -0
  78. package/.next/standalone/app/globals.css +424 -0
  79. package/.next/standalone/app/layout.tsx +3 -3
  80. package/.next/standalone/app/not-found.tsx +14 -23
  81. package/.next/standalone/components/artifact-panel.tsx +57 -13
  82. package/.next/standalone/components/changes-panel.tsx +178 -0
  83. package/.next/standalone/components/chat-container.tsx +44 -3
  84. package/.next/standalone/components/chat-input.tsx +44 -0
  85. package/.next/standalone/components/header.tsx +1 -13
  86. package/.next/standalone/hooks/use-changes.ts +61 -0
  87. package/.next/standalone/hooks/use-chat.ts +21 -3
  88. package/.next/standalone/hooks/use-conversations.ts +19 -11
  89. package/.next/standalone/lib/scraper/agent-state.ts +89 -54
  90. package/.next/standalone/lib/scraper/chat-history.ts +215 -85
  91. package/.next/standalone/lib/scraper/ide-artifacts.ts +212 -0
  92. package/.next/standalone/lib/scraper/ide-changes.ts +172 -0
  93. package/.next/standalone/lib/types.ts +11 -0
  94. package/.next/standalone/package-lock.json +2 -2
  95. package/.next/standalone/package.json +2 -2
  96. package/.next/standalone/scripts/check-send-button.js +126 -0
  97. package/.next/standalone/scripts/find-send-btn.js +65 -0
  98. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  99. package/.next/static/chunks/09dc2aa5c698c324.css +1 -0
  100. package/.next/static/chunks/{f7c83373e6561461.js → b9a0fabf54a78ef2.js} +1 -1
  101. package/.next/static/chunks/e2ccf5908cad5a88.js +5 -0
  102. package/.next/static/chunks/f7cc8fe5822bbc01.js +1 -0
  103. package/.next/static/chunks/{turbopack-3f34081d758747ed.js → turbopack-7b5dc393c5d3964b.js} +1 -1
  104. package/package.json +2 -2
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__151eca3a._.js +0 -3
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ec32b318._.js +0 -3
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f77eb371._.js +0 -3
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_9170b7a0._.js +0 -3
  109. package/.next/standalone/app/global-error.tsx +0 -42
  110. package/.next/static/chunks/2317ab948a7d90a4.js +0 -5
  111. package/.next/static/chunks/2d277a81099566c3.js +0 -1
  112. package/.next/static/chunks/ad1121f40e497811.css +0 -1
  113. package/.next/static/chunks/d5d4abede4bc89fd.js +0 -1
@@ -0,0 +1,178 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import type { ChangeFile } from '@/lib/types';
5
+
6
+ interface ChangesPanelProps {
7
+ open: boolean;
8
+ onClose: () => void;
9
+ changes: ChangeFile[];
10
+ }
11
+
12
+ interface DiffLine {
13
+ type: 'add' | 'del' | 'context' | 'header' | 'meta';
14
+ content: string;
15
+ lineNum?: number;
16
+ }
17
+
18
+ function parseDiff(raw: string): DiffLine[] {
19
+ const lines: DiffLine[] = [];
20
+ let addLineNum = 0;
21
+ let delLineNum = 0;
22
+
23
+ for (const line of raw.split('\n')) {
24
+ if (line.startsWith('diff --git') || line.startsWith('index ') || line.startsWith('new file') || line.startsWith('deleted file')) {
25
+ lines.push({ type: 'meta', content: line });
26
+ } else if (line.startsWith('---') || line.startsWith('+++')) {
27
+ lines.push({ type: 'meta', content: line });
28
+ } else if (line.startsWith('@@')) {
29
+ // Parse hunk header for line numbers
30
+ const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
31
+ if (match) {
32
+ delLineNum = parseInt(match[1]);
33
+ addLineNum = parseInt(match[2]);
34
+ }
35
+ lines.push({ type: 'header', content: line });
36
+ } else if (line.startsWith('+')) {
37
+ lines.push({ type: 'add', content: line.substring(1), lineNum: addLineNum++ });
38
+ } else if (line.startsWith('-')) {
39
+ lines.push({ type: 'del', content: line.substring(1), lineNum: delLineNum++ });
40
+ } else if (line.startsWith(' ')) {
41
+ lines.push({ type: 'context', content: line.substring(1), lineNum: addLineNum });
42
+ addLineNum++;
43
+ delLineNum++;
44
+ }
45
+ }
46
+
47
+ return lines;
48
+ }
49
+
50
+ export default function ChangesPanel({ open, onClose, changes }: ChangesPanelProps) {
51
+ const [viewingFile, setViewingFile] = useState<string | null>(null);
52
+ const [diffContent, setDiffContent] = useState<DiffLine[]>([]);
53
+ const [loading, setLoading] = useState(false);
54
+ const [viewingFilename, setViewingFilename] = useState('');
55
+
56
+ const totalAdditions = changes.reduce((s, c) => s + c.additions, 0);
57
+ const totalDeletions = changes.reduce((s, c) => s + c.deletions, 0);
58
+
59
+ const fileIcon = (name: string) => {
60
+ if (name.endsWith('.tsx') || name.endsWith('.jsx')) return '⚛️';
61
+ if (name.endsWith('.ts') || name.endsWith('.js')) return '📘';
62
+ if (name.endsWith('.css')) return '🎨';
63
+ if (name.endsWith('.md')) return '📄';
64
+ if (name.endsWith('.json')) return '📋';
65
+ return '📝';
66
+ };
67
+
68
+ const openDiff = async (change: ChangeFile) => {
69
+ setLoading(true);
70
+ setViewingFile(change.filepath);
71
+ setViewingFilename(change.filename);
72
+ setDiffContent([]);
73
+ try {
74
+ const res = await fetch(`/api/v1/changes/diff?filepath=${encodeURIComponent(change.filepath)}`);
75
+ const data = await res.json();
76
+ if (data.diff) {
77
+ setDiffContent(parseDiff(data.diff));
78
+ } else {
79
+ setDiffContent([{ type: 'meta', content: data.message || 'No diff available' }]);
80
+ }
81
+ } catch (e: any) {
82
+ setDiffContent([{ type: 'meta', content: `Error: ${e.message}` }]);
83
+ } finally {
84
+ setLoading(false);
85
+ }
86
+ };
87
+
88
+ const closeDiffViewer = () => {
89
+ setViewingFile(null);
90
+ setDiffContent([]);
91
+ setViewingFilename('');
92
+ };
93
+
94
+ return (
95
+ <div className={`changes-panel ${open ? 'open' : ''}`}>
96
+ {/* Header */}
97
+ <div className="changes-panel-header">
98
+ <button className="icon-btn" onClick={onClose} title="Close panel">
99
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
100
+ <line x1="18" y1="6" x2="6" y2="18" />
101
+ <line x1="6" y1="6" x2="18" y2="18" />
102
+ </svg>
103
+ </button>
104
+ <h3>Changes Overview</h3>
105
+ </div>
106
+
107
+ {viewingFile ? (
108
+ /* Diff viewer */
109
+ <div className="diff-viewer">
110
+ <div className="diff-viewer-header">
111
+ <button className="diff-back-btn" onClick={closeDiffViewer}>← Back</button>
112
+ <span className="diff-viewer-title">{viewingFilename}</span>
113
+ <span className="diff-viewer-path">{viewingFile}</span>
114
+ </div>
115
+ <div className="diff-viewer-body">
116
+ {loading ? (
117
+ <div style={{ color: 'var(--text-muted)', padding: '16px', fontStyle: 'italic' }}>Loading diff...</div>
118
+ ) : diffContent.length === 0 ? (
119
+ <div style={{ color: 'var(--text-muted)', padding: '16px' }}>No changes found</div>
120
+ ) : (
121
+ <div className="diff-lines">
122
+ {diffContent.map((line, i) => (
123
+ <div key={i} className={`diff-line diff-line-${line.type}`}>
124
+ <span className="diff-line-num">
125
+ {line.lineNum !== undefined ? line.lineNum : ''}
126
+ </span>
127
+ <span className="diff-line-indicator">
128
+ {line.type === 'add' ? '+' : line.type === 'del' ? '-' : line.type === 'header' ? '@@' : ''}
129
+ </span>
130
+ <span className="diff-line-content">{line.content}</span>
131
+ </div>
132
+ ))}
133
+ </div>
134
+ )}
135
+ </div>
136
+ </div>
137
+ ) : (
138
+ /* File list */
139
+ <>
140
+ {/* Summary bar */}
141
+ <div className="changes-summary">
142
+ <span className="changes-count">{changes.length} file{changes.length !== 1 ? 's' : ''} changed</span>
143
+ <div className="changes-stats">
144
+ {totalAdditions > 0 && <span className="changes-additions">+{totalAdditions}</span>}
145
+ {totalDeletions > 0 && <span className="changes-deletions">-{totalDeletions}</span>}
146
+ </div>
147
+ </div>
148
+
149
+ <div className="changes-file-list">
150
+ {changes.map(c => (
151
+ <button
152
+ key={c.filepath}
153
+ className="changes-file-item"
154
+ onClick={() => openDiff(c)}
155
+ title={`View diff: ${c.filepath}`}
156
+ >
157
+ <span className="changes-file-icon">{fileIcon(c.filename)}</span>
158
+ <div className="changes-file-info">
159
+ <div className="changes-file-name">{c.filename}</div>
160
+ <div className="changes-file-path">{c.filepath}</div>
161
+ </div>
162
+ <div className="changes-file-diff">
163
+ {c.additions > 0 && <span className="changes-additions">+{c.additions}</span>}
164
+ {c.deletions > 0 && <span className="changes-deletions">-{c.deletions}</span>}
165
+ </div>
166
+ </button>
167
+ ))}
168
+ {changes.length === 0 && (
169
+ <div style={{ textAlign: 'center', padding: '24px', color: 'var(--text-muted)', fontSize: '13px' }}>
170
+ No file changes in this conversation
171
+ </div>
172
+ )}
173
+ </div>
174
+ </>
175
+ )}
176
+ </div>
177
+ );
178
+ }
@@ -6,6 +6,7 @@ import WelcomeScreen from '@/components/welcome-screen';
6
6
  import MessageList from '@/components/message-list';
7
7
  import ChatInput from '@/components/chat-input';
8
8
  import ArtifactPanel from '@/components/artifact-panel';
9
+ import ChangesPanel from '@/components/changes-panel';
9
10
  import { useEffect } from 'react';
10
11
 
11
12
  export default function ChatContainer() {
@@ -35,14 +36,43 @@ export default function ChatContainer() {
35
36
  onSelectWindow={chat.selectWindow}
36
37
  onSelectConversation={chat.selectConversation}
37
38
  onNewChat={chat.startNewChat}
38
- onToggleArtifacts={chat.toggleArtifactPanel}
39
39
  onStartCdp={chat.startCdpServer}
40
40
  onOpenWindow={chat.openNewWindow}
41
41
  onCloseWindow={chat.closeWindowByIndex}
42
42
  />
43
43
 
44
44
  <main className="messages-area" role="log" aria-live="polite">
45
- {chat.showWelcome ? (
45
+ {chat.isLoadingHistory ? (
46
+ <div className="history-loading">
47
+ <div className="history-loading-header">
48
+ <div className="skeleton-shimmer skeleton-avatar" />
49
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '45%' }} />
50
+ </div>
51
+ <div className="history-loading-block">
52
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '80%' }} />
53
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '65%' }} />
54
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '72%' }} />
55
+ </div>
56
+ <div className="history-loading-header right">
57
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '35%' }} />
58
+ </div>
59
+ <div className="history-loading-block">
60
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '90%' }} />
61
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '55%' }} />
62
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '68%' }} />
63
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '40%' }} />
64
+ </div>
65
+ <div className="history-loading-header">
66
+ <div className="skeleton-shimmer skeleton-avatar" />
67
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '50%' }} />
68
+ </div>
69
+ <div className="history-loading-block">
70
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '75%' }} />
71
+ <div className="skeleton-shimmer skeleton-line" style={{ width: '60%' }} />
72
+ </div>
73
+ <p className="history-loading-text">Loading conversation history…</p>
74
+ </div>
75
+ ) : chat.showWelcome ? (
46
76
  <WelcomeScreen onQuickPrompt={chat.sendMessage} />
47
77
  ) : (
48
78
  <MessageList
@@ -52,7 +82,6 @@ export default function ChatContainer() {
52
82
  isStreaming={chat.isStreaming}
53
83
  onRetry={async () => {
54
84
  try {
55
- // To retry, we ping health and maybe trigger a status update
56
85
  await fetch('/api/v1/health');
57
86
  window.location.reload();
58
87
  } catch { /* ignore */ }
@@ -72,6 +101,12 @@ export default function ChatContainer() {
72
101
  isLoadingAgents={chat.isLoadingAgents}
73
102
  onFetchAgents={chat.fetchAgentList}
74
103
  onSwitchAgent={chat.switchAgent}
104
+ onToggleArtifacts={chat.toggleArtifactPanel}
105
+ artifactCount={chat.artifactFiles.length}
106
+ artifactPanelOpen={chat.artifactPanelOpen}
107
+ onToggleChanges={chat.toggleChangesPanel}
108
+ changesCount={chat.changeFiles.length}
109
+ changesPanelOpen={chat.changesPanelOpen}
75
110
  />
76
111
 
77
112
  <ArtifactPanel
@@ -80,6 +115,12 @@ export default function ChatContainer() {
80
115
  activeConversation={chat.activeConversation}
81
116
  files={chat.artifactFiles}
82
117
  />
118
+
119
+ <ChangesPanel
120
+ open={chat.changesPanelOpen}
121
+ onClose={chat.toggleChangesPanel}
122
+ changes={chat.changeFiles}
123
+ />
83
124
  </div>
84
125
  );
85
126
  }
@@ -18,11 +18,19 @@ interface ChatInputProps {
18
18
  isLoadingAgents: boolean;
19
19
  onFetchAgents: () => void;
20
20
  onSwitchAgent: (agentName: string) => void;
21
+ onToggleArtifacts: () => void;
22
+ artifactCount: number;
23
+ artifactPanelOpen: boolean;
24
+ onToggleChanges: () => void;
25
+ changesCount: number;
26
+ changesPanelOpen: boolean;
21
27
  }
22
28
 
23
29
  export default function ChatInput({
24
30
  onSend, isStreaming, currentMode, onToggleMode,
25
31
  currentAgent, agents, isLoadingAgents, onFetchAgents, onSwitchAgent,
32
+ onToggleArtifacts, artifactCount, artifactPanelOpen,
33
+ onToggleChanges, changesCount, changesPanelOpen,
26
34
  }: ChatInputProps) {
27
35
  const [value, setValue] = useState('');
28
36
  const [agentDropdownOpen, setAgentDropdownOpen] = useState(false);
@@ -166,6 +174,42 @@ export default function ChatInput({
166
174
  <span className="mode-label">{currentMode === 'planning' ? 'Plan' : 'Fast'}</span>
167
175
  </button>
168
176
 
177
+ {/* Artifacts Toggle */}
178
+ <button
179
+ className={`artifact-toggle-btn ${artifactPanelOpen ? 'active' : ''}`}
180
+ onClick={onToggleArtifacts}
181
+ title={`Artifacts${artifactCount > 0 ? ` (${artifactCount})` : ''}`}
182
+ aria-label="Toggle artifacts panel"
183
+ type="button"
184
+ >
185
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
186
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
187
+ <polyline points="14 2 14 8 20 8" />
188
+ <line x1="16" y1="13" x2="8" y2="13" />
189
+ <line x1="16" y1="17" x2="8" y2="17" />
190
+ </svg>
191
+ {artifactCount > 0 && (
192
+ <span className="artifact-badge">{artifactCount}</span>
193
+ )}
194
+ </button>
195
+
196
+ {/* Changes Overview Toggle */}
197
+ <button
198
+ className={`changes-toggle-btn ${changesPanelOpen ? 'active' : ''}`}
199
+ onClick={onToggleChanges}
200
+ title={`Changes${changesCount > 0 ? ` (${changesCount})` : ''}`}
201
+ aria-label="Toggle changes panel"
202
+ type="button"
203
+ >
204
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
205
+ <path d="M12 20h9" />
206
+ <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
207
+ </svg>
208
+ {changesCount > 0 && (
209
+ <span className="changes-badge">{changesCount}</span>
210
+ )}
211
+ </button>
212
+
169
213
  <span className="toolbar-agent-name" id="model-info">{currentAgent || ''}</span>
170
214
  </div>
171
215
 
@@ -15,7 +15,6 @@ interface HeaderProps {
15
15
  onSelectWindow: (idx: number) => void;
16
16
  onSelectConversation: (id: string) => void;
17
17
  onNewChat: () => void;
18
- onToggleArtifacts: () => void;
19
18
  onStartCdp: (projectDir?: string, killExisting?: boolean) => Promise<any>;
20
19
  onOpenWindow: (projectDir: string) => Promise<any>;
21
20
  onCloseWindow: (index: number) => Promise<any>;
@@ -23,7 +22,7 @@ interface HeaderProps {
23
22
 
24
23
  export default function Header({
25
24
  statusState, statusText, windows, conversations, activeConversation,
26
- cdpStatus, recentProjects, onSelectWindow, onSelectConversation, onNewChat, onToggleArtifacts,
25
+ cdpStatus, recentProjects, onSelectWindow, onSelectConversation, onNewChat,
27
26
  onStartCdp, onOpenWindow, onCloseWindow,
28
27
  }: HeaderProps) {
29
28
  const [windowOpen, setWindowOpen] = useState(false);
@@ -282,17 +281,6 @@ export default function Header({
282
281
  </div>
283
282
  </div>
284
283
 
285
- {/* Artifacts Button */}
286
- <button className="icon-btn" onClick={onToggleArtifacts} title="Artifacts" aria-label="Artifacts">
287
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
288
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
289
- <polyline points="14 2 14 8 20 8" />
290
- <line x1="16" y1="13" x2="8" y2="13" />
291
- <line x1="16" y1="17" x2="8" y2="17" />
292
- <polyline points="10 9 9 9 8 9" />
293
- </svg>
294
- </button>
295
-
296
284
  {/* New Chat Button */}
297
285
  <button className="icon-btn" onClick={onNewChat} title="New Chat" aria-label="New Chat">
298
286
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
@@ -0,0 +1,61 @@
1
+ import { useState, useCallback, useEffect, useRef } from 'react';
2
+ import type { ChangeFile } from '@/lib/types';
3
+
4
+ const API_BASE = '/api/v1';
5
+ const POLL_INTERVAL_MS = 5000;
6
+
7
+ /**
8
+ * Hook for fetching and managing the "Changes Overview" data
9
+ * scraped from the IDE's conversation panel.
10
+ */
11
+ export function useChanges() {
12
+ const [changeFiles, setChangeFiles] = useState<ChangeFile[]>([]);
13
+ const [changesPanelOpen, setChangesPanelOpen] = useState(false);
14
+
15
+ const lastHashRef = useRef('');
16
+ const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
17
+
18
+ const loadChanges = useCallback(async () => {
19
+ try {
20
+ const res = await fetch(`${API_BASE}/changes/active`);
21
+ const data = await res.json();
22
+ const changes: ChangeFile[] = data.changes || [];
23
+
24
+ const newHash = JSON.stringify(changes.map(c => `${c.filename}+${c.additions}-${c.deletions}`));
25
+ if (newHash !== lastHashRef.current) {
26
+ lastHashRef.current = newHash;
27
+ setChangeFiles(changes);
28
+ }
29
+ } catch { /* ignore */ }
30
+ }, []);
31
+
32
+ const toggleChangesPanel = useCallback(() => {
33
+ setChangesPanelOpen(prev => !prev);
34
+ }, []);
35
+
36
+ // Poll when panel is open
37
+ useEffect(() => {
38
+ if (changesPanelOpen) {
39
+ loadChanges();
40
+ pollingRef.current = setInterval(loadChanges, POLL_INTERVAL_MS);
41
+ } else {
42
+ if (pollingRef.current) {
43
+ clearInterval(pollingRef.current);
44
+ pollingRef.current = null;
45
+ }
46
+ }
47
+ return () => {
48
+ if (pollingRef.current) {
49
+ clearInterval(pollingRef.current);
50
+ pollingRef.current = null;
51
+ }
52
+ };
53
+ }, [changesPanelOpen, loadChanges]);
54
+
55
+ return {
56
+ changeFiles,
57
+ changesPanelOpen,
58
+ toggleChangesPanel,
59
+ loadChanges,
60
+ };
61
+ }
@@ -3,6 +3,7 @@
3
3
  import { useState, useRef, useCallback, useEffect } from 'react';
4
4
  import { useConversations } from './use-conversations';
5
5
  import { useArtifacts } from './use-artifacts';
6
+ import { useChanges } from './use-changes';
6
7
  import type { ChatMessage, SSEStep } from '@/lib/types';
7
8
 
8
9
  const API_BASE = '/api/v1';
@@ -14,6 +15,7 @@ export function useChat() {
14
15
  const [statusText, setStatusText] = useState('Agent');
15
16
  const [statusState, setStatusState] = useState('connected');
16
17
  const [showWelcome, setShowWelcome] = useState(true);
18
+ const [isLoadingHistory, setIsLoadingHistory] = useState(false);
17
19
  const [currentSteps, setCurrentSteps] = useState<SSEStep[]>([]);
18
20
  const [currentResponse, setCurrentResponse] = useState('');
19
21
  const [currentMode, setCurrentMode] = useState<'planning' | 'fast'>('planning');
@@ -38,14 +40,20 @@ export function useChat() {
38
40
  }, []);
39
41
 
40
42
  const fetchHistory = useCallback(async () => {
43
+ setIsLoadingHistory(true);
44
+ setMessages([]); // Clear existing messages immediately
41
45
  try {
42
46
  const res = await fetch(`${API_BASE}/chat/history`);
43
47
  const data = await res.json();
44
48
  if (data.turns && data.turns.length > 0) {
45
49
  setShowWelcome(false);
46
50
  setMessages(data.turns.map((t: any) => ({ role: t.role, content: t.content })));
51
+ } else {
52
+ setShowWelcome(true);
47
53
  }
48
- } catch { /* ignore */ }
54
+ } catch { /* ignore */ } finally {
55
+ setIsLoadingHistory(false);
56
+ }
49
57
  }, []);
50
58
 
51
59
  const {
@@ -56,11 +64,19 @@ export function useChat() {
56
64
  loadArtifacts
57
65
  } = useArtifacts();
58
66
 
67
+ const {
68
+ changeFiles,
69
+ changesPanelOpen,
70
+ toggleChangesPanel,
71
+ loadChanges,
72
+ } = useChanges();
73
+
59
74
  // Auto-open artifact panel and refresh files when a conversation switch completes
60
75
  const handleConversationSwitched = useCallback(() => {
61
76
  openArtifactPanel();
62
77
  loadArtifacts();
63
- }, [openArtifactPanel, loadArtifacts]);
78
+ loadChanges();
79
+ }, [openArtifactPanel, loadArtifacts, loadChanges]);
64
80
 
65
81
  const {
66
82
  windows,
@@ -328,12 +344,14 @@ export function useChat() {
328
344
 
329
345
  return {
330
346
  messages, isStreaming, isConnected, statusText, statusState,
331
- showWelcome, currentSteps, currentResponse, windows,
347
+ showWelcome, isLoadingHistory, currentSteps, currentResponse, windows,
332
348
  conversations, activeConversation, artifactFiles, artifactPanelOpen,
349
+ changeFiles, changesPanelOpen,
333
350
  currentMode, currentAgent, agents, isLoadingAgents,
334
351
  cdpStatus, recentProjects,
335
352
  sendMessage, startNewChat, approve, reject,
336
353
  selectWindow, selectConversation, toggleArtifactPanel, openArtifactPanel,
354
+ toggleChangesPanel,
337
355
  toggleMode, fetchAgentList, switchAgent,
338
356
  startCdpServer, openNewWindow, closeWindowByIndex,
339
357
  messagesEndRef, setShowWelcome,
@@ -105,17 +105,6 @@ export function useConversations(
105
105
  }
106
106
  }, [loadWindows, checkCdpStatus]);
107
107
 
108
- const selectWindow = useCallback(async (idx: number) => {
109
- try {
110
- await fetch(`${API_BASE}/windows/select`, {
111
- method: 'POST',
112
- headers: { 'Content-Type': 'application/json' },
113
- body: JSON.stringify({ index: idx }),
114
- });
115
- await loadWindows();
116
- } catch { /* ignore */ }
117
- }, [loadWindows]);
118
-
119
108
  const loadConversations = useCallback(async () => {
120
109
  try {
121
110
  const res = await fetch(`${API_BASE}/conversations`);
@@ -129,6 +118,25 @@ export function useConversations(
129
118
  } catch { /* ignore */ }
130
119
  }, []);
131
120
 
121
+ const selectWindow = useCallback(async (idx: number) => {
122
+ try {
123
+ // Clear current chat and show welcome while we load the new window's history
124
+ setShowWelcome(true);
125
+ await fetch(`${API_BASE}/windows/select`, {
126
+ method: 'POST',
127
+ headers: { 'Content-Type': 'application/json' },
128
+ body: JSON.stringify({ index: idx }),
129
+ });
130
+ await loadWindows();
131
+ // Fetch the new window's chat history
132
+ await fetchHistory();
133
+ // Refresh conversations list for the new window
134
+ await loadConversations();
135
+ // Notify parent (for artifact sync etc.)
136
+ onConversationSwitched?.();
137
+ } catch { /* ignore */ }
138
+ }, [loadWindows, fetchHistory, setShowWelcome, loadConversations, onConversationSwitched]);
139
+
132
140
  const selectConversation = useCallback(async (title: string) => {
133
141
  try {
134
142
  const res = await fetch(`${API_BASE}/conversations/select`, {