intermediate changes towards multiple generations
This commit is contained in:
parent
5f6dd08411
commit
8e8f0b4b64
@ -36,6 +36,7 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"html2canvas": "^1.4.1",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
|
||||
@ -8,8 +8,7 @@ import { OnboardingNote } from "./components/messages/OnboardingNote";
|
||||
import { usePersistedState } from "./hooks/usePersistedState";
|
||||
import TermsOfServiceDialog from "./components/TermsOfServiceDialog";
|
||||
import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants";
|
||||
import { History } from "./components/history/history_types";
|
||||
import { extractHistoryTree } from "./components/history/utils";
|
||||
import { extractHistory } from "./components/history/utils";
|
||||
import toast from "react-hot-toast";
|
||||
import { Stack } from "./lib/stacks";
|
||||
import { CodeGenerationModel } from "./lib/models";
|
||||
@ -23,6 +22,7 @@ import DeprecationMessage from "./components/messages/DeprecationMessage";
|
||||
import { GenerationSettings } from "./components/settings/GenerationSettings";
|
||||
import StartPane from "./components/start-pane/StartPane";
|
||||
import { takeScreenshot } from "./lib/takeScreenshot";
|
||||
import { Commit, createCommit } from "./components/history/history_types";
|
||||
|
||||
function App() {
|
||||
const {
|
||||
@ -34,18 +34,18 @@ function App() {
|
||||
referenceImages,
|
||||
setReferenceImages,
|
||||
|
||||
head,
|
||||
commits,
|
||||
addCommit,
|
||||
removeCommit,
|
||||
setHead,
|
||||
appendCommitCode,
|
||||
setCommitCode,
|
||||
resetCommits,
|
||||
|
||||
// Outputs
|
||||
setGeneratedCode,
|
||||
currentVariantIndex,
|
||||
setVariant,
|
||||
appendToVariant,
|
||||
resetVariants,
|
||||
appendExecutionConsole,
|
||||
resetExecutionConsoles,
|
||||
currentVersion,
|
||||
setCurrentVersion,
|
||||
appHistory,
|
||||
setAppHistory,
|
||||
} = useProjectStore();
|
||||
|
||||
const {
|
||||
@ -113,34 +113,30 @@ function App() {
|
||||
setShouldIncludeResultImage(false);
|
||||
setUpdateInstruction("");
|
||||
disableInSelectAndEditMode();
|
||||
setGeneratedCode("");
|
||||
resetVariants();
|
||||
resetExecutionConsoles();
|
||||
|
||||
resetCommits();
|
||||
|
||||
// Inputs
|
||||
setInputMode("image");
|
||||
setReferenceImages([]);
|
||||
setIsImportedFromCode(false);
|
||||
|
||||
setAppHistory([]);
|
||||
setCurrentVersion(null);
|
||||
};
|
||||
|
||||
const regenerate = () => {
|
||||
if (currentVersion === null) {
|
||||
// TODO: post to Sentry
|
||||
if (head === null) {
|
||||
toast.error(
|
||||
"No current version set. Please open a Github issue as this shouldn't happen."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the previous command
|
||||
const previousCommand = appHistory[currentVersion];
|
||||
if (previousCommand.type !== "ai_create") {
|
||||
const currentCommit = commits[head];
|
||||
if (currentCommit.type !== "ai_create") {
|
||||
toast.error("Only the first version can be regenerated.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-run the create
|
||||
doCreate(referenceImages, inputMode);
|
||||
};
|
||||
@ -149,25 +145,32 @@ function App() {
|
||||
const cancelCodeGeneration = () => {
|
||||
wsRef.current?.close?.(USER_CLOSE_WEB_SOCKET_CODE);
|
||||
// make sure stop can correct the state even if the websocket is already closed
|
||||
cancelCodeGenerationAndReset();
|
||||
// TODO: Look into this
|
||||
// cancelCodeGenerationAndReset();
|
||||
};
|
||||
|
||||
// Used for code generation failure as well
|
||||
const cancelCodeGenerationAndReset = () => {
|
||||
// When this is the first version, reset the entire app state
|
||||
if (currentVersion === null) {
|
||||
const cancelCodeGenerationAndReset = (commit: Commit) => {
|
||||
// When the current commit is the first version, reset the entire app state
|
||||
if (commit.type === "ai_create") {
|
||||
reset();
|
||||
} else {
|
||||
// Otherwise, revert to the last version
|
||||
setGeneratedCode(appHistory[currentVersion].code);
|
||||
// Otherwise, remove current commit from commits
|
||||
removeCommit(commit.hash);
|
||||
|
||||
// Revert to parent commit
|
||||
const parentCommitHash = commit.parentHash;
|
||||
if (parentCommitHash) {
|
||||
setHead(parentCommitHash);
|
||||
} else {
|
||||
// TODO: Hit Sentry
|
||||
}
|
||||
|
||||
setAppState(AppState.CODE_READY);
|
||||
}
|
||||
};
|
||||
|
||||
function doGenerateCode(
|
||||
params: CodeGenerationParams,
|
||||
parentVersion: number | null
|
||||
) {
|
||||
function doGenerateCode(params: CodeGenerationParams) {
|
||||
// Reset the execution console
|
||||
resetExecutionConsoles();
|
||||
|
||||
@ -177,69 +180,51 @@ function App() {
|
||||
// Merge settings with params
|
||||
const updatedParams = { ...params, ...settings };
|
||||
|
||||
const baseCommitObject = {
|
||||
date_created: new Date(),
|
||||
variants: [{ code: "" }, { code: "" }],
|
||||
selectedVariantIndex: 0,
|
||||
};
|
||||
|
||||
const commitInputObject =
|
||||
params.generationType === "create"
|
||||
? {
|
||||
...baseCommitObject,
|
||||
type: "ai_create" as const,
|
||||
parentHash: null,
|
||||
inputs: { image_url: referenceImages[0] },
|
||||
}
|
||||
: {
|
||||
...baseCommitObject,
|
||||
type: "ai_edit" as const,
|
||||
parentHash: head,
|
||||
inputs: {
|
||||
prompt: params.history
|
||||
? params.history[params.history.length - 1]
|
||||
: "",
|
||||
},
|
||||
};
|
||||
|
||||
const commit = createCommit(commitInputObject);
|
||||
addCommit(commit);
|
||||
setHead(commit.hash);
|
||||
|
||||
generateCode(
|
||||
wsRef,
|
||||
updatedParams,
|
||||
// On change
|
||||
(token, variant) => {
|
||||
if (variant === currentVariantIndex) {
|
||||
setGeneratedCode((prev) => prev + token);
|
||||
}
|
||||
|
||||
appendToVariant(token, variant);
|
||||
appendCommitCode(commit.hash, variant, token);
|
||||
},
|
||||
// On set code
|
||||
(code, variant) => {
|
||||
if (variant === currentVariantIndex) {
|
||||
setGeneratedCode(code);
|
||||
}
|
||||
|
||||
setVariant(code, variant);
|
||||
|
||||
// TODO: How to deal with variants?
|
||||
if (params.generationType === "create") {
|
||||
setAppHistory([
|
||||
{
|
||||
type: "ai_create",
|
||||
parentIndex: null,
|
||||
code,
|
||||
inputs: { image_url: referenceImages[0] },
|
||||
},
|
||||
]);
|
||||
setCurrentVersion(0);
|
||||
} else {
|
||||
setAppHistory((prev) => {
|
||||
// Validate parent version
|
||||
if (parentVersion === null) {
|
||||
toast.error(
|
||||
"No parent version set. Contact support or open a Github issue."
|
||||
);
|
||||
return prev;
|
||||
}
|
||||
|
||||
const newHistory: History = [
|
||||
...prev,
|
||||
{
|
||||
type: "ai_edit",
|
||||
parentIndex: parentVersion,
|
||||
code,
|
||||
inputs: {
|
||||
prompt: params.history
|
||||
? params.history[params.history.length - 1]
|
||||
: "", // History should never be empty when performing an edit
|
||||
},
|
||||
},
|
||||
];
|
||||
setCurrentVersion(newHistory.length - 1);
|
||||
return newHistory;
|
||||
});
|
||||
}
|
||||
setCommitCode(commit.hash, variant, code);
|
||||
},
|
||||
// On status update
|
||||
(line, variant) => appendExecutionConsole(variant, line),
|
||||
// On cancel
|
||||
() => {
|
||||
cancelCodeGenerationAndReset();
|
||||
cancelCodeGenerationAndReset(commit);
|
||||
},
|
||||
// On complete
|
||||
() => {
|
||||
@ -259,14 +244,11 @@ function App() {
|
||||
|
||||
// Kick off the code generation
|
||||
if (referenceImages.length > 0) {
|
||||
doGenerateCode(
|
||||
{
|
||||
generationType: "create",
|
||||
image: referenceImages[0],
|
||||
inputMode,
|
||||
},
|
||||
currentVersion
|
||||
);
|
||||
doGenerateCode({
|
||||
generationType: "create",
|
||||
image: referenceImages[0],
|
||||
inputMode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,16 +262,17 @@ function App() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVersion === null) {
|
||||
toast.error(
|
||||
"No current version set. Contact support or open a Github issue."
|
||||
);
|
||||
return;
|
||||
}
|
||||
// if (currentVersion === null) {
|
||||
// toast.error(
|
||||
// "No current version set. Contact support or open a Github issue."
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
let historyTree;
|
||||
try {
|
||||
historyTree = extractHistoryTree(appHistory, currentVersion);
|
||||
// TODO: Fix head being null
|
||||
historyTree = extractHistory(head || "", commits);
|
||||
} catch {
|
||||
toast.error(
|
||||
"Version history is invalid. This shouldn't happen. Please contact support or open a Github issue."
|
||||
@ -309,34 +292,28 @@ function App() {
|
||||
|
||||
const updatedHistory = [...historyTree, modifiedUpdateInstruction];
|
||||
|
||||
console.log(updatedHistory);
|
||||
|
||||
if (shouldIncludeResultImage) {
|
||||
const resultImage = await takeScreenshot();
|
||||
doGenerateCode(
|
||||
{
|
||||
generationType: "update",
|
||||
inputMode,
|
||||
image: referenceImages[0],
|
||||
resultImage: resultImage,
|
||||
history: updatedHistory,
|
||||
isImportedFromCode,
|
||||
},
|
||||
currentVersion
|
||||
);
|
||||
doGenerateCode({
|
||||
generationType: "update",
|
||||
inputMode,
|
||||
image: referenceImages[0],
|
||||
resultImage: resultImage,
|
||||
history: updatedHistory,
|
||||
isImportedFromCode,
|
||||
});
|
||||
} else {
|
||||
doGenerateCode(
|
||||
{
|
||||
generationType: "update",
|
||||
inputMode,
|
||||
image: referenceImages[0],
|
||||
history: updatedHistory,
|
||||
isImportedFromCode,
|
||||
},
|
||||
currentVersion
|
||||
);
|
||||
doGenerateCode({
|
||||
generationType: "update",
|
||||
inputMode,
|
||||
image: referenceImages[0],
|
||||
history: updatedHistory,
|
||||
isImportedFromCode,
|
||||
});
|
||||
}
|
||||
|
||||
setGeneratedCode("");
|
||||
resetVariants();
|
||||
setUpdateInstruction("");
|
||||
}
|
||||
|
||||
@ -358,18 +335,27 @@ function App() {
|
||||
// Set input state
|
||||
setIsImportedFromCode(true);
|
||||
|
||||
console.log(code);
|
||||
|
||||
// Set up this project
|
||||
setGeneratedCode(code);
|
||||
// TODO*
|
||||
// setGeneratedCode(code);
|
||||
setStack(stack);
|
||||
setAppHistory([
|
||||
{
|
||||
type: "code_create",
|
||||
parentIndex: null,
|
||||
code,
|
||||
inputs: { code },
|
||||
},
|
||||
]);
|
||||
setCurrentVersion(0);
|
||||
// setAppHistory([
|
||||
// {
|
||||
// type: "code_create",
|
||||
// parentIndex: null,
|
||||
// code,
|
||||
// inputs: { code },
|
||||
// },
|
||||
// ]);
|
||||
// setVariant(0, {
|
||||
// type: "code_create",
|
||||
// parentIndex: null,
|
||||
// code,
|
||||
// });
|
||||
// setCurrentVariantIndex(0);
|
||||
// setCurrentVersion(0);
|
||||
|
||||
// Set the app state
|
||||
setAppState(AppState.CODE_READY);
|
||||
|
||||
@ -2,7 +2,7 @@ import toast from "react-hot-toast";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Badge } from "../ui/badge";
|
||||
import { renderHistory } from "./utils";
|
||||
import { summarizeHistoryItem } from "./utils";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
@ -17,25 +17,58 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function HistoryDisplay({ shouldDisableReverts }: Props) {
|
||||
const {
|
||||
appHistory: history,
|
||||
currentVersion,
|
||||
setCurrentVersion,
|
||||
setGeneratedCode,
|
||||
} = useProjectStore();
|
||||
const renderedHistory = renderHistory(history, currentVersion);
|
||||
const { commits, head, setHead } = useProjectStore();
|
||||
|
||||
const revertToVersion = (index: number) => {
|
||||
if (index < 0 || index >= history.length || !history[index]) return;
|
||||
setCurrentVersion(index);
|
||||
setGeneratedCode(history[index].code);
|
||||
// TODO: Clean this up
|
||||
|
||||
const newHistory = Object.values(commits).flatMap((commit) => {
|
||||
if (commit.type === "ai_create" || commit.type === "ai_edit") {
|
||||
return {
|
||||
type: commit.type,
|
||||
hash: commit.hash,
|
||||
summary: summarizeHistoryItem(commit),
|
||||
parentHash: commit.parentHash,
|
||||
code: commit.variants[commit.selectedVariantIndex].code,
|
||||
inputs: commit.inputs,
|
||||
date_created: commit.date_created,
|
||||
};
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// Sort by date created
|
||||
newHistory.sort(
|
||||
(a, b) =>
|
||||
new Date(a.date_created).getTime() - new Date(b.date_created).getTime()
|
||||
);
|
||||
|
||||
const setParentVersion = (
|
||||
parentHash: string | null,
|
||||
currentHash: string | null
|
||||
) => {
|
||||
if (!parentHash) return null;
|
||||
const parentIndex = newHistory.findIndex(
|
||||
(item) => item.hash === parentHash
|
||||
);
|
||||
const currentIndex = newHistory.findIndex(
|
||||
(item) => item.hash === currentHash
|
||||
);
|
||||
return parentIndex !== -1 && parentIndex != currentIndex - 1
|
||||
? parentIndex + 1
|
||||
: null;
|
||||
};
|
||||
|
||||
return renderedHistory.length === 0 ? null : (
|
||||
// Update newHistory to include the parent version
|
||||
const updatedHistory = newHistory.map((item) => ({
|
||||
...item,
|
||||
parentVersion: setParentVersion(item.parentHash, item.hash),
|
||||
}));
|
||||
|
||||
return updatedHistory.length === 0 ? null : (
|
||||
<div className="flex flex-col h-screen">
|
||||
<h1 className="font-bold mb-2">Versions</h1>
|
||||
<ul className="space-y-0 flex flex-col-reverse">
|
||||
{renderedHistory.map((item, index) => (
|
||||
{updatedHistory.map((item, index) => (
|
||||
<li key={index}>
|
||||
<Collapsible>
|
||||
<div
|
||||
@ -43,8 +76,8 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) {
|
||||
"flex items-center justify-between space-x-2 w-full pr-2",
|
||||
"border-b cursor-pointer",
|
||||
{
|
||||
" hover:bg-black hover:text-white": !item.isActive,
|
||||
"bg-slate-500 text-white": item.isActive,
|
||||
" hover:bg-black hover:text-white": item.hash === head,
|
||||
"bg-slate-500 text-white": item.hash === head,
|
||||
}
|
||||
)}
|
||||
>
|
||||
@ -55,14 +88,14 @@ export default function HistoryDisplay({ shouldDisableReverts }: Props) {
|
||||
? toast.error(
|
||||
"Please wait for code generation to complete before viewing an older version."
|
||||
)
|
||||
: revertToVersion(index)
|
||||
: setHead(item.hash)
|
||||
}
|
||||
>
|
||||
<div className="flex gap-x-1 truncate">
|
||||
<h2 className="text-sm truncate">{item.summary}</h2>
|
||||
{item.parentVersion !== null && (
|
||||
<h2 className="text-sm">
|
||||
(parent: {item.parentVersion})
|
||||
(parent: v{item.parentVersion})
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,37 +1,44 @@
|
||||
export type HistoryItemType = "ai_create" | "ai_edit" | "code_create";
|
||||
export type CommitType = "ai_create" | "ai_edit" | "code_create";
|
||||
|
||||
type CommonHistoryItem = {
|
||||
parentIndex: null | number;
|
||||
export type CommitHash = string;
|
||||
|
||||
export type Variant = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type HistoryItem =
|
||||
| ({
|
||||
type: "ai_create";
|
||||
inputs: AiCreateInputs;
|
||||
} & CommonHistoryItem)
|
||||
| ({
|
||||
type: "ai_edit";
|
||||
inputs: AiEditInputs;
|
||||
} & CommonHistoryItem)
|
||||
| ({
|
||||
type: "code_create";
|
||||
inputs: CodeCreateInputs;
|
||||
} & CommonHistoryItem);
|
||||
|
||||
export type AiCreateInputs = {
|
||||
image_url: string;
|
||||
export type BaseCommit = {
|
||||
hash: CommitHash;
|
||||
parentHash: CommitHash | null;
|
||||
date_created: Date;
|
||||
variants: Variant[];
|
||||
selectedVariantIndex: number;
|
||||
};
|
||||
|
||||
export type AiEditInputs = {
|
||||
prompt: string;
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
// TODO: Move to a different file
|
||||
export function createCommit(
|
||||
commit: Omit<AiCreateCommit, "hash"> | Omit<AiEditCommit, "hash">
|
||||
): Commit {
|
||||
const hash = nanoid();
|
||||
return { ...commit, hash };
|
||||
}
|
||||
|
||||
export type AiCreateCommit = BaseCommit & {
|
||||
type: "ai_create";
|
||||
inputs: {
|
||||
image_url: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CodeCreateInputs = {
|
||||
code: string;
|
||||
export type AiEditCommit = BaseCommit & {
|
||||
type: "ai_edit";
|
||||
inputs: {
|
||||
prompt: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type History = HistoryItem[];
|
||||
export type Commit = AiCreateCommit | AiEditCommit;
|
||||
|
||||
export type RenderedHistoryItem = {
|
||||
type: string;
|
||||
|
||||
@ -1,231 +1,231 @@
|
||||
import { extractHistoryTree, renderHistory } from "./utils";
|
||||
import type { History } from "./history_types";
|
||||
// import { extractHistoryTree, renderHistory } from "./utils";
|
||||
// import type { History } from "./history_types";
|
||||
|
||||
const basicLinearHistory: History = [
|
||||
{
|
||||
type: "ai_create",
|
||||
parentIndex: null,
|
||||
code: "<html>1. create</html>",
|
||||
inputs: {
|
||||
image_url: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "ai_edit",
|
||||
parentIndex: 0,
|
||||
code: "<html>2. edit with better icons</html>",
|
||||
inputs: {
|
||||
prompt: "use better icons",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "ai_edit",
|
||||
parentIndex: 1,
|
||||
code: "<html>3. edit with better icons and red text</html>",
|
||||
inputs: {
|
||||
prompt: "make text red",
|
||||
},
|
||||
},
|
||||
];
|
||||
// const basicLinearHistory: History = [
|
||||
// {
|
||||
// type: "ai_create",
|
||||
// parentIndex: null,
|
||||
// code: "<html>1. create</html>",
|
||||
// inputs: {
|
||||
// image_url: "",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// type: "ai_edit",
|
||||
// parentIndex: 0,
|
||||
// code: "<html>2. edit with better icons</html>",
|
||||
// inputs: {
|
||||
// prompt: "use better icons",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// type: "ai_edit",
|
||||
// parentIndex: 1,
|
||||
// code: "<html>3. edit with better icons and red text</html>",
|
||||
// inputs: {
|
||||
// prompt: "make text red",
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
|
||||
const basicLinearHistoryWithCode: History = [
|
||||
{
|
||||
type: "code_create",
|
||||
parentIndex: null,
|
||||
code: "<html>1. create</html>",
|
||||
inputs: {
|
||||
code: "<html>1. create</html>",
|
||||
},
|
||||
},
|
||||
...basicLinearHistory.slice(1),
|
||||
];
|
||||
// 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 = [
|
||||
...basicLinearHistory,
|
||||
{
|
||||
type: "ai_edit",
|
||||
parentIndex: 1,
|
||||
code: "<html>4. edit with better icons and green text</html>",
|
||||
inputs: {
|
||||
prompt: "make text green",
|
||||
},
|
||||
},
|
||||
];
|
||||
// const basicBranchingHistory: History = [
|
||||
// ...basicLinearHistory,
|
||||
// {
|
||||
// type: "ai_edit",
|
||||
// parentIndex: 1,
|
||||
// code: "<html>4. edit with better icons and green text</html>",
|
||||
// inputs: {
|
||||
// prompt: "make text green",
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
|
||||
const longerBranchingHistory: History = [
|
||||
...basicBranchingHistory,
|
||||
{
|
||||
type: "ai_edit",
|
||||
parentIndex: 3,
|
||||
code: "<html>5. edit with better icons and green, bold text</html>",
|
||||
inputs: {
|
||||
prompt: "make text bold",
|
||||
},
|
||||
},
|
||||
];
|
||||
// const longerBranchingHistory: History = [
|
||||
// ...basicBranchingHistory,
|
||||
// {
|
||||
// type: "ai_edit",
|
||||
// parentIndex: 3,
|
||||
// code: "<html>5. edit with better icons and green, bold text</html>",
|
||||
// inputs: {
|
||||
// prompt: "make text bold",
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
|
||||
const basicBadHistory: History = [
|
||||
{
|
||||
type: "ai_create",
|
||||
parentIndex: null,
|
||||
code: "<html>1. create</html>",
|
||||
inputs: {
|
||||
image_url: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "ai_edit",
|
||||
parentIndex: 2, // <- Bad parent index
|
||||
code: "<html>2. edit with better icons</html>",
|
||||
inputs: {
|
||||
prompt: "use better icons",
|
||||
},
|
||||
},
|
||||
];
|
||||
// const basicBadHistory: History = [
|
||||
// {
|
||||
// type: "ai_create",
|
||||
// parentIndex: null,
|
||||
// code: "<html>1. create</html>",
|
||||
// inputs: {
|
||||
// image_url: "",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// type: "ai_edit",
|
||||
// parentIndex: 2, // <- Bad parent index
|
||||
// code: "<html>2. edit with better icons</html>",
|
||||
// inputs: {
|
||||
// prompt: "use better icons",
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
|
||||
describe("History Utils", () => {
|
||||
test("should correctly extract the history tree", () => {
|
||||
expect(extractHistoryTree(basicLinearHistory, 2)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text red",
|
||||
"<html>3. edit with better icons and red text</html>",
|
||||
]);
|
||||
// describe("History Utils", () => {
|
||||
// test("should correctly extract the history tree", () => {
|
||||
// expect(extractHistoryTree(basicLinearHistory, 2)).toEqual([
|
||||
// "<html>1. create</html>",
|
||||
// "use better icons",
|
||||
// "<html>2. edit with better icons</html>",
|
||||
// "make text red",
|
||||
// "<html>3. edit with better icons and red text</html>",
|
||||
// ]);
|
||||
|
||||
expect(extractHistoryTree(basicLinearHistory, 0)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
]);
|
||||
// expect(extractHistoryTree(basicLinearHistory, 0)).toEqual([
|
||||
// "<html>1. create</html>",
|
||||
// ]);
|
||||
|
||||
// Test branching
|
||||
expect(extractHistoryTree(basicBranchingHistory, 3)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text green",
|
||||
"<html>4. edit with better icons and green text</html>",
|
||||
]);
|
||||
// // Test branching
|
||||
// expect(extractHistoryTree(basicBranchingHistory, 3)).toEqual([
|
||||
// "<html>1. create</html>",
|
||||
// "use better icons",
|
||||
// "<html>2. edit with better icons</html>",
|
||||
// "make text green",
|
||||
// "<html>4. edit with better icons and green text</html>",
|
||||
// ]);
|
||||
|
||||
expect(extractHistoryTree(longerBranchingHistory, 4)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text green",
|
||||
"<html>4. edit with better icons and green text</html>",
|
||||
"make text bold",
|
||||
"<html>5. edit with better icons and green, bold text</html>",
|
||||
]);
|
||||
// expect(extractHistoryTree(longerBranchingHistory, 4)).toEqual([
|
||||
// "<html>1. create</html>",
|
||||
// "use better icons",
|
||||
// "<html>2. edit with better icons</html>",
|
||||
// "make text green",
|
||||
// "<html>4. edit with better icons and green text</html>",
|
||||
// "make text bold",
|
||||
// "<html>5. edit with better icons and green, bold text</html>",
|
||||
// ]);
|
||||
|
||||
expect(extractHistoryTree(longerBranchingHistory, 2)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text red",
|
||||
"<html>3. edit with better icons and red text</html>",
|
||||
]);
|
||||
// expect(extractHistoryTree(longerBranchingHistory, 2)).toEqual([
|
||||
// "<html>1. create</html>",
|
||||
// "use better icons",
|
||||
// "<html>2. edit with better icons</html>",
|
||||
// "make text red",
|
||||
// "<html>3. edit with better icons and red text</html>",
|
||||
// ]);
|
||||
|
||||
// Errors
|
||||
// // Errors
|
||||
|
||||
// Bad index
|
||||
expect(() => extractHistoryTree(basicLinearHistory, 100)).toThrow();
|
||||
expect(() => extractHistoryTree(basicLinearHistory, -2)).toThrow();
|
||||
// // Bad index
|
||||
// expect(() => extractHistoryTree(basicLinearHistory, 100)).toThrow();
|
||||
// expect(() => extractHistoryTree(basicLinearHistory, -2)).toThrow();
|
||||
|
||||
// Bad tree
|
||||
expect(() => extractHistoryTree(basicBadHistory, 1)).toThrow();
|
||||
});
|
||||
// // Bad tree
|
||||
// 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",
|
||||
},
|
||||
]);
|
||||
// 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",
|
||||
},
|
||||
]);
|
||||
// // 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 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",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
// // 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,33 +1,29 @@
|
||||
import {
|
||||
History,
|
||||
HistoryItem,
|
||||
HistoryItemType,
|
||||
RenderedHistoryItem,
|
||||
} from "./history_types";
|
||||
import { Commit, CommitHash } from "./history_types";
|
||||
|
||||
export function extractHistoryTree(
|
||||
history: History,
|
||||
version: number
|
||||
export function extractHistory(
|
||||
hash: CommitHash,
|
||||
commits: Record<CommitHash, Commit>
|
||||
): string[] {
|
||||
const flatHistory: string[] = [];
|
||||
|
||||
let currentIndex: number | null = version;
|
||||
while (currentIndex !== null) {
|
||||
const item: HistoryItem = history[currentIndex];
|
||||
let currentCommitHash: CommitHash | null = hash;
|
||||
while (currentCommitHash !== null) {
|
||||
const commit: Commit = commits[currentCommitHash];
|
||||
|
||||
if (item) {
|
||||
if (item.type === "ai_create") {
|
||||
if (commit) {
|
||||
if (commit.type === "ai_create") {
|
||||
// Don't include the image for ai_create
|
||||
flatHistory.unshift(item.code);
|
||||
} else if (item.type === "ai_edit") {
|
||||
flatHistory.unshift(item.code);
|
||||
flatHistory.unshift(item.inputs.prompt);
|
||||
} else if (item.type === "code_create") {
|
||||
flatHistory.unshift(item.code);
|
||||
flatHistory.unshift(commit.variants[commit.selectedVariantIndex].code);
|
||||
} else if (commit.type === "ai_edit") {
|
||||
flatHistory.unshift(commit.variants[commit.selectedVariantIndex].code);
|
||||
flatHistory.unshift(commit.inputs.prompt);
|
||||
}
|
||||
// } else if (item.type === "code_create") {
|
||||
// flatHistory.unshift(item.code);
|
||||
// }
|
||||
|
||||
// Move to the parent of the current item
|
||||
currentIndex = item.parentIndex;
|
||||
currentCommitHash = commit.parentHash;
|
||||
} else {
|
||||
throw new Error("Malformed history: missing parent index");
|
||||
}
|
||||
@ -36,61 +32,16 @@ export function extractHistoryTree(
|
||||
return flatHistory;
|
||||
}
|
||||
|
||||
function displayHistoryItemType(itemType: HistoryItemType) {
|
||||
switch (itemType) {
|
||||
export function summarizeHistoryItem(commit: Commit) {
|
||||
const commitType = commit.type;
|
||||
switch (commitType) {
|
||||
case "ai_create":
|
||||
return "Create";
|
||||
case "ai_edit":
|
||||
return "Edit";
|
||||
case "code_create":
|
||||
return "Imported from code";
|
||||
return commit.inputs.prompt;
|
||||
default: {
|
||||
const exhaustiveCheck: never = itemType;
|
||||
const exhaustiveCheck: never = commitType;
|
||||
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;
|
||||
};
|
||||
|
||||
@ -23,12 +23,17 @@ interface Props {
|
||||
|
||||
function PreviewPane({ doUpdate, reset, settings }: Props) {
|
||||
const { appState } = useAppStore();
|
||||
const { inputMode, generatedCode, setGeneratedCode } = useProjectStore();
|
||||
const { inputMode, head, commits } = useProjectStore();
|
||||
|
||||
const currentCommit = head && commits[head] ? commits[head] : "";
|
||||
const currentCode = currentCommit
|
||||
? currentCommit.variants[currentCommit.selectedVariantIndex].code
|
||||
: "";
|
||||
|
||||
const previewCode =
|
||||
inputMode === "video" && appState === AppState.CODING
|
||||
? extractHtml(generatedCode)
|
||||
: generatedCode;
|
||||
? extractHtml(currentCode)
|
||||
: currentCode;
|
||||
|
||||
return (
|
||||
<div className="ml-4">
|
||||
@ -45,7 +50,7 @@ function PreviewPane({ doUpdate, reset, settings }: Props) {
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => downloadCode(generatedCode)}
|
||||
onClick={() => downloadCode(previewCode)}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-x-2 mr-4 dark:text-white dark:bg-gray-700 download-btn"
|
||||
>
|
||||
@ -86,7 +91,8 @@ function PreviewPane({ doUpdate, reset, settings }: Props) {
|
||||
<TabsContent value="code">
|
||||
<CodeTab
|
||||
code={previewCode}
|
||||
setCode={setGeneratedCode}
|
||||
// TODO*
|
||||
setCode={() => {}}
|
||||
settings={settings}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@ -37,15 +37,16 @@ function Sidebar({
|
||||
setShouldIncludeResultImage,
|
||||
} = useAppStore();
|
||||
|
||||
const {
|
||||
inputMode,
|
||||
generatedCode,
|
||||
referenceImages,
|
||||
executionConsoles,
|
||||
currentVariantIndex,
|
||||
} = useProjectStore();
|
||||
const { inputMode, referenceImages, executionConsoles, head, commits } =
|
||||
useProjectStore();
|
||||
|
||||
const executionConsole = executionConsoles[currentVariantIndex] || [];
|
||||
const viewedCode =
|
||||
head && commits[head]
|
||||
? commits[head].variants[commits[head].selectedVariantIndex].code
|
||||
: "";
|
||||
|
||||
const executionConsole =
|
||||
(head && executionConsoles[commits[head].selectedVariantIndex]) || [];
|
||||
|
||||
// When coding is complete, focus on the update instruction textarea
|
||||
useEffect(() => {
|
||||
@ -77,7 +78,7 @@ function Sidebar({
|
||||
{executionConsole.slice(-1)[0]}
|
||||
</div>
|
||||
|
||||
<CodePreview code={generatedCode} />
|
||||
<CodePreview code={viewedCode} />
|
||||
|
||||
<div className="flex w-full">
|
||||
<Button
|
||||
|
||||
@ -1,45 +1,16 @@
|
||||
import { useProjectStore } from "../../store/project-store";
|
||||
|
||||
function Variants() {
|
||||
const {
|
||||
// Inputs
|
||||
referenceImages,
|
||||
const { head, commits, updateSelectedVariantIndex } = useProjectStore();
|
||||
|
||||
// Outputs
|
||||
variants,
|
||||
currentVariantIndex,
|
||||
setCurrentVariantIndex,
|
||||
setGeneratedCode,
|
||||
appHistory,
|
||||
setAppHistory,
|
||||
} = useProjectStore();
|
||||
|
||||
function switchVariant(index: number) {
|
||||
const variant = variants[index];
|
||||
setCurrentVariantIndex(index);
|
||||
setGeneratedCode(variant);
|
||||
if (appHistory.length === 1) {
|
||||
setAppHistory([
|
||||
{
|
||||
type: "ai_create",
|
||||
parentIndex: null,
|
||||
code: variant,
|
||||
inputs: { image_url: referenceImages[0] },
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
setAppHistory((prev) => {
|
||||
const newHistory = [...prev];
|
||||
newHistory[newHistory.length - 1].code = variant;
|
||||
return newHistory;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (variants.length === 0) {
|
||||
// TODO: Is HEAD null right? And check variants.length === 0 ||
|
||||
if (head === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const variants = commits[head || ""].variants;
|
||||
const selectedVariantIndex = commits[head || ""].selectedVariantIndex;
|
||||
|
||||
return (
|
||||
<div className="mt-4 mb-4">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@ -47,11 +18,11 @@ function Variants() {
|
||||
<div
|
||||
key={index}
|
||||
className={`p-2 border rounded-md cursor-pointer ${
|
||||
index === currentVariantIndex
|
||||
index === selectedVariantIndex
|
||||
? "bg-blue-100 dark:bg-blue-900"
|
||||
: "bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
}`}
|
||||
onClick={() => switchVariant(index)}
|
||||
onClick={() => updateSelectedVariantIndex(head, index)}
|
||||
>
|
||||
<h3 className="font-medium mb-1">Option {index + 1}</h3>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { create } from "zustand";
|
||||
import { History } from "../components/history/history_types";
|
||||
import { Commit, CommitHash } from "../components/history/history_types";
|
||||
|
||||
// Store for app-wide state
|
||||
interface ProjectStore {
|
||||
@ -11,32 +11,26 @@ interface ProjectStore {
|
||||
referenceImages: string[];
|
||||
setReferenceImages: (images: string[]) => void;
|
||||
|
||||
// Outputs and other state
|
||||
generatedCode: string;
|
||||
setGeneratedCode: (
|
||||
updater: string | ((currentCode: string) => string)
|
||||
) => void;
|
||||
// Outputs
|
||||
commits: Record<string, Commit>;
|
||||
head: CommitHash | null;
|
||||
|
||||
variants: string[];
|
||||
currentVariantIndex: number;
|
||||
setCurrentVariantIndex: (index: number) => void;
|
||||
setVariant: (code: string, index: number) => void;
|
||||
appendToVariant: (newTokens: string, index: number) => void;
|
||||
resetVariants: () => void;
|
||||
addCommit: (commit: Commit) => void;
|
||||
removeCommit: (hash: CommitHash) => void;
|
||||
|
||||
setHead: (hash: CommitHash) => void;
|
||||
appendCommitCode: (
|
||||
hash: CommitHash,
|
||||
numVariant: number,
|
||||
code: string
|
||||
) => void;
|
||||
setCommitCode: (hash: CommitHash, numVariant: number, code: string) => void;
|
||||
updateSelectedVariantIndex: (hash: CommitHash, index: number) => void;
|
||||
resetCommits: () => void;
|
||||
|
||||
executionConsoles: { [key: number]: string[] };
|
||||
appendExecutionConsole: (variantIndex: number, line: string) => void;
|
||||
resetExecutionConsoles: () => void;
|
||||
|
||||
// Tracks the currently shown version from app history
|
||||
// TODO: might want to move to appStore
|
||||
currentVersion: number | null;
|
||||
setCurrentVersion: (version: number | null) => void;
|
||||
|
||||
appHistory: History;
|
||||
setAppHistory: (
|
||||
updater: History | ((currentHistory: History) => History)
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const useProjectStore = create<ProjectStore>((set) => ({
|
||||
@ -48,39 +42,64 @@ export const useProjectStore = create<ProjectStore>((set) => ({
|
||||
referenceImages: [],
|
||||
setReferenceImages: (images) => set({ referenceImages: images }),
|
||||
|
||||
// Outputs and other state
|
||||
generatedCode: "",
|
||||
setGeneratedCode: (updater) =>
|
||||
// Outputs
|
||||
commits: {},
|
||||
head: null,
|
||||
|
||||
addCommit: (commit: Commit) => {
|
||||
set((state) => ({
|
||||
generatedCode:
|
||||
typeof updater === "function" ? updater(state.generatedCode) : updater,
|
||||
commits: { ...state.commits, [commit.hash]: commit },
|
||||
}));
|
||||
},
|
||||
removeCommit: (hash: CommitHash) => {
|
||||
set((state) => {
|
||||
const newCommits = { ...state.commits };
|
||||
delete newCommits[hash];
|
||||
return { commits: newCommits };
|
||||
});
|
||||
},
|
||||
|
||||
setHead: (hash: CommitHash) => set({ head: hash }),
|
||||
appendCommitCode: (hash: CommitHash, numVariant: number, code: string) =>
|
||||
set((state) => ({
|
||||
commits: {
|
||||
...state.commits,
|
||||
[hash]: {
|
||||
...state.commits[hash],
|
||||
variants: state.commits[hash].variants.map((variant, index) =>
|
||||
index === numVariant
|
||||
? { ...variant, code: variant.code + code }
|
||||
: variant
|
||||
),
|
||||
},
|
||||
},
|
||||
})),
|
||||
setCommitCode: (hash: CommitHash, numVariant: number, code: string) =>
|
||||
set((state) => ({
|
||||
commits: {
|
||||
...state.commits,
|
||||
[hash]: {
|
||||
...state.commits[hash],
|
||||
variants: state.commits[hash].variants.map((variant, index) =>
|
||||
index === numVariant ? { ...variant, code } : variant
|
||||
),
|
||||
},
|
||||
},
|
||||
})),
|
||||
updateSelectedVariantIndex: (hash: CommitHash, index: number) =>
|
||||
set((state) => ({
|
||||
commits: {
|
||||
...state.commits,
|
||||
[hash]: {
|
||||
...state.commits[hash],
|
||||
selectedVariantIndex: index,
|
||||
},
|
||||
},
|
||||
})),
|
||||
resetCommits: () => set({ commits: {}, head: null }),
|
||||
// TODO: Reset heads
|
||||
|
||||
currentVariantIndex: 0,
|
||||
variants: [],
|
||||
executionConsoles: {},
|
||||
|
||||
setCurrentVariantIndex: (index) => set({ currentVariantIndex: index }),
|
||||
setVariant: (code: string, index: number) =>
|
||||
set((state) => {
|
||||
const newVariants = [...state.variants];
|
||||
while (newVariants.length <= index) {
|
||||
newVariants.push("");
|
||||
}
|
||||
newVariants[index] = code;
|
||||
return { variants: newVariants };
|
||||
}),
|
||||
appendToVariant: (newTokens: string, index: number) =>
|
||||
set((state) => {
|
||||
const newVariants = [...state.variants];
|
||||
while (newVariants.length <= index) {
|
||||
newVariants.push("");
|
||||
}
|
||||
newVariants[index] += newTokens;
|
||||
return { variants: newVariants };
|
||||
}),
|
||||
resetVariants: () => set({ variants: [], currentVariantIndex: 0 }),
|
||||
|
||||
appendExecutionConsole: (variantIndex: number, line: string) =>
|
||||
set((state) => ({
|
||||
executionConsoles: {
|
||||
@ -92,13 +111,4 @@ export const useProjectStore = create<ProjectStore>((set) => ({
|
||||
},
|
||||
})),
|
||||
resetExecutionConsoles: () => set({ executionConsoles: {} }),
|
||||
|
||||
currentVersion: null,
|
||||
setCurrentVersion: (version) => set({ currentVersion: version }),
|
||||
appHistory: [],
|
||||
setAppHistory: (updater) =>
|
||||
set((state) => ({
|
||||
appHistory:
|
||||
typeof updater === "function" ? updater(state.appHistory) : updater,
|
||||
})),
|
||||
}));
|
||||
|
||||
@ -4441,6 +4441,11 @@ nanoid@^3.3.6, nanoid@^3.3.7:
|
||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz"
|
||||
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||
|
||||
nanoid@^5.0.7:
|
||||
version "5.0.7"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6"
|
||||
integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user