feat: advance and highlight next AI suggestion on accept/deny/copy-to-note, remove copied suggestion from inline list, and add native Linux .deb packaging
This commit is contained in:
@@ -910,6 +910,32 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
|
||||
setFocusedIndex(0);
|
||||
};
|
||||
|
||||
const advanceAndHighlightNext = (
|
||||
removedId: string,
|
||||
updatedAll: AISuggestion[],
|
||||
) => {
|
||||
const prevIdx = pendingSuggestions.findIndex((item) => item.id === removedId);
|
||||
const nextPending = updatedAll.filter((item) => item.status === "pending");
|
||||
if (nextPending.length === 0) {
|
||||
setFocusedIndex(0);
|
||||
setHighlightedLine(null);
|
||||
setHighlightedText(null);
|
||||
return;
|
||||
}
|
||||
const nextIdx = Math.min(
|
||||
prevIdx >= 0 ? prevIdx : focusedIndex,
|
||||
nextPending.length - 1,
|
||||
);
|
||||
const nextSg = nextPending[nextIdx]!;
|
||||
setFocusedIndex(nextIdx);
|
||||
if (nextSg.originalText) {
|
||||
setHighlightedText(nextSg.originalText);
|
||||
}
|
||||
if (nextSg.lineNumber != null) {
|
||||
setHighlightedLine(nextSg.lineNumber);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAcceptSuggestion = async (sg: AISuggestion) => {
|
||||
if (!aiStore || !activeSessionId) return;
|
||||
const replacement = (draftEdits[sg.id] ?? sg.newText ?? sg.text).trim();
|
||||
@@ -950,6 +976,7 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
|
||||
|
||||
const nextList = await aiStore.listSuggestions(activeSessionId);
|
||||
setSuggestions(nextList);
|
||||
advanceAndHighlightNext(sg.id, nextList);
|
||||
|
||||
if (applied) {
|
||||
toast.success(`Accepted granular edit: "${replacement.slice(0, 36)}${replacement.length > 36 ? "…" : ""}"`);
|
||||
@@ -968,10 +995,7 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
|
||||
}
|
||||
const nextList = await aiStore.listSuggestions(activeSessionId);
|
||||
setSuggestions(nextList);
|
||||
if (highlightedText === sg.originalText || highlightedLine === sg.lineNumber) {
|
||||
setHighlightedLine(null);
|
||||
setHighlightedText(null);
|
||||
}
|
||||
advanceAndHighlightNext(sg.id, nextList);
|
||||
toast.info(`Denied suggestion: ${sg.title}`);
|
||||
};
|
||||
|
||||
@@ -995,7 +1019,19 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
|
||||
status: "draft",
|
||||
});
|
||||
setNotes(await aiStore.listNotes(activeSessionId));
|
||||
toast.success(`Copied "${sg.title}" to Notes`);
|
||||
|
||||
// Remove the suggestion from the inline edits list and advance to the next suggestion
|
||||
setSuggestions((prev) => prev.filter((item) => item.id !== sg.id));
|
||||
if (typeof aiStore.deleteSuggestion === "function") {
|
||||
await aiStore.deleteSuggestion(activeSessionId, sg.id);
|
||||
} else {
|
||||
await aiStore.resolveSuggestion(activeSessionId, sg.id, false);
|
||||
}
|
||||
const nextList = await aiStore.listSuggestions(activeSessionId);
|
||||
setSuggestions(nextList);
|
||||
advanceAndHighlightNext(sg.id, nextList);
|
||||
|
||||
toast.success(`Moved "${sg.title}" to Notes`);
|
||||
};
|
||||
|
||||
const handleAddNote = async () => {
|
||||
|
||||
@@ -139,6 +139,11 @@ describe("AISidebar + WritingView granular text-pegged suggestion flow", () => {
|
||||
await new Promise((r) => setTimeout(r, 120));
|
||||
});
|
||||
|
||||
// After accepting Suggestion #1, the next suggestion (#2) must be automatically highlighted
|
||||
expect(useProjectStore.getState().highlightedText).toContain(
|
||||
"the light had turned the sea the color of a bruised plum",
|
||||
);
|
||||
|
||||
// Accept Suggestion #2 (clause 2 of the SAME paragraph 2)
|
||||
const acceptBtn2 = Array.from(container.querySelectorAll("button")).find((b) =>
|
||||
b.textContent?.includes("Accept"),
|
||||
@@ -150,6 +155,25 @@ describe("AISidebar + WritingView granular text-pegged suggestion flow", () => {
|
||||
await new Promise((r) => setTimeout(r, 120));
|
||||
});
|
||||
|
||||
// After accepting Suggestion #2, Suggestion #3 (on the next paragraph) must be automatically highlighted
|
||||
expect(useProjectStore.getState().highlightedText).toContain(
|
||||
"She pressed her palm against the cold window",
|
||||
);
|
||||
expect(container.querySelector(".ps-editor")?.getAttribute("data-highlight-line")).toBe("3");
|
||||
|
||||
// Clicking "Copy to Note" on Suggestion #3 removes it from the inline suggestions list
|
||||
const beforeCards = container.querySelectorAll(".ps-ai-suggestion-card").length;
|
||||
const copyToNoteBtn = Array.from(container.querySelectorAll("button")).find((b) =>
|
||||
b.textContent?.includes("Copy to Note"),
|
||||
);
|
||||
expect(copyToNoteBtn).toBeDefined();
|
||||
await act(async () => {
|
||||
copyToNoteBtn!.click();
|
||||
await new Promise((r) => setTimeout(r, 120));
|
||||
});
|
||||
const afterCards = container.querySelectorAll(".ps-ai-suggestion-card").length;
|
||||
expect(afterCards).toBe(beforeCards - 1);
|
||||
|
||||
// Verify BOTH granular clauses in paragraph 2 were updated AND the rest of the paragraph was NOT deleted!
|
||||
const pmText = container.querySelector(".ProseMirror")?.textContent ?? "";
|
||||
expect(pmText).toContain("The ferry that carried Mara Vell to the Glass Harbor cleared the mainland before dawn");
|
||||
|
||||
Reference in New Issue
Block a user