Spaces:
Running
Running
✨ update ui to show what changed
Browse files- server.js +26 -2
- src/assets/index.css +4 -0
- src/components/ask-ai/ask-ai.tsx +2 -2
- src/components/settings/settings.tsx +1 -1
- src/views/App.tsx +29 -2
server.js
CHANGED
@@ -498,6 +498,8 @@ ${REPLACE_END}
|
|
498 |
|
499 |
if (chunk) {
|
500 |
let newHtml = html;
|
|
|
|
|
501 |
|
502 |
// Find all search/replace blocks in the chunk
|
503 |
let position = 0;
|
@@ -536,9 +538,30 @@ ${REPLACE_END}
|
|
536 |
if (searchBlock.trim() === "") {
|
537 |
// Inserting at the beginning
|
538 |
newHtml = `${replaceBlock}\n${newHtml}`;
|
|
|
|
|
|
|
539 |
} else {
|
540 |
-
//
|
541 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
542 |
}
|
543 |
|
544 |
// Move position to after this block to find the next one
|
@@ -548,6 +571,7 @@ ${REPLACE_END}
|
|
548 |
return res.status(200).send({
|
549 |
ok: true,
|
550 |
html: newHtml,
|
|
|
551 |
});
|
552 |
} else {
|
553 |
return res.status(400).send({
|
|
|
498 |
|
499 |
if (chunk) {
|
500 |
let newHtml = html;
|
501 |
+
// array of arrays to hold updated lines (start and end line numbers)
|
502 |
+
const updatedLines = [];
|
503 |
|
504 |
// Find all search/replace blocks in the chunk
|
505 |
let position = 0;
|
|
|
538 |
if (searchBlock.trim() === "") {
|
539 |
// Inserting at the beginning
|
540 |
newHtml = `${replaceBlock}\n${newHtml}`;
|
541 |
+
|
542 |
+
// Track first line as updated
|
543 |
+
updatedLines.push([1, replaceBlock.split("\n").length]);
|
544 |
} else {
|
545 |
+
// Find the position of the search block in the HTML
|
546 |
+
const blockPosition = newHtml.indexOf(searchBlock);
|
547 |
+
if (blockPosition !== -1) {
|
548 |
+
// Count lines before the search block
|
549 |
+
const beforeText = newHtml.substring(0, blockPosition);
|
550 |
+
const startLineNumber = beforeText.split("\n").length;
|
551 |
+
|
552 |
+
// Count lines in search and replace blocks
|
553 |
+
const searchLines = searchBlock.split("\n").length;
|
554 |
+
const replaceLines = replaceBlock.split("\n").length;
|
555 |
+
|
556 |
+
// Calculate end line (start + length of replaced content)
|
557 |
+
const endLineNumber = startLineNumber + replaceLines - 1;
|
558 |
+
|
559 |
+
// Track the line numbers that were updated
|
560 |
+
updatedLines.push([startLineNumber, endLineNumber]);
|
561 |
+
|
562 |
+
// Perform the replacement
|
563 |
+
newHtml = newHtml.replace(searchBlock, replaceBlock);
|
564 |
+
}
|
565 |
}
|
566 |
|
567 |
// Move position to after this block to find the next one
|
|
|
571 |
return res.status(200).send({
|
572 |
ok: true,
|
573 |
html: newHtml,
|
574 |
+
updatedLines,
|
575 |
});
|
576 |
} else {
|
577 |
return res.status(400).send({
|
src/assets/index.css
CHANGED
@@ -136,3 +136,7 @@
|
|
136 |
.monaco-editor .line-numbers {
|
137 |
@apply !text-neutral-500;
|
138 |
}
|
|
|
|
|
|
|
|
|
|
136 |
.monaco-editor .line-numbers {
|
137 |
@apply !text-neutral-500;
|
138 |
}
|
139 |
+
|
140 |
+
.matched-line {
|
141 |
+
@apply bg-sky-500/30;
|
142 |
+
}
|
src/components/ask-ai/ask-ai.tsx
CHANGED
@@ -31,7 +31,7 @@ function AskAI({
|
|
31 |
isAiWorking: boolean;
|
32 |
onNewPrompt: (prompt: string) => void;
|
33 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
34 |
-
onSuccess: (h: string, p: string) => void;
|
35 |
}) {
|
36 |
const refThink = useRef<HTMLDivElement | null>(null);
|
37 |
|
@@ -100,7 +100,7 @@ function AskAI({
|
|
100 |
setPreviousPrompt(prompt);
|
101 |
setPrompt("");
|
102 |
setisAiWorking(false);
|
103 |
-
onSuccess(res.html, prompt);
|
104 |
audio.play();
|
105 |
}
|
106 |
} else {
|
|
|
31 |
isAiWorking: boolean;
|
32 |
onNewPrompt: (prompt: string) => void;
|
33 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
34 |
+
onSuccess: (h: string, p: string, n?: number[][]) => void;
|
35 |
}) {
|
36 |
const refThink = useRef<HTMLDivElement | null>(null);
|
37 |
|
|
|
100 |
setPreviousPrompt(prompt);
|
101 |
setPrompt("");
|
102 |
setisAiWorking(false);
|
103 |
+
onSuccess(res.html, prompt, res.updatedLines);
|
104 |
audio.play();
|
105 |
}
|
106 |
} else {
|
src/components/settings/settings.tsx
CHANGED
@@ -105,7 +105,7 @@ function Settings({
|
|
105 |
label: string;
|
106 |
isNew?: boolean;
|
107 |
}) => (
|
108 |
-
<SelectItem value={value} className="">
|
109 |
{label}
|
110 |
{isNew && (
|
111 |
<span className="text-xs bg-gradient-to-br from-sky-400 to-sky-600 text-white rounded-full px-1.5 py-0.5">
|
|
|
105 |
label: string;
|
106 |
isNew?: boolean;
|
107 |
}) => (
|
108 |
+
<SelectItem key={value} value={value} className="">
|
109 |
{label}
|
110 |
{isNew && (
|
111 |
<span className="text-xs bg-gradient-to-br from-sky-400 to-sky-600 text-white rounded-full px-1.5 py-0.5">
|
src/views/App.tsx
CHANGED
@@ -34,6 +34,8 @@ export default function App() {
|
|
34 |
const resizer = useRef<HTMLDivElement>(null);
|
35 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
36 |
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
|
|
|
37 |
|
38 |
const [html, setHtml] = useState((htmlStorage as string) ?? defaultHTML);
|
39 |
const [isAiWorking, setisAiWorking] = useState(false);
|
@@ -222,14 +224,21 @@ export default function App() {
|
|
222 |
const newValue = value ?? "";
|
223 |
setHtml(newValue);
|
224 |
}}
|
225 |
-
onMount={(editor) =>
|
|
|
|
|
|
|
226 |
/>
|
227 |
<AskAI
|
228 |
html={html}
|
229 |
setHtml={(newHtml: string) => {
|
230 |
setHtml(newHtml);
|
231 |
}}
|
232 |
-
onSuccess={(
|
|
|
|
|
|
|
|
|
233 |
const currentHistory = [...htmlHistory];
|
234 |
currentHistory.unshift({
|
235 |
html: finalHtml,
|
@@ -241,6 +250,24 @@ export default function App() {
|
|
241 |
if (window.innerWidth <= 1024) {
|
242 |
setCurrentTab("preview");
|
243 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
244 |
}}
|
245 |
isAiWorking={isAiWorking}
|
246 |
setisAiWorking={setisAiWorking}
|
|
|
34 |
const resizer = useRef<HTMLDivElement>(null);
|
35 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
36 |
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
37 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
38 |
+
const monacoRef = useRef<any>(null);
|
39 |
|
40 |
const [html, setHtml] = useState((htmlStorage as string) ?? defaultHTML);
|
41 |
const [isAiWorking, setisAiWorking] = useState(false);
|
|
|
224 |
const newValue = value ?? "";
|
225 |
setHtml(newValue);
|
226 |
}}
|
227 |
+
onMount={(editor, monaco) => {
|
228 |
+
editorRef.current = editor;
|
229 |
+
monacoRef.current = monaco;
|
230 |
+
}}
|
231 |
/>
|
232 |
<AskAI
|
233 |
html={html}
|
234 |
setHtml={(newHtml: string) => {
|
235 |
setHtml(newHtml);
|
236 |
}}
|
237 |
+
onSuccess={(
|
238 |
+
finalHtml: string,
|
239 |
+
p: string,
|
240 |
+
updatedLines?: number[][]
|
241 |
+
) => {
|
242 |
const currentHistory = [...htmlHistory];
|
243 |
currentHistory.unshift({
|
244 |
html: finalHtml,
|
|
|
250 |
if (window.innerWidth <= 1024) {
|
251 |
setCurrentTab("preview");
|
252 |
}
|
253 |
+
if (updatedLines && updatedLines?.length > 0) {
|
254 |
+
const decorations = updatedLines.map((line) => ({
|
255 |
+
range: new monacoRef.current.Range(
|
256 |
+
line[0],
|
257 |
+
1,
|
258 |
+
line[1],
|
259 |
+
1
|
260 |
+
),
|
261 |
+
options: {
|
262 |
+
inlineClassName: "matched-line",
|
263 |
+
},
|
264 |
+
}));
|
265 |
+
setTimeout(() => {
|
266 |
+
editorRef?.current
|
267 |
+
?.getModel()
|
268 |
+
?.deltaDecorations([], decorations);
|
269 |
+
}, 100);
|
270 |
+
}
|
271 |
}}
|
272 |
isAiWorking={isAiWorking}
|
273 |
setisAiWorking={setisAiWorking}
|