Spaces:
Running
Running
add a way to create space in a follow up request
Browse files- app/api/ask/route.ts +47 -6
- components/editor/ask-ai/index.tsx +3 -7
- components/editor/index.tsx +15 -6
- components/editor/preview/index.tsx +2 -1
- hooks/useAi.ts +21 -14
- hooks/useEditor.ts +8 -2
- lib/prompts.ts +16 -12
app/api/ask/route.ts
CHANGED
|
@@ -16,13 +16,15 @@ import {
|
|
| 16 |
SEARCH_START,
|
| 17 |
UPDATE_PAGE_START,
|
| 18 |
UPDATE_PAGE_END,
|
|
|
|
| 19 |
} from "@/lib/prompts";
|
| 20 |
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 21 |
import { Page } from "@/types";
|
| 22 |
-
import { uploadFiles } from "@huggingface/hub";
|
| 23 |
import { isAuthenticated } from "@/lib/auth";
|
| 24 |
import { getBestProvider } from "@/lib/best-provider";
|
| 25 |
import { rewritePrompt } from "@/lib/rewrite-prompt";
|
|
|
|
| 26 |
|
| 27 |
const ipAddresses = new Map();
|
| 28 |
|
|
@@ -31,7 +33,7 @@ export async function POST(request: NextRequest) {
|
|
| 31 |
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 32 |
|
| 33 |
const body = await request.json();
|
| 34 |
-
const { prompt, provider, model, redesignMarkdown, enhancedSettings } = body;
|
| 35 |
|
| 36 |
if (!model || (!prompt && !redesignMarkdown)) {
|
| 37 |
return NextResponse.json(
|
|
@@ -131,7 +133,7 @@ export async function POST(request: NextRequest) {
|
|
| 131 |
},
|
| 132 |
{
|
| 133 |
role: "user",
|
| 134 |
-
content: `${rewrittenPrompt}${redesignMarkdown ? `\n\nHere is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown. Use the images in the markdown.` : ""}`
|
| 135 |
},
|
| 136 |
],
|
| 137 |
max_tokens: selectedProvider.max_tokens,
|
|
@@ -220,10 +222,12 @@ export async function PUT(request: NextRequest) {
|
|
| 220 |
const authHeaders = await headers();
|
| 221 |
|
| 222 |
const body = await request.json();
|
| 223 |
-
const { prompt, previousPrompts, provider, selectedElementHtml, model, pages, files, repoId } =
|
| 224 |
body;
|
| 225 |
|
| 226 |
-
|
|
|
|
|
|
|
| 227 |
return NextResponse.json(
|
| 228 |
{ ok: false, error: "Missing required fields" },
|
| 229 |
{ status: 400 }
|
|
@@ -300,7 +304,7 @@ export async function PUT(request: NextRequest) {
|
|
| 300 |
messages: [
|
| 301 |
{
|
| 302 |
role: "system",
|
| 303 |
-
content: FOLLOW_UP_SYSTEM_PROMPT,
|
| 304 |
},
|
| 305 |
{
|
| 306 |
role: "user",
|
|
@@ -514,6 +518,42 @@ export async function PUT(request: NextRequest) {
|
|
| 514 |
files.push(file);
|
| 515 |
});
|
| 516 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
const response = await uploadFiles({
|
| 518 |
repo: {
|
| 519 |
type: "space",
|
|
@@ -528,6 +568,7 @@ export async function PUT(request: NextRequest) {
|
|
| 528 |
ok: true,
|
| 529 |
updatedLines,
|
| 530 |
pages: updatedPages,
|
|
|
|
| 531 |
commit: {
|
| 532 |
...response.commit,
|
| 533 |
title: prompt,
|
|
|
|
| 16 |
SEARCH_START,
|
| 17 |
UPDATE_PAGE_START,
|
| 18 |
UPDATE_PAGE_END,
|
| 19 |
+
PROMPT_FOR_PROJECT_NAME,
|
| 20 |
} from "@/lib/prompts";
|
| 21 |
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 22 |
import { Page } from "@/types";
|
| 23 |
+
import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
|
| 24 |
import { isAuthenticated } from "@/lib/auth";
|
| 25 |
import { getBestProvider } from "@/lib/best-provider";
|
| 26 |
import { rewritePrompt } from "@/lib/rewrite-prompt";
|
| 27 |
+
import { COLORS } from "@/lib/utils";
|
| 28 |
|
| 29 |
const ipAddresses = new Map();
|
| 30 |
|
|
|
|
| 33 |
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 34 |
|
| 35 |
const body = await request.json();
|
| 36 |
+
const { prompt, provider, model, redesignMarkdown, enhancedSettings, pages } = body;
|
| 37 |
|
| 38 |
if (!model || (!prompt && !redesignMarkdown)) {
|
| 39 |
return NextResponse.json(
|
|
|
|
| 133 |
},
|
| 134 |
{
|
| 135 |
role: "user",
|
| 136 |
+
content: `${rewrittenPrompt}${redesignMarkdown ? `\n\nHere is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown. Use the images in the markdown.` : ""} : ""}`
|
| 137 |
},
|
| 138 |
],
|
| 139 |
max_tokens: selectedProvider.max_tokens,
|
|
|
|
| 222 |
const authHeaders = await headers();
|
| 223 |
|
| 224 |
const body = await request.json();
|
| 225 |
+
const { prompt, previousPrompts, provider, selectedElementHtml, model, pages, files, repoId: repoIdFromBody, isNew, enhancedSettings } =
|
| 226 |
body;
|
| 227 |
|
| 228 |
+
let repoId = repoIdFromBody;
|
| 229 |
+
|
| 230 |
+
if (!prompt || pages.length === 0) {
|
| 231 |
return NextResponse.json(
|
| 232 |
{ ok: false, error: "Missing required fields" },
|
| 233 |
{ status: 400 }
|
|
|
|
| 304 |
messages: [
|
| 305 |
{
|
| 306 |
role: "system",
|
| 307 |
+
content: FOLLOW_UP_SYSTEM_PROMPT + (isNew ? PROMPT_FOR_PROJECT_NAME : ""),
|
| 308 |
},
|
| 309 |
{
|
| 310 |
role: "user",
|
|
|
|
| 518 |
files.push(file);
|
| 519 |
});
|
| 520 |
|
| 521 |
+
if (isNew) {
|
| 522 |
+
const projectName = chunk.match(/<<<<<<< PROJECT_NAME_START ([\s\S]*?) >>>>>>> PROJECT_NAME_END/)?.[1]?.trim();
|
| 523 |
+
const formattedTitle = projectName?.toLowerCase()
|
| 524 |
+
.replace(/[^a-z0-9]+/g, "-")
|
| 525 |
+
.split("-")
|
| 526 |
+
.filter(Boolean)
|
| 527 |
+
.join("-")
|
| 528 |
+
.slice(0, 96);
|
| 529 |
+
const repo: RepoDesignation = {
|
| 530 |
+
type: "space",
|
| 531 |
+
name: `${user.name}/${formattedTitle}`,
|
| 532 |
+
};
|
| 533 |
+
const { repoUrl} = await createRepo({
|
| 534 |
+
repo,
|
| 535 |
+
accessToken: user.token as string,
|
| 536 |
+
});
|
| 537 |
+
repoId = repoUrl.split("/").slice(-2).join("/");
|
| 538 |
+
const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 539 |
+
const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 540 |
+
const README = `---
|
| 541 |
+
title: ${projectName}
|
| 542 |
+
colorFrom: ${colorFrom}
|
| 543 |
+
colorTo: ${colorTo}
|
| 544 |
+
emoji: 🐳
|
| 545 |
+
sdk: static
|
| 546 |
+
pinned: false
|
| 547 |
+
tags:
|
| 548 |
+
- deepsite-v3
|
| 549 |
+
---
|
| 550 |
+
|
| 551 |
+
# Welcome to your new DeepSite project!
|
| 552 |
+
This project was created with [DeepSite](https://deepsite.hf.co).
|
| 553 |
+
`;
|
| 554 |
+
files.push(new File([README], "README.md", { type: "text/markdown" }));
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
const response = await uploadFiles({
|
| 558 |
repo: {
|
| 559 |
type: "space",
|
|
|
|
| 568 |
ok: true,
|
| 569 |
updatedLines,
|
| 570 |
pages: updatedPages,
|
| 571 |
+
repoId,
|
| 572 |
commit: {
|
| 573 |
...response.commit,
|
| 574 |
title: prompt,
|
components/editor/ask-ai/index.tsx
CHANGED
|
@@ -41,7 +41,7 @@ export const AskAi = ({
|
|
| 41 |
onScrollToBottom?: () => void;
|
| 42 |
}) => {
|
| 43 |
const { user, projects } = useUser();
|
| 44 |
-
const {
|
| 45 |
const {
|
| 46 |
isAiWorking,
|
| 47 |
isThinking,
|
|
@@ -75,10 +75,6 @@ export const AskAi = ({
|
|
| 75 |
const [think, setThink] = useState("");
|
| 76 |
const [openThink, setOpenThink] = useState(false);
|
| 77 |
|
| 78 |
-
const isSameHtml = useMemo(() => {
|
| 79 |
-
return isTheSameHtml(currentPageData.html);
|
| 80 |
-
}, [currentPageData.html]);
|
| 81 |
-
|
| 82 |
const handleThink = (think: string) => {
|
| 83 |
setThink(think);
|
| 84 |
setIsThinking(true);
|
|
@@ -93,7 +89,7 @@ export const AskAi = ({
|
|
| 93 |
if (!redesignMarkdown && !prompt.trim()) return;
|
| 94 |
|
| 95 |
if (isFollowUp && !redesignMarkdown && !isSameHtml) {
|
| 96 |
-
const result = await callAiFollowUp(prompt, enhancedSettings);
|
| 97 |
|
| 98 |
if (result?.error) {
|
| 99 |
handleError(result.error, result.message);
|
|
@@ -223,7 +219,7 @@ export const AskAi = ({
|
|
| 223 |
/>
|
| 224 |
</div>
|
| 225 |
<div className="flex items-center justify-between gap-2 px-4 pb-3 mt-2">
|
| 226 |
-
<div className="flex-1 flex items-center justify-start gap-1.5">
|
| 227 |
<PromptBuilder
|
| 228 |
enhancedSettings={enhancedSettings!}
|
| 229 |
setEnhancedSettings={setEnhancedSettings}
|
|
|
|
| 41 |
onScrollToBottom?: () => void;
|
| 42 |
}) => {
|
| 43 |
const { user, projects } = useUser();
|
| 44 |
+
const { isSameHtml, isUploading, pages, isLoadingProject } = useEditor();
|
| 45 |
const {
|
| 46 |
isAiWorking,
|
| 47 |
isThinking,
|
|
|
|
| 75 |
const [think, setThink] = useState("");
|
| 76 |
const [openThink, setOpenThink] = useState(false);
|
| 77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
const handleThink = (think: string) => {
|
| 79 |
setThink(think);
|
| 80 |
setIsThinking(true);
|
|
|
|
| 89 |
if (!redesignMarkdown && !prompt.trim()) return;
|
| 90 |
|
| 91 |
if (isFollowUp && !redesignMarkdown && !isSameHtml) {
|
| 92 |
+
const result = await callAiFollowUp(prompt, enhancedSettings, isNew);
|
| 93 |
|
| 94 |
if (result?.error) {
|
| 95 |
handleError(result.error, result.message);
|
|
|
|
| 219 |
/>
|
| 220 |
</div>
|
| 221 |
<div className="flex items-center justify-between gap-2 px-4 pb-3 mt-2">
|
| 222 |
+
<div className="flex-1 flex items-center justify-start gap-1.5 flex-wrap">
|
| 223 |
<PromptBuilder
|
| 224 |
enhancedSettings={enhancedSettings!}
|
| 225 |
setEnhancedSettings={setEnhancedSettings}
|
components/editor/index.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
import { useMemo, useRef, useState, useEffect } from "react";
|
| 3 |
-
import { useCopyToClipboard } from "react-use";
|
| 4 |
import { CopyIcon } from "lucide-react";
|
| 5 |
import { toast } from "sonner";
|
| 6 |
import classNames from "classnames";
|
|
@@ -10,6 +10,7 @@ import Editor from "@monaco-editor/react";
|
|
| 10 |
import { useEditor } from "@/hooks/useEditor";
|
| 11 |
import { Header } from "@/components/editor/header";
|
| 12 |
import { useAi } from "@/hooks/useAi";
|
|
|
|
| 13 |
|
| 14 |
import { ListPages } from "./pages";
|
| 15 |
import { AskAi } from "./ask-ai";
|
|
@@ -47,7 +48,17 @@ export const AppEditor = ({
|
|
| 47 |
const editor = useRef<HTMLDivElement>(null);
|
| 48 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 49 |
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
useEffect(() => {
|
| 52 |
if (hasUnsavedChanges && !isAiWorking) {
|
| 53 |
setShowSavePopup(true);
|
|
@@ -92,11 +103,9 @@ export const AppEditor = ({
|
|
| 92 |
horizontal: "hidden",
|
| 93 |
},
|
| 94 |
wordWrap: "on",
|
| 95 |
-
readOnly: !!isAiWorking || !!currentCommit
|
| 96 |
readOnlyMessage: {
|
| 97 |
-
value:
|
| 98 |
-
? "You can't edit the code, as this is a new project. Ask DeepSite first."
|
| 99 |
-
: currentCommit
|
| 100 |
? "You can't edit the code, as this is an old version of the project."
|
| 101 |
: "Wait for DeepSite to finish working...",
|
| 102 |
isTrusted: true,
|
|
|
|
| 1 |
"use client";
|
| 2 |
import { useMemo, useRef, useState, useEffect } from "react";
|
| 3 |
+
import { useCopyToClipboard, useMount } from "react-use";
|
| 4 |
import { CopyIcon } from "lucide-react";
|
| 5 |
import { toast } from "sonner";
|
| 6 |
import classNames from "classnames";
|
|
|
|
| 10 |
import { useEditor } from "@/hooks/useEditor";
|
| 11 |
import { Header } from "@/components/editor/header";
|
| 12 |
import { useAi } from "@/hooks/useAi";
|
| 13 |
+
import { defaultHTML } from "@/lib/consts";
|
| 14 |
|
| 15 |
import { ListPages } from "./pages";
|
| 16 |
import { AskAi } from "./ask-ai";
|
|
|
|
| 48 |
const editor = useRef<HTMLDivElement>(null);
|
| 49 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 50 |
|
| 51 |
+
useMount(() => {
|
| 52 |
+
if (isNew) {
|
| 53 |
+
setPages([
|
| 54 |
+
{
|
| 55 |
+
path: "index.html",
|
| 56 |
+
html: defaultHTML,
|
| 57 |
+
},
|
| 58 |
+
]);
|
| 59 |
+
}
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
useEffect(() => {
|
| 63 |
if (hasUnsavedChanges && !isAiWorking) {
|
| 64 |
setShowSavePopup(true);
|
|
|
|
| 103 |
horizontal: "hidden",
|
| 104 |
},
|
| 105 |
wordWrap: "on",
|
| 106 |
+
readOnly: !!isAiWorking || !!currentCommit,
|
| 107 |
readOnlyMessage: {
|
| 108 |
+
value: currentCommit
|
|
|
|
|
|
|
| 109 |
? "You can't edit the code, as this is an old version of the project."
|
| 110 |
: "Wait for DeepSite to finish working...",
|
| 111 |
isTrusted: true,
|
components/editor/preview/index.tsx
CHANGED
|
@@ -33,6 +33,7 @@ export const Preview = forwardRef<LivePreviewRef, { isNew: boolean }>(
|
|
| 33 |
pages,
|
| 34 |
setPages,
|
| 35 |
setCurrentPage,
|
|
|
|
| 36 |
} = useEditor();
|
| 37 |
const {
|
| 38 |
isEditableModeEnabled,
|
|
@@ -239,7 +240,7 @@ export const Preview = forwardRef<LivePreviewRef, { isNew: boolean }>(
|
|
| 239 |
</span>
|
| 240 |
</div>
|
| 241 |
)}
|
| 242 |
-
{isNew && !isLoadingProject && !globalAiLoading ? (
|
| 243 |
<iframe
|
| 244 |
className={classNames(
|
| 245 |
"w-full select-none transition-all duration-200 bg-black h-full",
|
|
|
|
| 33 |
pages,
|
| 34 |
setPages,
|
| 35 |
setCurrentPage,
|
| 36 |
+
isSameHtml,
|
| 37 |
} = useEditor();
|
| 38 |
const {
|
| 39 |
isEditableModeEnabled,
|
|
|
|
| 240 |
</span>
|
| 241 |
</div>
|
| 242 |
)}
|
| 243 |
+
{isNew && !isLoadingProject && !globalAiLoading && isSameHtml ? (
|
| 244 |
<iframe
|
| 245 |
className={classNames(
|
| 246 |
"w-full select-none transition-all duration-200 bg-black h-full",
|
hooks/useAi.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { LivePreviewRef } from "@/components/editor/live-preview";
|
|
| 14 |
export const useAi = (onScrollToBottom?: () => void, livePreviewRef?: React.RefObject<LivePreviewRef | null>) => {
|
| 15 |
const client = useQueryClient();
|
| 16 |
const audio = useRef<HTMLAudioElement | null>(null);
|
| 17 |
-
const { setPages, setCurrentPage, setPrompts, prompts, pages, project, setProject, commits, setCommits, setLastSavedPages } = useEditor();
|
| 18 |
const [controller, setController] = useState<AbortController | null>(null);
|
| 19 |
const [storageProvider, setStorageProvider] = useLocalStorage("provider", "auto");
|
| 20 |
const [storageModel, setStorageModel] = useLocalStorage("model", MODELS[0].value);
|
|
@@ -256,7 +256,7 @@ export const useAi = (onScrollToBottom?: () => void, livePreviewRef?: React.RefO
|
|
| 256 |
}
|
| 257 |
};
|
| 258 |
|
| 259 |
-
const callAiFollowUp = async (prompt: string, enhancedSettings?: EnhancedSettings) => {
|
| 260 |
if (isAiWorking) return;
|
| 261 |
if (!prompt.trim()) return;
|
| 262 |
|
|
@@ -266,7 +266,7 @@ export const useAi = (onScrollToBottom?: () => void, livePreviewRef?: React.RefO
|
|
| 266 |
const abortController = new AbortController();
|
| 267 |
setController(abortController);
|
| 268 |
|
| 269 |
-
try {
|
| 270 |
const request = await fetch("/api/ask", {
|
| 271 |
method: "PUT",
|
| 272 |
body: JSON.stringify({
|
|
@@ -277,7 +277,9 @@ export const useAi = (onScrollToBottom?: () => void, livePreviewRef?: React.RefO
|
|
| 277 |
pages,
|
| 278 |
selectedElementHtml: selectedElement?.outerHTML,
|
| 279 |
files: selectedFiles,
|
| 280 |
-
repoId: project?.space_id
|
|
|
|
|
|
|
| 281 |
}),
|
| 282 |
headers: {
|
| 283 |
"Content-Type": "application/json",
|
|
@@ -307,20 +309,25 @@ export const useAi = (onScrollToBottom?: () => void, livePreviewRef?: React.RefO
|
|
| 307 |
}
|
| 308 |
|
| 309 |
toast.success("AI responded successfully");
|
| 310 |
-
setIsAiWorking(false);
|
| 311 |
const iframe = document.getElementById(
|
| 312 |
"preview-iframe"
|
| 313 |
) as HTMLIFrameElement;
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
if (audio.current) audio.current.play();
|
| 325 |
if (iframe) {
|
| 326 |
setTimeout(() => {
|
|
|
|
| 14 |
export const useAi = (onScrollToBottom?: () => void, livePreviewRef?: React.RefObject<LivePreviewRef | null>) => {
|
| 15 |
const client = useQueryClient();
|
| 16 |
const audio = useRef<HTMLAudioElement | null>(null);
|
| 17 |
+
const { setPages, setCurrentPage, setPrompts, prompts, pages, project, setProject, commits, setCommits, setLastSavedPages, isSameHtml } = useEditor();
|
| 18 |
const [controller, setController] = useState<AbortController | null>(null);
|
| 19 |
const [storageProvider, setStorageProvider] = useLocalStorage("provider", "auto");
|
| 20 |
const [storageModel, setStorageModel] = useLocalStorage("model", MODELS[0].value);
|
|
|
|
| 256 |
}
|
| 257 |
};
|
| 258 |
|
| 259 |
+
const callAiFollowUp = async (prompt: string, enhancedSettings?: EnhancedSettings, isNew?: boolean) => {
|
| 260 |
if (isAiWorking) return;
|
| 261 |
if (!prompt.trim()) return;
|
| 262 |
|
|
|
|
| 266 |
const abortController = new AbortController();
|
| 267 |
setController(abortController);
|
| 268 |
|
| 269 |
+
try {
|
| 270 |
const request = await fetch("/api/ask", {
|
| 271 |
method: "PUT",
|
| 272 |
body: JSON.stringify({
|
|
|
|
| 277 |
pages,
|
| 278 |
selectedElementHtml: selectedElement?.outerHTML,
|
| 279 |
files: selectedFiles,
|
| 280 |
+
repoId: project?.space_id,
|
| 281 |
+
isNew,
|
| 282 |
+
enhancedSettings,
|
| 283 |
}),
|
| 284 |
headers: {
|
| 285 |
"Content-Type": "application/json",
|
|
|
|
| 309 |
}
|
| 310 |
|
| 311 |
toast.success("AI responded successfully");
|
|
|
|
| 312 |
const iframe = document.getElementById(
|
| 313 |
"preview-iframe"
|
| 314 |
) as HTMLIFrameElement;
|
| 315 |
|
| 316 |
+
if (isNew && res.repoId) {
|
| 317 |
+
router.push(`/projects/${res.repoId}`);
|
| 318 |
+
setIsAiWorking(false);
|
| 319 |
+
} else {
|
| 320 |
+
setPages(res.pages);
|
| 321 |
+
setLastSavedPages([...res.pages]); // Mark AI changes as saved
|
| 322 |
+
setCommits([res.commit, ...commits]);
|
| 323 |
+
setPrompts(
|
| 324 |
+
[...prompts, prompt]
|
| 325 |
+
)
|
| 326 |
+
setSelectedElement(null);
|
| 327 |
+
setSelectedFiles([]);
|
| 328 |
+
setIsEditableModeEnabled(false);
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
if (audio.current) audio.current.play();
|
| 332 |
if (iframe) {
|
| 333 |
setTimeout(() => {
|
hooks/useEditor.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { useRouter } from "next/navigation";
|
|
| 7 |
import { defaultHTML } from "@/lib/consts";
|
| 8 |
import { Commit, HtmlHistory, Page, Project } from "@/types";
|
| 9 |
import { api } from "@/lib/api";
|
|
|
|
| 10 |
|
| 11 |
export const useEditor = (namespace?: string, repoId?: string) => {
|
| 12 |
const client = useQueryClient();
|
|
@@ -60,7 +61,7 @@ export const useEditor = (namespace?: string, repoId?: string) => {
|
|
| 60 |
const { data: pages = [] } = useQuery<Page[]>({
|
| 61 |
queryKey: ["editor.pages"],
|
| 62 |
queryFn: async (): Promise<Page[]> => {
|
| 63 |
-
return
|
| 64 |
{
|
| 65 |
path: "index.html",
|
| 66 |
html: defaultHTML,
|
|
@@ -71,7 +72,7 @@ export const useEditor = (namespace?: string, repoId?: string) => {
|
|
| 71 |
refetchOnReconnect: false,
|
| 72 |
refetchOnMount: false,
|
| 73 |
retry: false,
|
| 74 |
-
initialData:
|
| 75 |
{
|
| 76 |
path: "index.html",
|
| 77 |
html: defaultHTML,
|
|
@@ -319,6 +320,10 @@ export const useEditor = (namespace?: string, repoId?: string) => {
|
|
| 319 |
}
|
| 320 |
}, [namespace, repoId])
|
| 321 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
return {
|
| 323 |
isLoadingProject,
|
| 324 |
project,
|
|
@@ -341,6 +346,7 @@ export const useEditor = (namespace?: string, repoId?: string) => {
|
|
| 341 |
currentCommit,
|
| 342 |
setCurrentCommit,
|
| 343 |
setProject,
|
|
|
|
| 344 |
isUploading: uploadFilesMutation.isPending,
|
| 345 |
globalEditorLoading: uploadFilesMutation.isPending || isLoadingProject,
|
| 346 |
// Unsaved changes functionality
|
|
|
|
| 7 |
import { defaultHTML } from "@/lib/consts";
|
| 8 |
import { Commit, HtmlHistory, Page, Project } from "@/types";
|
| 9 |
import { api } from "@/lib/api";
|
| 10 |
+
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
| 11 |
|
| 12 |
export const useEditor = (namespace?: string, repoId?: string) => {
|
| 13 |
const client = useQueryClient();
|
|
|
|
| 61 |
const { data: pages = [] } = useQuery<Page[]>({
|
| 62 |
queryKey: ["editor.pages"],
|
| 63 |
queryFn: async (): Promise<Page[]> => {
|
| 64 |
+
return [
|
| 65 |
{
|
| 66 |
path: "index.html",
|
| 67 |
html: defaultHTML,
|
|
|
|
| 72 |
refetchOnReconnect: false,
|
| 73 |
refetchOnMount: false,
|
| 74 |
retry: false,
|
| 75 |
+
initialData: [
|
| 76 |
{
|
| 77 |
path: "index.html",
|
| 78 |
html: defaultHTML,
|
|
|
|
| 320 |
}
|
| 321 |
}, [namespace, repoId])
|
| 322 |
|
| 323 |
+
const isSameHtml = useMemo(() => {
|
| 324 |
+
return isTheSameHtml(currentPageData.html);
|
| 325 |
+
}, [pages]);
|
| 326 |
+
|
| 327 |
return {
|
| 328 |
isLoadingProject,
|
| 329 |
project,
|
|
|
|
| 346 |
currentCommit,
|
| 347 |
setCurrentCommit,
|
| 348 |
setProject,
|
| 349 |
+
isSameHtml,
|
| 350 |
isUploading: uploadFilesMutation.isPending,
|
| 351 |
globalEditorLoading: uploadFilesMutation.isPending || isLoadingProject,
|
| 352 |
// Unsaved changes functionality
|
lib/prompts.ts
CHANGED
|
@@ -74,21 +74,25 @@ If it's a new page, you MUST applied the following NEW_PAGE_START and UPDATE_PAG
|
|
| 74 |
${PROMPT_FOR_IMAGE_GENERATION}
|
| 75 |
Do NOT explain the changes or what you did, just return the expected results.
|
| 76 |
Update Format Rules:
|
| 77 |
-
1. Start with ${
|
| 78 |
-
2.
|
| 79 |
-
3. Close the start tag with the ${
|
| 80 |
-
4. Start with ${
|
| 81 |
-
5. Provide the
|
| 82 |
-
6.
|
| 83 |
-
7.
|
| 84 |
-
8.
|
| 85 |
-
9.
|
| 86 |
-
10.
|
| 87 |
-
11.
|
| 88 |
-
12.
|
|
|
|
|
|
|
|
|
|
| 89 |
Example Modifying Code:
|
| 90 |
\`\`\`
|
| 91 |
Some explanation...
|
|
|
|
| 92 |
${UPDATE_PAGE_START}index.html${UPDATE_PAGE_END}
|
| 93 |
${SEARCH_START}
|
| 94 |
<h1>Old Title</h1>
|
|
|
|
| 74 |
${PROMPT_FOR_IMAGE_GENERATION}
|
| 75 |
Do NOT explain the changes or what you did, just return the expected results.
|
| 76 |
Update Format Rules:
|
| 77 |
+
1. Start with ${PROJECT_NAME_START}.
|
| 78 |
+
2. Add the name of the project, right after the start tag.
|
| 79 |
+
3. Close the start tag with the ${PROJECT_NAME_END}.
|
| 80 |
+
4. Start with ${UPDATE_PAGE_START}
|
| 81 |
+
5. Provide the name of the page you are modifying.
|
| 82 |
+
6. Close the start tag with the ${UPDATE_PAGE_END}.
|
| 83 |
+
7. Start with ${SEARCH_START}
|
| 84 |
+
8. Provide the exact lines from the current code that need to be replaced.
|
| 85 |
+
9. Use ${DIVIDER} to separate the search block from the replacement.
|
| 86 |
+
10. Provide the new lines that should replace the original lines.
|
| 87 |
+
11. End with ${REPLACE_END}
|
| 88 |
+
12. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
|
| 89 |
+
13. To insert code, use an empty SEARCH block (only ${SEARCH_START} and ${DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
|
| 90 |
+
14. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only ${DIVIDER} and ${REPLACE_END} on their lines).
|
| 91 |
+
15. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
|
| 92 |
Example Modifying Code:
|
| 93 |
\`\`\`
|
| 94 |
Some explanation...
|
| 95 |
+
${PROJECT_NAME_START}Project Name${PROJECT_NAME_END}
|
| 96 |
${UPDATE_PAGE_START}index.html${UPDATE_PAGE_END}
|
| 97 |
${SEARCH_START}
|
| 98 |
<h1>Old Title</h1>
|