diff --git a/css/art/genAI.css b/css/art/genAI.css
index ddc5482cb17605044a76d698aaf1253c872fa30a..8d111f780b44da137dc59309f762990d28a314cb 100644
--- a/css/art/genAI.css
+++ b/css/art/genAI.css
@@ -17,11 +17,33 @@
     animation: spin 2s linear infinite;
 }
 
+.rightArrow {
+    right: 0px;
+}
+
+.leftArrow {
+    left: 0px;
+}
+
+.arrow {
+    display: none;
+    position: absolute;
+    bottom: 0px;
+    cursor: pointer;
+    border: none;
+    background: none;
+}
+
+.arrow:hover {
+    border: none;
+    background: none;
+}
+
 .ai-art-container {
     width: 100%;
     height: 100%;
     min-width: 100px;
-    min-height: 100px;
+    min-height: 150px;
     float: right;
     border: 3px hidden;
     object-fit: contain;
@@ -34,8 +56,8 @@
 .ai-toolbar {
     display: none;
     position: absolute;
-    right: 1rem;
-    top: 1rem;
+    right: 0px;
+    top: 0px;
 }
 
 .ai-art-container:hover .ai-toolbar {
@@ -43,6 +65,9 @@
     flex-direction: column;
 }
 
+.ai-art-container:hover .arrow {
+    display: block;
+}
 
 .ai-toolbar button {
     /* position: absolute; */
diff --git a/src/art/artJS.js b/src/art/artJS.js
index 7b50e278b5020baae9f98a4227ecfa659bc9f070..efa2d8fc38b28c636294f8fcd8d5d8e2ed8c41fe 100644
--- a/src/art/artJS.js
+++ b/src/art/artJS.js
@@ -434,10 +434,10 @@ App.Art.customArtElement = function(imageInfo, imageSize) {
  * @param {number} imageSize - The size of the image to render
  * @returns {Promise<HTMLElement>} Promise object that resolves with the created img element
  */
-async function renderAIArt(slave, imageSize) {
+async function renderAIArt(slave, imageSize, imageNum = null) {
 	let imgElement;
-	if (slave.custom.aiImageId === null) {
-		imgElement = document.createElement("div");
+	if (slave.custom.aiImageIds.length === 0) {
+		imgElement = document.createElement("div", {is: `slaveImg${slave.ID}`});
 	} else {
 		imgElement = document.createElement("img");
 	}
@@ -452,8 +452,15 @@ async function renderAIArt(slave, imageSize) {
 	}
 
 	try {
-		const imageData = await App.Art.GenAI.imageDB.getImage(slave.custom.aiImageId);
+		// Initial slave state
+		if (imageNum === -1) {
+			return imgElement;
+		}
+		const imageIdx = imageNum || 0;
+		const imageDbId = slave.custom.aiImageIds[imageIdx];
+		const imageData = await App.Art.GenAI.imageDB.getImage(imageDbId);
 		imgElement.setAttribute("src", imageData.data);
+		imgElement.setAttribute("title", `${slave.custom.aiDisplayImageIdx + 1}/${slave.custom.aiImageIds.length}`);
 	} catch (e) {
 		return Promise.reject(e);
 	}
@@ -473,7 +480,11 @@ App.Art.aiArtElement = function(slave, imageSize) {
 	toolbar.classList.add('ai-toolbar');
 	container.appendChild(toolbar);
 	/** @type {HTMLButtonElement} */
-	let refreshButton;
+	let replaceButton;
+	/** @type {HTMLButtonElement} */
+	let generationButton;
+	/** @type {HTMLButtonElement} */
+	let deletionButton;
 	/** @type {HTMLDivElement} */
 	let spinner;
 	/** @type {HTMLButtonElement} */
@@ -525,19 +536,67 @@ App.Art.aiArtElement = function(slave, imageSize) {
 	 * @param {HTMLDivElement} toolbar
 	 * @param {HTMLDivElement} container
 	 */
-	function makeRefreshButton(toolbar, container) {
-		refreshButton = document.createElement("button");
-		refreshButton.innerText = '⟳';
-		refreshButton.title = 'Regenerate';
-		refreshButton.addEventListener("click", function() {
-			console.log('clicked listner to refresh button');
+	function makeReplaceButton(toolbar, container) {
+		replaceButton = document.createElement("button");
+		replaceButton.innerText = '⟳';
+		replaceButton.title = 'Replace';
+		replaceButton.addEventListener("click", function() {
+			console.log('clicked listner to replace button');
+			if (!container.classList.contains("refreshing")) {
+				if (slave.custom.aiDisplayImageIdx === -1) return;
+				updateAndRefresh(slave.custom.aiDisplayImageIdx);
+			}
+		});
+		toolbar.appendChild(replaceButton);
+	}
+	makeReplaceButton(toolbar, container);
+
+	/**
+	 * @param {HTMLDivElement} toolbar
+ 	 * @param {HTMLDivElement} container
+ 	 */
+	function makeGenerationButton(toolbar, container) {
+		generationButton = document.createElement("button");
+		generationButton.innerText = '+';
+		generationButton.title = 'Add image';
+		generationButton.addEventListener("click", function() {
 			if (!container.classList.contains("refreshing")) {
 				updateAndRefresh();
 			}
 		});
-		toolbar.appendChild(refreshButton);
+		toolbar.appendChild(generationButton);
 	}
-	makeRefreshButton(toolbar, container);
+	makeGenerationButton(toolbar, container);
+
+	async function deleteSlaveAiImage(slave, idx) {
+		const deletionId = slave.custom.aiImageIds[idx];
+		await App.Art.GenAI.imageDB.removeImage(deletionId);
+		slave.custom.aiImageIds = [...slave.custom.aiImageIds.slice(0, idx), ...slave.custom.aiImageIds.slice(idx + 1)];
+		if (slave.custom.aiImageIds.length === 0) {
+			slave.custom.aiDisplayImageIdx = -1;
+		} else if (slave.custom.aiDisplayImageIdx !== 0) {
+			slave.custom.aiDisplayImageIdx--;
+		}
+	};
+
+	/**
+	 * @param {HTMLDivElement} toolbar
+ 	 * @param {HTMLDivElement} container
+ 	 */
+	 function makeDeleteButton(toolbar, container) {
+		deletionButton = document.createElement("button");
+		deletionButton.innerText = 'Ⓧ';
+		deletionButton.title = 'Delete image';
+		deletionButton.addEventListener("click", async function() {
+			if (!container.classList.contains("refreshing")) {
+				if (slave.custom.aiDisplayImageIdx === -1) return;
+				await deleteSlaveAiImage(slave, slave.custom.aiDisplayImageIdx)
+				refresh(false);
+			}
+		});
+		toolbar.appendChild(deletionButton);
+	}
+	makeDeleteButton(toolbar, container);
 
 	/**
 	 * @param {HTMLDivElement} container
@@ -555,23 +614,77 @@ App.Art.aiArtElement = function(slave, imageSize) {
 	 * @param {boolean} retry should we retry image generation or not?
 	 */
 	function refresh(retry) {
-		renderAIArt(slave, imageSize)
+		renderAIArt(slave, imageSize, slave.custom.aiDisplayImageIdx)
 			.then((imgElement) => {
 				container.querySelector('.ai-art-image')?.remove();
 				container.prepend(imgElement); // prepend it before the toolbar and spinner, otherwise you can't see them
-			}).catch(() => {
+			}).catch((e) => {
 				if (retry) {
+					console.log('Error in refresh retry')
+					console.log(e);
 					updateAndRefresh();
 				}
 			});
 	}
 
-	function updateAndRefresh() {
+	function makeImageNavigationArrows(container) {
+		const leftArrow = document.createElement('button');
+		leftArrow.classList.add("leftArrow", "arrow");
+		leftArrow.name = 'leftButton';
+		leftArrow.title = 'Previous image';
+		leftArrow.innerText = '←';
+		leftArrow.onclick = (e) => {
+			// Stop update onclick
+			e.stopPropagation();
+			if(!slave.custom.aiImageIds) {
+				slave.custom.aiImageIds = [];
+			}
+
+			if (slave.custom.aiImageIds.length === 0) {
+				updateAndRefresh();
+			} else {
+				if (slave.custom.aiDisplayImageIdx > 0) {
+					slave.custom.aiDisplayImageIdx--;
+				} else {
+					slave.custom.aiDisplayImageIdx = slave.custom.aiImageIds.length - 1;
+				}
+				refresh(false);
+			};
+		};
+		container.appendChild(leftArrow);
+
+		const rightArrow = document.createElement('button');
+		rightArrow.classList.add("rightArrow", "arrow");
+		rightArrow.name = 'rightButton';
+		rightArrow.title = 'Next image';
+		rightArrow.innerText = '→';
+		rightArrow.onclick = (e) => {
+			e.stopPropagation();
+			if(!slave.custom.aiImageIds) {
+				slave.custom.aiImageIds = [];
+			}
+
+			if (slave.custom.aiImageIds.length === 0) {
+				updateAndRefresh();
+			} else {
+				if (slave.custom.aiDisplayImageIdx < slave.custom.aiImageIds.length - 1) {
+					slave.custom.aiDisplayImageIdx++;
+				} else {
+					slave.custom.aiDisplayImageIdx = 0;
+				}
+				refresh(false);
+			}
+		};
+		container.appendChild(rightArrow);
+	}
+	makeImageNavigationArrows(container);
+
+	function updateAndRefresh(index = null) {
 		const imageGenerator = new App.Art.GenAI.StableDiffusionClient();
 
 		container.classList.add("refreshing");
 
-		imageGenerator.updateSlave(slave).then(() => {
+		imageGenerator.updateSlave(slave, index).then(() => {
 			refresh(false);
 		}).catch(error => {
 			console.error(error);
@@ -580,11 +693,10 @@ App.Art.aiArtElement = function(slave, imageSize) {
 		});
 	}
 
-
-	if (slave.custom.aiImageId === null) {
+	if (slave.custom.aiImageIds === null) {
 		updateAndRefresh();
 	} else {
-		refresh(true);
+		refresh(false);
 	}
 	return container;
 };
diff --git a/src/art/genAI/imageDB.js b/src/art/genAI/imageDB.js
index 0323db9090947cf51a78903218082318191e8c42..3c6e5973d0dae5433136fe4bb7bf3313da803027 100644
--- a/src/art/genAI/imageDB.js
+++ b/src/art/genAI/imageDB.js
@@ -63,10 +63,9 @@ App.Art.GenAI.imageDB = (function() {
 	 */
 	async function getImage(id) {
 		return new Promise((resolve, reject) => {
-			let transaction = db.transaction(['images'], 'readonly');
-			let objectStore = transaction.objectStore('images');
-
-			let request = objectStore.get(id);
+			const transaction = db.transaction(['images'], 'readonly');
+			const objectStore = transaction.objectStore('images');
+			const request = objectStore.get(id);
 
 			request.onsuccess = function() {
 				resolve(request.result);
diff --git a/src/art/genAI/stableDiffusion.js b/src/art/genAI/stableDiffusion.js
index 21fd3bc4cd8bd514e3ffa825dc27f9b10bd8e6fd..3e7cd7faf93c70ba397cfee508a2f5c05a768433 100644
--- a/src/art/genAI/stableDiffusion.js
+++ b/src/art/genAI/stableDiffusion.js
@@ -110,7 +110,7 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 				body: satisfied.last().body,
 			};
 			try {
-				const response = await fetchWithTimeout(`${V.aiApiUrl}/sdapi/v1/txt2img`, 60000, options);
+				const response = await fetchWithTimeout(`${V.aiApiUrl}/sdapi/v1/txt2img`, 600000, options);
 				if (!response.ok) {
 					throw new Error(`Error fetching Stable Diffusion image - status: ${response.status}`);
 				}
@@ -136,7 +136,7 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 	/**
 	 * Queue image generation for an entity
 	 * @param {number} slaveID or a unique negative value for non-slave entities
-	 * @param {string} body body of the post request to be sent to txt2img
+	 * @param {string} body of the post request to be sent to txt2img
 	 * @returns {Promise<Object>}
 	 */
 	async add(slaveID, body) {
@@ -223,7 +223,6 @@ App.Art.GenAI.StableDiffusionClient = class {
 	 */
 	async fetchImageForSlave(slave) {
 		const settings = this.buildStableDiffusionSettings(slave);
-
 		// set up a passage switch handler to interrupt image generation if it's incomplete
 		const oldHandler = App.Utils.PassageSwitchHandler.get();
 		App.Utils.PassageSwitchHandler.set(() => {
@@ -240,21 +239,54 @@ App.Art.GenAI.StableDiffusionClient = class {
 	/**
 	 * Update a slave object with a new image
 	 * @param {FC.SlaveState} slave - The slave to update
+	 * @param {number | null} replacementImageIndex - If provided, replace the image at this index
 	 */
-	async updateSlave(slave) {
+	async updateSlave(slave, replacementImageIndex = null) {
 		const base64Image = await this.fetchImageForSlave(slave);
-		const mimeType = getMimeType(base64Image);
+		const imageData = getImageData(base64Image);
+		const imagePreexisting = await compareExistingImages(slave, imageData);
 
-		const dbrecord = {data: `data:${mimeType};base64,${base64Image}`};
-		if (slave.custom.aiImageId !== null) {
-			dbrecord.id = slave.custom.aiImageId;
+		// If new image, add or replace it in
+		if (imagePreexisting === -1) {
+			const imageId = await App.Art.GenAI.imageDB.putImage({data: imageData});
+			if (replacementImageIndex !== null) {
+				await App.Art.GenAI.imageDB.removeImage(slave.custom.aiImageIds[replacementImageIndex]);
+				slave.custom.aiImageIds[replacementImageIndex] = imageId;
+			} else {
+				slave.custom.aiImageIds.push(imageId);
+				slave.custom.aiDisplayImageIdx = slave.custom.aiImageIds.indexOf(imageId)
+			}
+		// If image already exists, just update the display idx to it
+		} else {
+			console.log('generated redundant image, no image stored')
+			slave.custom.aiDisplayImageIdx = imagePreexisting;
 		}
-
-		const imageId = await App.Art.GenAI.imageDB.putImage(dbrecord);
-		slave.custom.aiImageId = imageId;
 	}
 };
 
+/**
+ * Search slave's existing images for a match with the new image.
+ * @param {FC.SlaveState} slave - The slave we're updating
+ * @param {string} newImageData - new image
+ * @returns index of the image in aiImageIds or -1
+ */
+async function compareExistingImages (slave, newImageData) {
+	const aiImages = await Promise.all(slave.custom.aiImageIds.map(id => {
+		return App.Art.GenAI.imageDB.getImage(id);
+	}));
+	return aiImages.findIndex(img => img.data === newImageData);
+}
+
+/**
+ * Add mime type to a base64 encoded image
+ * @param {string} base64Image
+ * @returns {string} data string
+ */
+function getImageData(base64Image) {
+	const mimeType = getMimeType(base64Image);
+	return `data:${mimeType};base64,${base64Image}`
+}
+
 /**
  * @param {string} base64Image
  * @returns {string}
diff --git a/src/data/backwardsCompatibility/datatypeCleanup.js b/src/data/backwardsCompatibility/datatypeCleanup.js
index 5f401ffb15b50c7933050f1973d25681a49a0c9c..eea18fd7183159c878a457862ce7f96c7365e282 100644
--- a/src/data/backwardsCompatibility/datatypeCleanup.js
+++ b/src/data/backwardsCompatibility/datatypeCleanup.js
@@ -266,8 +266,15 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 			slave.custom.name = "";
 		}
 
-		if (!slave.custom.hasOwnProperty("aiImageId")) {
-			slave.custom.aiImageId = null;
+		if (!slave.custom.hasOwnProperty("aiImageIds")) {
+			if (slave.custom.hasOwnProperty("aiImageId") && slave.custom.aiImageId !== null) {
+				slave.custom.aiImageIds = [slave.custom.aiImageId];
+				slave.custom.aiDisplayImageIdx = 0;
+				delete slave.custom.aiImageId;
+			} else {
+				slave.custom.aiImageIds = [];
+				slave.custom.aiDisplayImageIdx = -1;
+			}
 		}
 	}
 
diff --git a/src/js/SlaveState.js b/src/js/SlaveState.js
index 03f3fd0307633714ef395b8b5a7f68185ffb839d..89a9cc9d8fc186aba92f876b610b1e40f95a4224 100644
--- a/src/js/SlaveState.js
+++ b/src/js/SlaveState.js
@@ -409,9 +409,16 @@ App.Entity.SlaveCustomAddonsState = class SlaveCustomAddonsState {
 		 * holds the ai image ID
 		 *
 		 * used if ai images are enabled
+		 * @type {Array<number>}
+		 */
+		this.aiImageIds = [];
+		/**
+		 * holds the index of the displayed AI image in aiImageIds
+		 *
+		 * used if ai images are enabled
 		 * @type {number}
 		 */
-		this.aiImageId = null;
+		this.aiDisplayImageIdx = -1;
 		/**
 		 * custom AI prompts; may be null or absent
 		 * @type {App.Entity.SlaveCustomAIPrompts}