A browser extension that lets you summarize any webpage and ask questions using AI.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix follow-up suggestions: stale requests, parsing, chat error UI

+88 -18
+88 -18
popup/popup.js
··· 92 92 93 93 let generatedSuggestions = []; // AI-generated follow-up questions based on summary 94 94 95 + /** Bumped when starting a new summary or sending a chat message; stale suggestion requests skip UI/cache updates. */ 96 + let activeSuggestionsTaskId = 0; 97 + function bumpSuggestionsTask() { 98 + activeSuggestionsTaskId++; 99 + } 100 + 101 + /** 102 + * Split model output into follow-up question lines (newline-separated, or several "…?" chunks on one line). 103 + */ 104 + function parseSuggestionLines(raw, maxCount, minLen) { 105 + let text = String(raw || "").trim(); 106 + if (!text) return []; 107 + 108 + text = text.replace(/^```[\w]*\n?/i, "").replace(/\n?```\s*$/i, ""); 109 + 110 + let lines = text 111 + .split("\n") 112 + .map((line) => line.trim()) 113 + .filter(Boolean); 114 + 115 + if (lines.length <= 1 && text.includes("?")) { 116 + lines = text 117 + .split(/(?<=\?)\s+/) 118 + .map((s) => s.trim()) 119 + .filter(Boolean); 120 + } 121 + 122 + return lines 123 + .map((line) => 124 + line 125 + .replace(/^\s*[\[(]?\d+[)\].:-]\s*/i, "") 126 + .replace(/^[->•*]+\s*/, "") 127 + .trim(), 128 + ) 129 + .map((line) => line.replace(/^\d+[.\-)]\s*/, "").trim()) 130 + .filter((line) => line.replace(/\?+$/, "").trim().length >= minLen) 131 + .slice(0, maxCount); 132 + } 133 + 95 134 const SUGGESTIONS_PROMPT = CONFIG.PROMPTS.SUGGESTIONS; 96 135 97 136 const CHAT_SUGGESTIONS_PROMPT = CONFIG.PROMPTS.CHAT_SUGGESTIONS; ··· 797 836 const message = chatInput.value.trim(); 798 837 if (!message || isChatLoading || !currentPageContent) return; 799 838 839 + bumpSuggestionsTask(); 800 840 const gen = ++chatReplyGeneration; 801 841 802 842 // Add user message to history ··· 963 1003 }); 964 1004 965 1005 async function generateQuickSummary() { 1006 + bumpSuggestionsTask(); 966 1007 setLoading(true); 967 1008 currentSummaryMode = "quick"; 968 1009 initialState.classList.add("hidden"); ··· 1087 1128 // Don't generate if chat already started 1088 1129 if (chatHistory.length > 0) return; 1089 1130 1131 + const taskId = activeSuggestionsTaskId; 1132 + 1090 1133 // Show loading state for suggestions 1091 1134 renderSuggestionsLoading(); 1092 1135 1093 1136 // Add timeout failsafe - if suggestions take too long, show fallback 1094 1137 const timeoutId = setTimeout(() => { 1138 + if (taskId !== activeSuggestionsTaskId) return; 1095 1139 if (generatedSuggestions.length === 0) { 1096 1140 console.log("Suggestions generation timed out, showing fallback"); 1097 1141 generatedSuggestions = []; ··· 1102 1146 try { 1103 1147 const settings = await getApiSettings(); 1104 1148 1149 + if (taskId !== activeSuggestionsTaskId) { 1150 + clearTimeout(timeoutId); 1151 + return; 1152 + } 1153 + 1105 1154 const apiMessages = [ 1106 1155 { role: "system", content: SYSTEM_PROMPT_SUMMARIZER }, 1107 1156 { ··· 1131 1180 1132 1181 clearTimeout(timeoutId); 1133 1182 1183 + if (taskId !== activeSuggestionsTaskId) { 1184 + return; 1185 + } 1186 + 1134 1187 if (!response || !response.success) { 1135 1188 throw new Error(response?.error || "Failed to generate suggestions"); 1136 1189 } ··· 1143 1196 1144 1197 const content = rawContent.slice(0, CONFIG.API.SUGGESTIONS_MAX_PARSE_CHARS); 1145 1198 1146 - // Parse suggestions from the response (one per line) 1147 - // More lenient parsing - accept lines with questions or just meaningful content 1148 - generatedSuggestions = content 1149 - .split("\n") 1150 - .map((line) => line.trim()) 1151 - .map((line) => line.replace(/^\d+[.\-)]\s*/, "")) // Remove numbering like "1. " or "1) " or "- " 1152 - .filter((line) => line.length > 10) // Must be substantial 1153 - .slice(0, 3); 1199 + generatedSuggestions = parseSuggestionLines(content, 3, 8); 1154 1200 1155 1201 console.log("Parsed suggestions:", generatedSuggestions); 1156 1202 ··· 1160 1206 generatedSuggestions = []; 1161 1207 } 1162 1208 1209 + if (taskId !== activeSuggestionsTaskId) { 1210 + return; 1211 + } 1212 + 1163 1213 // Render the generated suggestions (or fallback) 1164 1214 renderSuggestions(); 1165 1215 ··· 1175 1225 } 1176 1226 } catch (error) { 1177 1227 clearTimeout(timeoutId); 1228 + if (taskId !== activeSuggestionsTaskId) { 1229 + return; 1230 + } 1178 1231 console.error("Failed to generate suggestions:", error); 1179 1232 // Fall back to static suggestions if generation fails 1180 1233 generatedSuggestions = []; ··· 1212 1265 } 1213 1266 1214 1267 async function generateSuggestionsForChat(lastResponse) { 1268 + const taskId = activeSuggestionsTaskId; 1269 + 1215 1270 // Show loading state with skeletons (no default for chat follow-ups) 1216 1271 renderSuggestionsLoading(false); 1217 1272 1218 1273 // Timeout failsafe 1219 1274 const timeoutId = setTimeout(() => { 1275 + if (taskId !== activeSuggestionsTaskId) return; 1220 1276 if (generatedSuggestions.length === 0) { 1221 1277 generatedSuggestions = []; 1222 1278 renderSuggestions(false); ··· 1226 1282 try { 1227 1283 const settings = await getApiSettings(); 1228 1284 1285 + if (taskId !== activeSuggestionsTaskId) { 1286 + clearTimeout(timeoutId); 1287 + return; 1288 + } 1289 + 1229 1290 const apiMessages = [ 1230 1291 { role: "system", content: SYSTEM_PROMPT_CHAT }, 1231 1292 { ··· 1251 1312 1252 1313 clearTimeout(timeoutId); 1253 1314 1315 + if (taskId !== activeSuggestionsTaskId) { 1316 + return; 1317 + } 1318 + 1254 1319 if (!response || !response.success) { 1255 1320 throw new Error(response?.error || "Failed to generate suggestions"); 1256 1321 } ··· 1263 1328 1264 1329 const content = rawContent.slice(0, CONFIG.API.SUGGESTIONS_MAX_PARSE_CHARS); 1265 1330 1266 - // Parse suggestions from the response 1267 - generatedSuggestions = content 1268 - .split("\n") 1269 - .map((line) => line.trim()) 1270 - .map((line) => line.replace(/^[->•]\s*/, "")) // Remove bullet points 1271 - .map((line) => line.replace(/^\d+[.\-)]\s*/, "")) 1272 - .filter((line) => line.length > 5) 1273 - .slice(0, 2); 1331 + generatedSuggestions = parseSuggestionLines(content, 2, 5); 1274 1332 1275 1333 console.log("Parsed chat suggestions:", generatedSuggestions); 1276 1334 ··· 1278 1336 generatedSuggestions = []; 1279 1337 } 1280 1338 1339 + if (taskId !== activeSuggestionsTaskId) { 1340 + return; 1341 + } 1342 + 1281 1343 renderSuggestions(false); 1282 1344 1283 1345 // Cache the suggestions for this tab 1284 - if (currentTabId) { 1346 + if (currentTabId && generatedSuggestions.length > 0) { 1285 1347 chrome.storage.session.set({ 1286 1348 [SUGGESTIONS_CACHE_PREFIX + currentTabId]: { 1287 1349 suggestions: generatedSuggestions, ··· 1292 1354 } 1293 1355 } catch (error) { 1294 1356 clearTimeout(timeoutId); 1357 + if (taskId !== activeSuggestionsTaskId) { 1358 + return; 1359 + } 1360 + const msg = String(error?.message || error || ""); 1295 1361 console.error("Failed to generate chat suggestions:", error); 1296 1362 generatedSuggestions = []; 1363 + if (/cancel/i.test(msg)) { 1364 + const existing = resultContainer.querySelector(".chat-suggestions"); 1365 + if (existing) existing.remove(); 1366 + return; 1367 + } 1297 1368 renderSuggestions(false); 1298 - renderSuggestions(); 1299 1369 } 1300 1370 } 1301 1371