✨ 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}
|