forked from Artemis43/AG-Chat-Recovery
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
244 lines (212 loc) · 10.4 KB
/
Copy pathapp.js
File metadata and controls
244 lines (212 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
document.addEventListener("DOMContentLoaded", () => {
const listContainer = document.getElementById("chatList");
const mainBody = document.getElementById("contentBody");
const topbar = document.getElementById("topbar");
let currentChats = [];
// Fetch the list of broken chats
fetch('/api/broken_chats')
.then(response => response.json())
.then(data => {
if (data.error) {
listContainer.innerHTML = `<div class="loading" style="color:var(--danger)">Error: ${data.error}</div>`;
return;
}
currentChats = data;
renderSidebar();
})
.catch(err => {
listContainer.innerHTML = `<div class="loading" style="color:var(--danger)">Failed to connect to Python backend.<br><br>Did you run 'python RestoreChats.py'?</div>`;
});
// Shutdown Server Logic
const btnShutdown = document.getElementById("btnShutdown");
if (btnShutdown) {
btnShutdown.addEventListener("click", () => {
if (confirm("Are you sure you want to stop the background server?\nYou will need to restart the application to recover more chats.")) {
btnShutdown.disabled = true;
btnShutdown.innerText = "Stopping...";
fetch('/api/shutdown', { method: 'POST' })
.then(res => res.json())
.then(() => {
document.body.innerHTML = `
<div style="height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: var(--bg-color); color: var(--text-primary); font-family: 'Inter', sans-serif;">
<h2 style="margin-bottom: 10px;">Server Offline</h2>
<p style="color: var(--text-secondary);">The Antigravity Recovery background service has been completely stopped.</p>
<p style="color: var(--text-secondary); margin-top: 5px;">You may now safely close this browser window.</p>
</div>
`;
// Attempt to close the window via JS (often blocked by browsers, but worth a try)
setTimeout(() => window.close(), 3000);
})
.catch(err => {
alert("Shutdown command sent, but connection was lost (as expected).");
});
}
});
}
function renderSidebar() {
if (currentChats.length === 0) {
listContainer.innerHTML = `<div class="loading">No broken conversations found.</div>`;
return;
}
listContainer.innerHTML = '';
currentChats.forEach(chat => {
const div = document.createElement("div");
div.className = "chat-item";
// If it doesn't have a pb file to restore from, fade it out
if (!chat.has_pb) div.style.opacity = "0.4";
// Format timestamp safely
let timeStr = "";
if (chat.timestamp) {
const date = new Date(chat.timestamp * 1000);
timeStr = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) +
" - " +
date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
}
div.innerHTML = `
<div class="chat-title">
${escapeHTML(chat.title)}
<div class="chat-time">${escapeHTML(timeStr)}</div>
</div>
<div class="chat-id">${chat.id.substring(0, 13)}...</div>
`;
div.addEventListener('click', () => {
document.querySelectorAll('.chat-item').forEach(el => el.classList.remove('active'));
div.classList.add('active');
loadChatDetails(chat);
});
listContainer.appendChild(div);
});
}
function loadChatDetails(chat) {
// Update topbar actions
const isRecoverable = chat.has_pb;
const btnDisplay = 'display:inline-block;';
const disabledAttr = isRecoverable ? '' : 'disabled="true"';
const btnText = isRecoverable ? 'Recover Chat' : 'Unrecoverable (Missing .pb)';
const btnStyles = isRecoverable ? '' : 'background-color: var(--border); color: var(--text-secondary); cursor: not-allowed;';
topbar.innerHTML = `
<h2>${escapeHTML(chat.title)}</h2>
<div style="display: flex; gap: 10px;">
<button class="btn-primary" id="btnRecover" style="${btnDisplay} ${btnStyles}" ${disabledAttr}>${btnText}</button>
<button class="btn-danger-outline" id="btnDelete" style="${btnDisplay}">Delete Context</button>
</div>
`;
mainBody.innerHTML = `<div class="loading">Loading chat contents...</div>`;
fetch(`/api/chat_details?id=${chat.id}`)
.then(res => res.json())
.then(data => {
if (data.error) throw new Error(data.error);
// Render markdown
const parsedHTML = marked.parse(data.content);
mainBody.innerHTML = `<div class="markdown-body fade-in">${parsedHTML}</div>`;
document.getElementById("btnRecover").addEventListener("click", () => {
showRecoveryModal(chat);
});
document.getElementById("btnDelete").addEventListener("click", () => {
if (confirm(`Are you absolutely sure you want to permanently delete the cached context folder for:\n\n"${chat.title}"\n\nThis cannot be undone.`)) {
executeDelete(chat.id);
}
});
})
.catch(err => {
mainBody.innerHTML = `<div class="loading" style="color:var(--danger)">Failed to load chat details: ${err.message}</div>`;
});
}
function executeDelete(conversationId) {
const btn = document.getElementById("btnDelete");
btn.disabled = true;
btn.innerText = "Deleting...";
fetch('/api/delete_unrecoverable', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ conversation_id: conversationId })
})
.then(response => response.json())
.then(data => {
if (!data.success) { throw new Error(data.error); }
// Remove from local array and re-render
currentChats = currentChats.filter(c => c.id !== conversationId);
renderSidebar();
topbar.innerHTML = `<h2>Select a conversation to preview...</h2>`;
mainBody.innerHTML = `
<div class="welcome-screen">
<img src="icon.svg" class="huge-icon" alt="Welcome">
<h3>Chat Deleted</h3>
<p>The broken context folder has been permanently removed.</p>
</div>
`;
})
.catch(err => {
alert(`Failed to delete: ${err.message}`);
btn.disabled = false;
btn.innerText = "Delete Context";
});
}
function showRecoveryModal(chat) {
// Create modal overlay
const overlay = document.createElement("div");
overlay.className = "modal-overlay";
overlay.style.display = "flex";
overlay.innerHTML = `
<div class="modal fade-in">
<h3>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
Confirm Recovery
</h3>
<p>To safely recover <strong>${escapeHTML(chat.title)}</strong>, you must first open Antigravity IDE and start a <strong>Brand New Chat.</strong></p>
<p>This action will overwrite that new chat's database file with this original conversation.</p>
<button id="modalExecuteBtn" class="btn-danger">Yes, I have an empty chat open!</button>
<button id="modalCancelBtn" class="btn-cancel">Cancel</button>
<div id="statusMsg" class="status-message"></div>
</div>
`;
document.body.appendChild(overlay);
document.getElementById("modalCancelBtn").addEventListener("click", () => {
document.body.removeChild(overlay);
});
const btn = document.getElementById("modalExecuteBtn");
btn.addEventListener("click", () => executeHijack(chat.id, btn, overlay));
}
function executeHijack(conversationId, btnElement, overlay) {
btnElement.disabled = true;
btnElement.innerHTML = `Recovering...`;
const statusBox = document.getElementById("statusMsg");
statusBox.className = "status-message";
statusBox.innerHTML = "";
fetch('/api/hijack', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ conversation_id: conversationId })
})
.then(response => response.json())
.then(data => {
if (!data.success) { throw new Error(data.error); }
statusBox.classList.add("success");
statusBox.innerHTML = `<strong>SUCCESS!</strong><br>Please restart your IDE.`;
btnElement.style.display = 'none';
document.getElementById("modalCancelBtn").innerHTML = "Close";
})
.catch(err => {
statusBox.classList.add("error");
statusBox.innerHTML = `<strong>FAILED</strong><br>${err.message}`;
btnElement.disabled = false;
btnElement.innerHTML = `Try Again`;
});
}
// Basic HTML escaping to prevent XSS
function escapeHTML(str) {
return str.replace(/[&<>'"]/g,
tag => ({
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
}[tag])
);
}
});