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.
- package/.next/standalone/.next/app-path-routes-manifest.json +2 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +12 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page.js +5 -6
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js +5 -1
- package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/cdp-start/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/cdp-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/close/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/close/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/open/route.js +2 -2
- package/.next/standalone/.next/server/app/api/v1/windows/open/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/debug/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/debug/page.js +5 -5
- package/.next/standalone/.next/server/app/debug/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/debug/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page.js +5 -5
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__26662154._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__53c4f34d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__851f6b5a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__94275f7f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9a1969e6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c34d50c8._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c696771d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d13bbe3c._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d172e6aa._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_active_route_actions_1bb9fc18.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_diff_route_actions_65d9ee16.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__d080cb3d._.js → [root-of-the-server]__012405ac._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a457c799._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__f62d412e._.js → [root-of-the-server]__b9356576._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__52af585c._.js → [root-of-the-server]__ce78239f._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__ae6d24d9._.js → [root-of-the-server]__f47dc36d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_524b2348._.js → _657ecbe9._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{_fe4475aa._.js → _939145a4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_layout_tsx_271801d7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_not-found_tsx_ef35050a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/components_chat-container_tsx_fcbc457f._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_ece394eb.js +3 -0
- 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
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_f183c70b._.js → node_modules_next_dist_f21d913a._.js} +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/app/api/v1/artifacts/active/[filename]/route.ts +73 -23
- package/.next/standalone/app/api/v1/artifacts/active/route.ts +103 -52
- package/.next/standalone/app/api/v1/changes/active/route.ts +27 -0
- package/.next/standalone/app/api/v1/changes/diff/route.ts +119 -0
- package/.next/standalone/app/globals.css +424 -0
- package/.next/standalone/app/layout.tsx +3 -3
- package/.next/standalone/app/not-found.tsx +14 -23
- package/.next/standalone/components/artifact-panel.tsx +57 -13
- package/.next/standalone/components/changes-panel.tsx +178 -0
- package/.next/standalone/components/chat-container.tsx +44 -3
- package/.next/standalone/components/chat-input.tsx +44 -0
- package/.next/standalone/components/header.tsx +1 -13
- package/.next/standalone/hooks/use-changes.ts +61 -0
- package/.next/standalone/hooks/use-chat.ts +21 -3
- package/.next/standalone/hooks/use-conversations.ts +19 -11
- package/.next/standalone/lib/scraper/agent-state.ts +89 -54
- package/.next/standalone/lib/scraper/chat-history.ts +215 -85
- package/.next/standalone/lib/scraper/ide-artifacts.ts +212 -0
- package/.next/standalone/lib/scraper/ide-changes.ts +172 -0
- package/.next/standalone/lib/types.ts +11 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/scripts/check-send-button.js +126 -0
- package/.next/standalone/scripts/find-send-btn.js +65 -0
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/09dc2aa5c698c324.css +1 -0
- package/.next/static/chunks/{f7c83373e6561461.js → b9a0fabf54a78ef2.js} +1 -1
- package/.next/static/chunks/e2ccf5908cad5a88.js +5 -0
- package/.next/static/chunks/f7cc8fe5822bbc01.js +1 -0
- package/.next/static/chunks/{turbopack-3f34081d758747ed.js → turbopack-7b5dc393c5d3964b.js} +1 -1
- package/package.json +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__151eca3a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ec32b318._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f77eb371._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_9170b7a0._.js +0 -3
- package/.next/standalone/app/global-error.tsx +0 -42
- package/.next/static/chunks/2317ab948a7d90a4.js +0 -5
- package/.next/static/chunks/2d277a81099566c3.js +0 -1
- package/.next/static/chunks/ad1121f40e497811.css +0 -1
- 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.
|
|
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,
|
|
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
|
-
|
|
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`, {
|