Pre-process history for history display so we can write unit tests for it
This commit is contained in:
parent
9f064c57a7
commit
b88320177e
@ -1,4 +1,4 @@
|
|||||||
import { History, HistoryItemType } from "./history_types";
|
import { History } from "./history_types";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import {
|
import {
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
HoverCardContent,
|
HoverCardContent,
|
||||||
} from "../ui/hover-card";
|
} from "../ui/hover-card";
|
||||||
import { Badge } from "../ui/badge";
|
import { Badge } from "../ui/badge";
|
||||||
|
import { renderHistory } from "./utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
history: History;
|
history: History;
|
||||||
@ -15,32 +16,19 @@ interface Props {
|
|||||||
shouldDisableReverts: boolean;
|
shouldDisableReverts: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayHistoryItemType(itemType: HistoryItemType) {
|
|
||||||
switch (itemType) {
|
|
||||||
case "ai_create":
|
|
||||||
return "Create";
|
|
||||||
case "ai_edit":
|
|
||||||
return "Edit";
|
|
||||||
case "code_create":
|
|
||||||
return "Imported from code";
|
|
||||||
default: {
|
|
||||||
const exhaustiveCheck: never = itemType;
|
|
||||||
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HistoryDisplay({
|
export default function HistoryDisplay({
|
||||||
history,
|
history,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
revertToVersion,
|
revertToVersion,
|
||||||
shouldDisableReverts,
|
shouldDisableReverts,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
return history.length === 0 ? null : (
|
const renderedHistory = renderHistory(history, currentVersion);
|
||||||
|
|
||||||
|
return renderedHistory.length === 0 ? null : (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="flex flex-col h-screen">
|
||||||
<h1 className="font-bold mb-2">Versions</h1>
|
<h1 className="font-bold mb-2">Versions</h1>
|
||||||
<ul className="space-y-0 flex flex-col-reverse">
|
<ul className="space-y-0 flex flex-col-reverse">
|
||||||
{history.map((item, index) => (
|
{renderedHistory.map((item, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<HoverCard>
|
<HoverCard>
|
||||||
<HoverCardTrigger
|
<HoverCardTrigger
|
||||||
@ -48,9 +36,8 @@ export default function HistoryDisplay({
|
|||||||
"flex items-center justify-between space-x-2 p-2",
|
"flex items-center justify-between space-x-2 p-2",
|
||||||
"border-b cursor-pointer",
|
"border-b cursor-pointer",
|
||||||
{
|
{
|
||||||
" hover:bg-black hover:text-white":
|
" hover:bg-black hover:text-white": !item.isActive,
|
||||||
index !== currentVersion,
|
"bg-slate-500 text-white": item.isActive,
|
||||||
"bg-slate-500 text-white": index === currentVersion,
|
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -63,32 +50,16 @@ export default function HistoryDisplay({
|
|||||||
>
|
>
|
||||||
{" "}
|
{" "}
|
||||||
<div className="flex gap-x-1 truncate">
|
<div className="flex gap-x-1 truncate">
|
||||||
<h2 className="text-sm truncate">
|
<h2 className="text-sm truncate">{item.summary}</h2>
|
||||||
{item.type === "ai_edit"
|
{item.parentVersion !== null && (
|
||||||
? item.inputs.prompt
|
<h2 className="text-sm">(parent: {item.parentVersion})</h2>
|
||||||
: item.type === "ai_create"
|
)}
|
||||||
? "Create"
|
|
||||||
: "Imported from code"}
|
|
||||||
</h2>
|
|
||||||
{/* <h2 className="text-sm">{displayHistoryItemType(item.type)}</h2> */}
|
|
||||||
{item.parentIndex !== null &&
|
|
||||||
item.parentIndex !== index - 1 ? (
|
|
||||||
<h2 className="text-sm">
|
|
||||||
(parent: v{(item.parentIndex || 0) + 1})
|
|
||||||
</h2>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-sm">v{index + 1}</h2>
|
<h2 className="text-sm">v{index + 1}</h2>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent>
|
<HoverCardContent>
|
||||||
<div>
|
<div>{item.summary}</div>
|
||||||
{item.type === "ai_edit"
|
<Badge>{item.type}</Badge>
|
||||||
? item.inputs.prompt
|
|
||||||
: item.type === "ai_create"
|
|
||||||
? "Create"
|
|
||||||
: "Imported from code"}
|
|
||||||
</div>
|
|
||||||
<Badge>{displayHistoryItemType(item.type)}</Badge>
|
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -32,3 +32,10 @@ export type CodeCreateInputs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type History = HistoryItem[];
|
export type History = HistoryItem[];
|
||||||
|
|
||||||
|
export type RenderedHistoryItem = {
|
||||||
|
type: string;
|
||||||
|
summary: string;
|
||||||
|
parentVersion: string | null;
|
||||||
|
isActive: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { extractHistoryTree } from "./utils";
|
import { extractHistoryTree, renderHistory } from "./utils";
|
||||||
import type { History } from "./history_types";
|
import type { History } from "./history_types";
|
||||||
|
|
||||||
const basicLinearHistory: History = [
|
const basicLinearHistory: History = [
|
||||||
@ -29,6 +29,18 @@ const basicLinearHistory: History = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const basicLinearHistoryWithCode: History = [
|
||||||
|
{
|
||||||
|
type: "code_create",
|
||||||
|
parentIndex: null,
|
||||||
|
code: "<html>1. create</html>",
|
||||||
|
inputs: {
|
||||||
|
code: "<html>1. create</html>",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...basicLinearHistory.slice(1),
|
||||||
|
];
|
||||||
|
|
||||||
const basicBranchingHistory: History = [
|
const basicBranchingHistory: History = [
|
||||||
...basicLinearHistory,
|
...basicLinearHistory,
|
||||||
{
|
{
|
||||||
@ -121,3 +133,98 @@ test("should correctly extract the history tree", () => {
|
|||||||
// Bad tree
|
// Bad tree
|
||||||
expect(() => extractHistoryTree(basicBadHistory, 1)).toThrow();
|
expect(() => extractHistoryTree(basicBadHistory, 1)).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should correctly render the history tree", () => {
|
||||||
|
expect(renderHistory(basicLinearHistory, 2)).toEqual([
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "Create",
|
||||||
|
type: "Create",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "use better icons",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: true,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "make text red",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Current version is the first version
|
||||||
|
expect(renderHistory(basicLinearHistory, 0)).toEqual([
|
||||||
|
{
|
||||||
|
isActive: true,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "Create",
|
||||||
|
type: "Create",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "use better icons",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "make text red",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Render a history with code
|
||||||
|
expect(renderHistory(basicLinearHistoryWithCode, 0)).toEqual([
|
||||||
|
{
|
||||||
|
isActive: true,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "Imported from code",
|
||||||
|
type: "Imported from code",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "use better icons",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "make text red",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Render a non-linear history
|
||||||
|
expect(renderHistory(basicBranchingHistory, 3)).toEqual([
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "Create",
|
||||||
|
type: "Create",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "use better icons",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
parentVersion: null,
|
||||||
|
summary: "make text red",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isActive: true,
|
||||||
|
parentVersion: "v2",
|
||||||
|
summary: "make text green",
|
||||||
|
type: "Edit",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { History, HistoryItem } from "./history_types";
|
import {
|
||||||
|
History,
|
||||||
|
HistoryItem,
|
||||||
|
HistoryItemType,
|
||||||
|
RenderedHistoryItem,
|
||||||
|
} from "./history_types";
|
||||||
|
|
||||||
export function extractHistoryTree(
|
export function extractHistoryTree(
|
||||||
history: History,
|
history: History,
|
||||||
@ -30,3 +35,62 @@ export function extractHistoryTree(
|
|||||||
|
|
||||||
return flatHistory;
|
return flatHistory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayHistoryItemType(itemType: HistoryItemType) {
|
||||||
|
switch (itemType) {
|
||||||
|
case "ai_create":
|
||||||
|
return "Create";
|
||||||
|
case "ai_edit":
|
||||||
|
return "Edit";
|
||||||
|
case "code_create":
|
||||||
|
return "Imported from code";
|
||||||
|
default: {
|
||||||
|
const exhaustiveCheck: never = itemType;
|
||||||
|
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizeHistoryItem(item: HistoryItem) {
|
||||||
|
const itemType = item.type;
|
||||||
|
switch (itemType) {
|
||||||
|
case "ai_create":
|
||||||
|
return "Create";
|
||||||
|
case "ai_edit":
|
||||||
|
return item.inputs.prompt;
|
||||||
|
case "code_create":
|
||||||
|
return "Imported from code";
|
||||||
|
default: {
|
||||||
|
const exhaustiveCheck: never = itemType;
|
||||||
|
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderHistory = (
|
||||||
|
history: History,
|
||||||
|
currentVersion: number | null
|
||||||
|
) => {
|
||||||
|
const renderedHistory: RenderedHistoryItem[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < history.length; i++) {
|
||||||
|
const item = history[i];
|
||||||
|
// Only show the parent version if it's not the previous version
|
||||||
|
// (i.e. it's the branching point) and if it's not the first version
|
||||||
|
const parentVersion =
|
||||||
|
item.parentIndex !== null && item.parentIndex !== i - 1
|
||||||
|
? `v${(item.parentIndex || 0) + 1}`
|
||||||
|
: null;
|
||||||
|
const type = displayHistoryItemType(item.type);
|
||||||
|
const isActive = i === currentVersion;
|
||||||
|
const summary = summarizeHistoryItem(item);
|
||||||
|
renderedHistory.push({
|
||||||
|
isActive,
|
||||||
|
summary: summary,
|
||||||
|
parentVersion,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderedHistory;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user