diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx
index a14b70ac8d42cb2c3e83322a83bf07007d3ba92f..f115e7948904f29b2e79e97c095dda34ddf5894e 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx
@@ -18,6 +18,8 @@ import AttachItem from "./AttachItem";
 import { PASTE_ATTACHMENT_EVENT } from "../DnDWrapper";
 
 export const PROMPT_INPUT_EVENT = "set_prompt_input";
+const MAX_EDIT_STACK_SIZE = 100;
+
 export default function PromptInput({
   submit,
   onChange,
@@ -32,14 +34,24 @@ export default function PromptInput({
   const formRef = useRef(null);
   const textareaRef = useRef(null);
   const [_, setFocused] = useState(false);
+  const undoStack = useRef([]);
+  const redoStack = useRef([]);
 
-  // To prevent too many re-renders we remotely listen for updates from the parent
-  // via an event cycle. Otherwise, using message as a prop leads to a re-render every
-  // change on the input.
+  /**
+   * To prevent too many re-renders we remotely listen for updates from the parent
+   * via an event cycle. Otherwise, using message as a prop leads to a re-render every
+   * change on the input.
+   * @param {Event} e
+   */
   function handlePromptUpdate(e) {
     setPromptInput(e?.detail ?? "");
   }
 
+  function resetTextAreaHeight() {
+    if (!textareaRef.current) return;
+    textareaRef.current.style.height = "auto";
+  }
+
   useEffect(() => {
     if (!!window)
       window.addEventListener(PROMPT_INPUT_EVENT, handlePromptUpdate);
@@ -48,51 +60,120 @@ export default function PromptInput({
   }, []);
 
   useEffect(() => {
-    if (!inputDisabled && textareaRef.current) {
-      textareaRef.current.focus();
-    }
+    if (!inputDisabled && textareaRef.current) textareaRef.current.focus();
     resetTextAreaHeight();
   }, [inputDisabled]);
 
-  const handleSubmit = (e) => {
+  /**
+   * Save the current state before changes
+   * @param {number} adjustment
+   */
+  function saveCurrentState(adjustment = 0) {
+    if (undoStack.current.length >= MAX_EDIT_STACK_SIZE)
+      undoStack.current.shift();
+    undoStack.current.push({
+      value: promptInput,
+      cursorPositionStart: textareaRef.current.selectionStart + adjustment,
+      cursorPositionEnd: textareaRef.current.selectionEnd + adjustment,
+    });
+  }
+  const debouncedSaveState = debounce(saveCurrentState, 250);
+
+  function handleSubmit(e) {
     setFocused(false);
     submit(e);
-  };
+  }
 
-  const resetTextAreaHeight = () => {
-    if (textareaRef.current) {
-      textareaRef.current.style.height = "auto";
-    }
-  };
+  function resetTextAreaHeight() {
+    if (!textareaRef.current) return;
+    textareaRef.current.style.height = "auto";
+  }
 
-  const checkForSlash = (e) => {
+  function checkForSlash(e) {
     const input = e.target.value;
     if (input === "/") setShowSlashCommand(true);
     if (showSlashCommand) setShowSlashCommand(false);
     return;
-  };
+  }
+  const watchForSlash = debounce(checkForSlash, 300);
 
-  const checkForAt = (e) => {
+  function checkForAt(e) {
     const input = e.target.value;
     if (input === "@") return setShowAgents(true);
     if (showAgents) return setShowAgents(false);
-  };
+  }
+  const watchForAt = debounce(checkForAt, 300);
 
-  const captureEnter = (event) => {
-    if (event.keyCode == 13) {
-      if (!event.shiftKey) {
-        submit(event);
-      }
+  /**
+   * Capture enter key press to handle submission, redo, or undo
+   * via keyboard shortcuts
+   * @param {KeyboardEvent} event
+   */
+  function captureEnterOrUndo(event) {
+    // Is simple enter key press w/o shift key
+    if (event.keyCode === 13 && !event.shiftKey) {
+      event.preventDefault();
+      return submit(event);
+    }
+
+    // Is undo with Ctrl+Z or Cmd+Z + Shift key = Redo
+    if (
+      (event.ctrlKey || event.metaKey) &&
+      event.key === "z" &&
+      event.shiftKey
+    ) {
+      event.preventDefault();
+      if (redoStack.current.length === 0) return;
+
+      const nextState = redoStack.current.pop();
+      if (!nextState) return;
+
+      undoStack.current.push({
+        value: promptInput,
+        cursorPositionStart: textareaRef.current.selectionStart,
+        cursorPositionEnd: textareaRef.current.selectionEnd,
+      });
+      setPromptInput(nextState.value);
+      setTimeout(() => {
+        textareaRef.current.setSelectionRange(
+          nextState.cursorPositionStart,
+          nextState.cursorPositionEnd
+        );
+      }, 0);
     }
-  };
 
-  const adjustTextArea = (event) => {
+    // Undo with Ctrl+Z or Cmd+Z
+    if (
+      (event.ctrlKey || event.metaKey) &&
+      event.key === "z" &&
+      !event.shiftKey
+    ) {
+      if (undoStack.current.length === 0) return;
+      const lastState = undoStack.current.pop();
+      if (!lastState) return;
+
+      redoStack.current.push({
+        value: promptInput,
+        cursorPositionStart: textareaRef.current.selectionStart,
+        cursorPositionEnd: textareaRef.current.selectionEnd,
+      });
+      setPromptInput(lastState.value);
+      setTimeout(() => {
+        textareaRef.current.setSelectionRange(
+          lastState.cursorPositionStart,
+          lastState.cursorPositionEnd
+        );
+      }, 0);
+    }
+  }
+
+  function adjustTextArea(event) {
     const element = event.target;
     element.style.height = "auto";
     element.style.height = `${element.scrollHeight}px`;
-  };
+  }
 
-  const handlePasteEvent = (e) => {
+  function handlePasteEvent(e) {
     e.preventDefault();
     if (e.clipboardData.items.length === 0) return false;
 
@@ -140,10 +221,16 @@ export default function PromptInput({
       }, 0);
     }
     return;
-  };
+  }
 
-  const watchForSlash = debounce(checkForSlash, 300);
-  const watchForAt = debounce(checkForAt, 300);
+  function handleChange(e) {
+    debouncedSaveState(-1);
+    onChange(e);
+    watchForSlash(e);
+    watchForAt(e);
+    adjustTextArea(e);
+    setPromptInput(e.target.value);
+  }
 
   return (
     <div className="w-full fixed md:absolute bottom-0 left-0 z-10 md:z-0 flex justify-center items-center">
@@ -168,15 +255,12 @@ export default function PromptInput({
             <div className="flex items-center w-full border-b-2 border-gray-500/50">
               <textarea
                 ref={textareaRef}
-                onChange={(e) => {
-                  onChange(e);
-                  watchForSlash(e);
-                  watchForAt(e);
-                  adjustTextArea(e);
-                  setPromptInput(e.target.value);
+                onChange={handleChange}
+                onKeyDown={captureEnterOrUndo}
+                onPaste={(e) => {
+                  saveCurrentState();
+                  handlePasteEvent(e);
                 }}
-                onKeyDown={captureEnter}
-                onPaste={handlePasteEvent}
                 required={true}
                 disabled={inputDisabled}
                 onFocus={() => setFocused(true)}