Add rename buttons for ComfyUI workflows and style presets (#5124) * Initial plan * Add rename buttons for ComfyUI workflows and style presets Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Address review feedback: pencil icon, filename validation, 204 status, error prefix, frontend duplicate check, re-render styles select Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * fix: ensure existing workflow check only considers HTMLOptionElement instances --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

23ba3e5bb2e7aad59d0912b2b026a14052936f1b

Copilot <198982749+Copilot@users.noreply.github.com>

Signed
3 files changed, +131 -0Ignore whitespace
public/scripts/extensions/stable-diffusion/index.js+96 -0
@@ -765,6 +765,48 @@ async function onSaveStyleClick() {
765765 saveSettingsDebounced();
766766}
767767
768+async function onRenameStyleClick() {
769+ const selectedStyle = extension_settings.sd.style;
770+ const styleObject = extension_settings.sd.styles.find(x => x.name === selectedStyle);
771+
772+ if (!styleObject) {
773+ return;
774+ }
775+
776+ const newName = await callGenericPopup(t`Enter new style name:`, POPUP_TYPE.INPUT, selectedStyle);
777+
778+ if (!newName) {
779+ return;
780+ }
781+
782+ const name = String(newName).trim();
783+
784+ if (name === selectedStyle) {
785+ return;
786+ }
787+
788+ const existingStyle = extension_settings.sd.styles.find(x => x.name === name);
789+
790+ if (existingStyle) {
791+ toastr.error(t`A style with that name already exists`);
792+ return;
793+ }
794+
795+ styleObject.name = name;
796+ extension_settings.sd.style = name;
797+
798+ $('#sd_style').empty();
799+ for (const style of extension_settings.sd.styles) {
800+ const option = document.createElement('option');
801+ option.value = style.name;
802+ option.text = style.name;
803+ option.selected = style.name === extension_settings.sd.style;
804+ $('#sd_style').append(option);
805+ }
806+
807+ saveSettingsDebounced();
808+}
809+
768810/**
769811 * Modifies prompt based on user inputs.
770812 * @param {string} prompt Prompt to refine
@@ -4728,6 +4770,58 @@ async function onComfyDeleteWorkflowClick() {
47284770 onComfyWorkflowChange();
47294771}
47304772
4773+async function onComfyRenameWorkflowClick() {
4774+ const oldName = extension_settings.sd.comfy_workflow;
4775+
4776+ if (!oldName) {
4777+ return;
4778+ }
4779+
4780+ let newName = await callGenericPopup(t`Enter new workflow name:`, POPUP_TYPE.INPUT, oldName);
4781+
4782+ if (!newName) {
4783+ return;
4784+ }
4785+
4786+ newName = String(newName).trim();
4787+
4788+ if (!newName.toLowerCase().endsWith('.json')) {
4789+ newName += '.json';
4790+ }
4791+
4792+ if (newName === oldName) {
4793+ return;
4794+ }
4795+
4796+ const existingWorkflow = Array
4797+ .from(document.querySelectorAll('#sd_comfy_workflow option'))
4798+ .find(opt => opt instanceof HTMLOptionElement && opt.value === newName);
4799+
4800+ if (existingWorkflow) {
4801+ toastr.warning(t`A workflow with that name already exists`);
4802+ return;
4803+ }
4804+
4805+ const response = await fetch('/api/sd/comfy/rename-workflow', {
4806+ method: 'POST',
4807+ headers: getRequestHeaders(),
4808+ body: JSON.stringify({
4809+ old_name: oldName,
4810+ new_name: newName,
4811+ }),
4812+ });
4813+
4814+ if (!response.ok) {
4815+ const text = await response.text();
4816+ toastr.error(t`Failed to rename workflow.\n\n${text}`);
4817+ return;
4818+ }
4819+
4820+ extension_settings.sd.comfy_workflow = newName;
4821+ saveSettingsDebounced();
4822+ await loadComfyWorkflows();
4823+}
4824+
47314825/**
47324826 * Sends a chat message with the generated image.
47334827 * @param {string} prompt Prompt used for the image generation
@@ -5601,9 +5695,11 @@ jQuery(async () => {
56015695 $('#sd_comfy_workflow').on('change', onComfyWorkflowChange);
56025696 $('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);
56035697 $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);
5698+ $('#sd_comfy_rename_workflow').on('click', onComfyRenameWorkflowClick);
56045699 $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);
56055700 $('#sd_style').on('change', onStyleSelect);
56065701 $('#sd_save_style').on('click', onSaveStyleClick);
5702+ $('#sd_rename_style').on('click', onRenameStyleClick);
56075703 $('#sd_delete_style').on('click', onDeleteStyleClick);
56085704 $('#sd_character_prompt_block').hide();
56095705 $('#sd_interactive_mode').on('input', onInteractiveModeInput);
public/scripts/extensions/stable-diffusion/settings.html+6 -0
@@ -275,6 +275,9 @@
275275 <div id="sd_comfy_new_workflow" class="menu_button menu_button_icon" data-i18n="[title]Create new workflow" title="Create new workflow">
276276 <i class="fa-solid fa-plus"></i>
277277 </div>
278+ <div id="sd_comfy_rename_workflow" class="menu_button menu_button_icon" data-i18n="[title]Rename workflow" title="Rename workflow">
279+ <i class="fa-solid fa-pencil"></i>
280+ </div>
278281 <div id="sd_comfy_delete_workflow" class="menu_button menu_button_icon" data-i18n="[title]Delete workflow" title="Delete workflow">
279282 <i class="fa-solid fa-trash-can"></i>
280283 </div>
@@ -572,6 +575,9 @@
572575 <div id="sd_save_style" data-i18n="[title]Save style" title="Save style" class="menu_button">
573576 <i class="fa-solid fa-save"></i>
574577 </div>
578+ <div id="sd_rename_style" data-i18n="[title]Rename style" title="Rename style" class="menu_button">
579+ <i class="fa-solid fa-pencil"></i>
580+ </div>
575581 <div id="sd_delete_style" data-i18n="[title]Delete style" title="Delete style" class="menu_button">
576582 <i class="fa-solid fa-trash-can"></i>
577583 </div>
src/endpoints/stable-diffusion.js+29 -0
@@ -12,6 +12,7 @@ import mime from 'mime-types';
1212
1313import { delay, getBasicAuthHeader, isValidUrl, tryParse } from '../util.js';
1414import { readSecret, SECRET_KEYS } from './secrets.js';
15+import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
1516import { AIMLAPI_HEADERS } from '../constants.js';
1617
1718/**
@@ -532,6 +533,34 @@ comfy.post('/delete-workflow', async (request, response) => {
532533 }
533534});
534535
536+comfy.post('/rename-workflow', getFileNameValidationFunction('old_name'), getFileNameValidationFunction('new_name'), async (request, response) => {
537+ try {
538+ const oldName = sanitize(String(request.body.old_name));
539+ const newName = sanitize(String(request.body.new_name));
540+
541+ if (path.extname(oldName).toLowerCase() !== '.json' || path.extname(newName).toLowerCase() !== '.json') {
542+ return response.status(400).send('Only JSON workflow files are allowed');
543+ }
544+
545+ const oldPath = path.join(request.user.directories.comfyWorkflows, oldName);
546+ const newPath = path.join(request.user.directories.comfyWorkflows, newName);
547+
548+ if (!fs.existsSync(oldPath)) {
549+ return response.status(404).send('Workflow not found');
550+ }
551+
552+ if (fs.existsSync(newPath)) {
553+ return response.status(409).send('A workflow with that name already exists');
554+ }
555+
556+ fs.renameSync(oldPath, newPath);
557+ return response.sendStatus(204);
558+ } catch (error) {
559+ console.error('ComfyUI workflow rename failed', error);
560+ return response.sendStatus(500);
561+ }
562+});
563+
535564comfy.post('/generate', async (request, response) => {
536565 try {
537566 let item;