feat: newest message first, Raycast-style conversation sidebar (#7)
This commit was merged in pull request #7.
This commit is contained in:
@@ -4,7 +4,6 @@ import {
|
||||
STREAMING_CURSOR,
|
||||
USER_LABEL,
|
||||
buildThreadMarkdown,
|
||||
previewText,
|
||||
quote,
|
||||
sessionToPlainText,
|
||||
} from "../lib/markdown";
|
||||
@@ -14,7 +13,6 @@ function message(overrides: Partial<ThreadMessage> & Pick<ThreadMessage, "role">
|
||||
return {
|
||||
id: "m1",
|
||||
text: "",
|
||||
preview: "",
|
||||
createdAt: 0,
|
||||
isStreaming: false,
|
||||
thinking: "",
|
||||
@@ -46,6 +44,21 @@ describe("buildThreadMarkdown", () => {
|
||||
expect(markdown).toContain("4");
|
||||
});
|
||||
|
||||
it("groups an answer with its question, dividing only between exchanges", () => {
|
||||
const markdown = buildThreadMarkdown([
|
||||
message({ id: "t2:assistant", role: "assistant", text: "second answer" }),
|
||||
message({ id: "t2:user", role: "user", text: "second question" }),
|
||||
message({ id: "t1:assistant", role: "assistant", text: "first answer" }),
|
||||
message({ id: "t1:user", role: "user", text: "first question" }),
|
||||
]);
|
||||
|
||||
// One rule between the two exchanges, none inside one.
|
||||
expect(markdown.match(/^---$/gm)?.length).toBe(1);
|
||||
// And the answer precedes the question it belongs to.
|
||||
expect(markdown.indexOf("second answer")).toBeLessThan(markdown.indexOf("second question"));
|
||||
expect(markdown.indexOf("second question")).toBeLessThan(markdown.indexOf("first answer"));
|
||||
});
|
||||
|
||||
it("labels each speaker, in order", () => {
|
||||
const markdown = buildThreadMarkdown([
|
||||
message({ role: "user", text: "q" }),
|
||||
@@ -89,6 +102,15 @@ describe("buildThreadMarkdown", () => {
|
||||
expect(finished).not.toContain(STREAMING_CURSOR);
|
||||
});
|
||||
|
||||
it("shows the activity line while a tool runs", () => {
|
||||
const markdown = buildThreadMarkdown([
|
||||
message({ role: "assistant", isStreaming: true, status: "Searching the web" }),
|
||||
]);
|
||||
|
||||
expect(markdown).toContain("_Searching the web…_");
|
||||
expect(markdown).not.toContain("Thinking");
|
||||
});
|
||||
|
||||
it("shows a placeholder while an answer has no text yet", () => {
|
||||
expect(buildThreadMarkdown([message({ role: "assistant", isStreaming: true })])).toContain("Thinking");
|
||||
expect(buildThreadMarkdown([message({ role: "assistant" })])).toContain("No response");
|
||||
@@ -125,20 +147,6 @@ describe("buildThreadMarkdown", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("previewText", () => {
|
||||
it("collapses newlines so a preview stays on one row", () => {
|
||||
expect(previewText("a\n\nb", 40)).toBe("a b");
|
||||
});
|
||||
|
||||
it("clips with an ellipsis", () => {
|
||||
expect(previewText("a".repeat(50), 5)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it("leaves short text alone", () => {
|
||||
expect(previewText("hi", 40)).toBe("hi");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sessionToPlainText", () => {
|
||||
function turn(prompt: string, response: string, error?: string): ChatTurn {
|
||||
return { id: prompt, prompt, response, thinking: "", model: "m", createdAt: 0, toolCalls: [], error };
|
||||
|
||||
Reference in New Issue
Block a user