feat(ai): support editing sent user messages
* feat(ai): support editing sent user messages Adds inline edit support for user messages in the AI chat panel. Hover over a user message to reveal the edit button; confirming the edit truncates all subsequent history and re-sends with the new content. * fix(ai): fix IME composition and ref-focus regression in message edit - Handle IME composition events in onEditKeydown to prevent accidental submission on Enter during CJK candidate selection - Replace per-render :ref focus callback with nextTick + data attribute to avoid cursor jumping on every keystroke - Extract visibleToActualIndex into aiMessageEdit.ts for testability - Add 8 unit tests covering index mapping with contextSummary messages * fix(ai): prevent data loss and mention pollution in message edit - Guard connection/config before truncating messages to prevent silent data loss when send() bails early - Clear selectedMentions before resend to prevent stale chips from polluting edited message content
This commit is contained in:
1 parent
f1f4518b67
commit
fe4908321e
10 files changed
+169
-4
No files matched your search
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { visibleToActualIndex } from "@/lib/aiMessageEdit";
|
||||
|
||||
describe("visibleToActualIndex", () => {
|
||||
it("maps visible index 0 to first non-summary message", () => {
|
||||
const messages = [{ kind: undefined }, { kind: undefined }, { kind: undefined }];
|
||||
expect(visibleToActualIndex(messages, 0)).toBe(0);
|
||||
});
|
||||
|
||||
it("maps visible index 1 correctly with no summaries", () => {
|
||||
const messages = [{ kind: undefined }, { kind: undefined }, { kind: undefined }];
|
||||
expect(visibleToActualIndex(messages, 1)).toBe(1);
|
||||
});
|
||||
|
||||
it("skips contextSummary messages when computing visible index", () => {
|
||||
const messages = [{ kind: undefined }, { kind: "contextSummary" }, { kind: undefined }, { kind: undefined }];
|
||||
// visible[0] → actual[0], visible[1] → actual[2], visible[2] → actual[3]
|
||||
expect(visibleToActualIndex(messages, 0)).toBe(0);
|
||||
expect(visibleToActualIndex(messages, 1)).toBe(2);
|
||||
expect(visibleToActualIndex(messages, 2)).toBe(3);
|
||||
});
|
||||
|
||||
it("skips multiple consecutive contextSummary messages", () => {
|
||||
const messages = [{ kind: "contextSummary" }, { kind: "contextSummary" }, { kind: undefined }, { kind: undefined }];
|
||||
expect(visibleToActualIndex(messages, 0)).toBe(2);
|
||||
expect(visibleToActualIndex(messages, 1)).toBe(3);
|
||||
});
|
||||
|
||||
it("returns -1 when visibleIndex is out of range", () => {
|
||||
const messages = [{ kind: undefined }, { kind: undefined }];
|
||||
expect(visibleToActualIndex(messages, 5)).toBe(-1);
|
||||
});
|
||||
|
||||
it("returns -1 for empty messages array", () => {
|
||||
expect(visibleToActualIndex([], 0)).toBe(-1);
|
||||
});
|
||||
|
||||
it("returns -1 when all messages are contextSummary", () => {
|
||||
const messages = [{ kind: "contextSummary" }, { kind: "contextSummary" }];
|
||||
expect(visibleToActualIndex(messages, 0)).toBe(-1);
|
||||
});
|
||||
|
||||
it("handles last visible message correctly", () => {
|
||||
const messages = [{ kind: undefined }, { kind: "contextSummary" }, { kind: undefined }];
|
||||
expect(visibleToActualIndex(messages, 1)).toBe(2);
|
||||
expect(visibleToActualIndex(messages, 2)).toBe(-1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface MessageWithKind {
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a visible message index (contextSummary messages excluded) to the
|
||||
* actual index in the full messages array.
|
||||
* Returns -1 if not found.
|
||||
*/
|
||||
export function visibleToActualIndex(messages: MessageWithKind[], visibleIndex: number): number {
|
||||
let vi = 0;
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
if (messages[i].kind !== "contextSummary") {
|
||||
if (vi === visibleIndex) return i;
|
||||
vi++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
Reference in new issue
Block a user