mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-25 04:21:17 -03:00
feat(ui): add send-prompt-to-workflow button for prompt and negative prompt
- Add sendPromptToWorkflow() and stripLoraTags() exports to uiHelpers.js - Add send button (paper-plane icon) to recipe modal and showcase hover panel - Restructure showcase metadata panel layout to match recipe modal style - Respect strip <lora:> setting before sending - Uses 'replace' mode (not append) on text-capable workflow nodes - Add translations for all 10 locales
This commit is contained in:
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "Modell im Workflow aktualisiert",
|
"modelUpdated": "Modell im Workflow aktualisiert",
|
||||||
"modelFailed": "Fehler beim Aktualisieren des Modellknotens",
|
"modelFailed": "Fehler beim Aktualisieren des Modellknotens",
|
||||||
"embeddingAdded": "Embedding zum Workflow hinzugefügt",
|
"embeddingAdded": "Embedding zum Workflow hinzugefügt",
|
||||||
"embeddingFailed": "Fehler beim Hinzufügen des Embeddings"
|
"embeddingFailed": "Fehler beim Hinzufügen des Embeddings",
|
||||||
|
"promptSent": "Prompt an Workflow gesendet",
|
||||||
|
"promptFailed": "Fehler beim Senden des Prompts"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Rezept",
|
"recipe": "Rezept",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "Prompt",
|
||||||
"replace": "Ersetzen",
|
"replace": "Ersetzen",
|
||||||
"append": "Anhängen",
|
"append": "Anhängen",
|
||||||
"selectTargetNode": "Zielknoten auswählen",
|
"selectTargetNode": "Zielknoten auswählen",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "Bitte geben Sie einen LoRA-Namen oder Syntax ein",
|
"enterLoraName": "Bitte geben Sie einen LoRA-Namen oder Syntax ein",
|
||||||
"reconnectedSuccessfully": "LoRA erfolgreich neu verbunden",
|
"reconnectedSuccessfully": "LoRA erfolgreich neu verbunden",
|
||||||
"reconnectFailed": "Fehler beim Neuverbinden des LoRA: {message}",
|
"reconnectFailed": "Fehler beim Neuverbinden des LoRA: {message}",
|
||||||
|
"noPromptToSend": "Kein zu sendender Prompt",
|
||||||
"cannotSend": "Kann Rezept nicht senden: Fehlende Rezept-ID",
|
"cannotSend": "Kann Rezept nicht senden: Fehlende Rezept-ID",
|
||||||
"sendFailed": "Fehler beim Senden des Rezepts an Workflow",
|
"sendFailed": "Fehler beim Senden des Rezepts an Workflow",
|
||||||
"sendError": "Fehler beim Senden des Rezepts an Workflow",
|
"sendError": "Fehler beim Senden des Rezepts an Workflow",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "Model updated in workflow",
|
"modelUpdated": "Model updated in workflow",
|
||||||
"modelFailed": "Failed to update model node",
|
"modelFailed": "Failed to update model node",
|
||||||
"embeddingAdded": "Embedding added to workflow",
|
"embeddingAdded": "Embedding added to workflow",
|
||||||
"embeddingFailed": "Failed to add embedding"
|
"embeddingFailed": "Failed to add embedding",
|
||||||
|
"promptSent": "Prompt sent to workflow",
|
||||||
|
"promptFailed": "Failed to send prompt"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Recipe",
|
"recipe": "Recipe",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "Prompt",
|
||||||
"replace": "Replace",
|
"replace": "Replace",
|
||||||
"append": "Append",
|
"append": "Append",
|
||||||
"selectTargetNode": "Select target node",
|
"selectTargetNode": "Select target node",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "Please enter a LoRA name or syntax",
|
"enterLoraName": "Please enter a LoRA name or syntax",
|
||||||
"reconnectedSuccessfully": "LoRA reconnected successfully",
|
"reconnectedSuccessfully": "LoRA reconnected successfully",
|
||||||
"reconnectFailed": "Error reconnecting LoRA: {message}",
|
"reconnectFailed": "Error reconnecting LoRA: {message}",
|
||||||
|
"noPromptToSend": "No prompt to send",
|
||||||
"cannotSend": "Cannot send recipe: Missing recipe ID",
|
"cannotSend": "Cannot send recipe: Missing recipe ID",
|
||||||
"sendFailed": "Failed to send recipe to workflow",
|
"sendFailed": "Failed to send recipe to workflow",
|
||||||
"sendError": "Error sending recipe to workflow",
|
"sendError": "Error sending recipe to workflow",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "Modelo actualizado en el flujo de trabajo",
|
"modelUpdated": "Modelo actualizado en el flujo de trabajo",
|
||||||
"modelFailed": "Error al actualizar nodo de modelo",
|
"modelFailed": "Error al actualizar nodo de modelo",
|
||||||
"embeddingAdded": "Embedding añadido al flujo de trabajo",
|
"embeddingAdded": "Embedding añadido al flujo de trabajo",
|
||||||
"embeddingFailed": "Error al añadir el embedding"
|
"embeddingFailed": "Error al añadir el embedding",
|
||||||
|
"promptSent": "Prompt enviado al flujo de trabajo",
|
||||||
|
"promptFailed": "Error al enviar el prompt"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Receta",
|
"recipe": "Receta",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "Prompt",
|
||||||
"replace": "Reemplazar",
|
"replace": "Reemplazar",
|
||||||
"append": "Añadir",
|
"append": "Añadir",
|
||||||
"selectTargetNode": "Seleccionar nodo de destino",
|
"selectTargetNode": "Seleccionar nodo de destino",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "Por favor introduce un nombre de LoRA o sintaxis",
|
"enterLoraName": "Por favor introduce un nombre de LoRA o sintaxis",
|
||||||
"reconnectedSuccessfully": "LoRA reconectado exitosamente",
|
"reconnectedSuccessfully": "LoRA reconectado exitosamente",
|
||||||
"reconnectFailed": "Error reconectando LoRA: {message}",
|
"reconnectFailed": "Error reconectando LoRA: {message}",
|
||||||
|
"noPromptToSend": "No hay prompt para enviar",
|
||||||
"cannotSend": "No se puede enviar receta: Falta ID de receta",
|
"cannotSend": "No se puede enviar receta: Falta ID de receta",
|
||||||
"sendFailed": "Error al enviar receta al flujo de trabajo",
|
"sendFailed": "Error al enviar receta al flujo de trabajo",
|
||||||
"sendError": "Error enviando receta al flujo de trabajo",
|
"sendError": "Error enviando receta al flujo de trabajo",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "Modèle mis à jour dans le workflow",
|
"modelUpdated": "Modèle mis à jour dans le workflow",
|
||||||
"modelFailed": "Échec de la mise à jour du nœud modèle",
|
"modelFailed": "Échec de la mise à jour du nœud modèle",
|
||||||
"embeddingAdded": "Embedding ajouté au workflow",
|
"embeddingAdded": "Embedding ajouté au workflow",
|
||||||
"embeddingFailed": "Échec de l'ajout de l'embedding"
|
"embeddingFailed": "Échec de l'ajout de l'embedding",
|
||||||
|
"promptSent": "Prompt envoyé au workflow",
|
||||||
|
"promptFailed": "Échec de l'envoi du prompt"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Recipe",
|
"recipe": "Recipe",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "Prompt",
|
||||||
"replace": "Remplacer",
|
"replace": "Remplacer",
|
||||||
"append": "Ajouter",
|
"append": "Ajouter",
|
||||||
"selectTargetNode": "Sélectionner le nœud cible",
|
"selectTargetNode": "Sélectionner le nœud cible",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "Veuillez entrer un nom ou une syntaxe LoRA",
|
"enterLoraName": "Veuillez entrer un nom ou une syntaxe LoRA",
|
||||||
"reconnectedSuccessfully": "LoRA reconnecté avec succès",
|
"reconnectedSuccessfully": "LoRA reconnecté avec succès",
|
||||||
"reconnectFailed": "Erreur lors de la reconnexion du LoRA : {message}",
|
"reconnectFailed": "Erreur lors de la reconnexion du LoRA : {message}",
|
||||||
|
"noPromptToSend": "Aucun prompt à envoyer",
|
||||||
"cannotSend": "Impossible d'envoyer la recipe : ID de recipe manquant",
|
"cannotSend": "Impossible d'envoyer la recipe : ID de recipe manquant",
|
||||||
"sendFailed": "Échec de l'envoi de la recipe vers le workflow",
|
"sendFailed": "Échec de l'envoi de la recipe vers le workflow",
|
||||||
"sendError": "Erreur lors de l'envoi de la recipe vers le workflow",
|
"sendError": "Erreur lors de l'envoi de la recipe vers le workflow",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "מודל עודכן ב-workflow",
|
"modelUpdated": "מודל עודכן ב-workflow",
|
||||||
"modelFailed": "עדכון צומת המודל נכשל",
|
"modelFailed": "עדכון צומת המודל נכשל",
|
||||||
"embeddingAdded": "Embedding נוסף ל-workflow",
|
"embeddingAdded": "Embedding נוסף ל-workflow",
|
||||||
"embeddingFailed": "הוספת Embedding נכשלה"
|
"embeddingFailed": "הוספת Embedding נכשלה",
|
||||||
|
"promptSent": "הנחיה נשלחה ל-workflow",
|
||||||
|
"promptFailed": "שליחת ההנחיה נכשלה"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "מתכון",
|
"recipe": "מתכון",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "הנחיה",
|
||||||
"replace": "החלף",
|
"replace": "החלף",
|
||||||
"append": "הוסף",
|
"append": "הוסף",
|
||||||
"selectTargetNode": "בחר צומת יעד",
|
"selectTargetNode": "בחר צומת יעד",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "אנא הזן שם LoRA או תחביר",
|
"enterLoraName": "אנא הזן שם LoRA או תחביר",
|
||||||
"reconnectedSuccessfully": "LoRA קושר מחדש בהצלחה",
|
"reconnectedSuccessfully": "LoRA קושר מחדש בהצלחה",
|
||||||
"reconnectFailed": "שגיאה בקישור מחדש של LoRA: {message}",
|
"reconnectFailed": "שגיאה בקישור מחדש של LoRA: {message}",
|
||||||
|
"noPromptToSend": "אין הנחיה לשליחה",
|
||||||
"cannotSend": "לא ניתן לשלוח מתכון: חסר מזהה מתכון",
|
"cannotSend": "לא ניתן לשלוח מתכון: חסר מזהה מתכון",
|
||||||
"sendFailed": "שליחת המתכון ל-workflow נכשלה",
|
"sendFailed": "שליחת המתכון ל-workflow נכשלה",
|
||||||
"sendError": "שגיאה בשליחת המתכון ל-workflow",
|
"sendError": "שגיאה בשליחת המתכון ל-workflow",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "モデルがワークフローで更新されました",
|
"modelUpdated": "モデルがワークフローで更新されました",
|
||||||
"modelFailed": "モデルノードの更新に失敗しました",
|
"modelFailed": "モデルノードの更新に失敗しました",
|
||||||
"embeddingAdded": "Embeddingをワークフローに追加しました",
|
"embeddingAdded": "Embeddingをワークフローに追加しました",
|
||||||
"embeddingFailed": "Embeddingの追加に失敗しました"
|
"embeddingFailed": "Embeddingの追加に失敗しました",
|
||||||
|
"promptSent": "プロンプトをワークフローに送信しました",
|
||||||
|
"promptFailed": "プロンプトの送信に失敗しました"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "レシピ",
|
"recipe": "レシピ",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "プロンプト",
|
||||||
"replace": "置換",
|
"replace": "置換",
|
||||||
"append": "追加",
|
"append": "追加",
|
||||||
"selectTargetNode": "ターゲットノードを選択",
|
"selectTargetNode": "ターゲットノードを選択",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "LoRA名または構文を入力してください",
|
"enterLoraName": "LoRA名または構文を入力してください",
|
||||||
"reconnectedSuccessfully": "LoRAが正常に再接続されました",
|
"reconnectedSuccessfully": "LoRAが正常に再接続されました",
|
||||||
"reconnectFailed": "LoRA再接続エラー:{message}",
|
"reconnectFailed": "LoRA再接続エラー:{message}",
|
||||||
|
"noPromptToSend": "送信するプロンプトがありません",
|
||||||
"cannotSend": "レシピを送信できません:レシピIDがありません",
|
"cannotSend": "レシピを送信できません:レシピIDがありません",
|
||||||
"sendFailed": "レシピのワークフローへの送信に失敗しました",
|
"sendFailed": "レシピのワークフローへの送信に失敗しました",
|
||||||
"sendError": "レシピのワークフロー送信エラー",
|
"sendError": "レシピのワークフロー送信エラー",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "모델이 워크플로에서 업데이트되었습니다",
|
"modelUpdated": "모델이 워크플로에서 업데이트되었습니다",
|
||||||
"modelFailed": "모델 노드 업데이트 실패",
|
"modelFailed": "모델 노드 업데이트 실패",
|
||||||
"embeddingAdded": "Embedding을 워크플로에 추가했습니다",
|
"embeddingAdded": "Embedding을 워크플로에 추가했습니다",
|
||||||
"embeddingFailed": "Embedding 추가 실패"
|
"embeddingFailed": "Embedding 추가 실패",
|
||||||
|
"promptSent": "프롬프트를 워크플로에 보냈습니다",
|
||||||
|
"promptFailed": "프롬프트 보내기 실패"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "레시피",
|
"recipe": "레시피",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "임베딩",
|
"embedding": "임베딩",
|
||||||
|
"prompt": "프롬프트",
|
||||||
"replace": "교체",
|
"replace": "교체",
|
||||||
"append": "추가",
|
"append": "추가",
|
||||||
"selectTargetNode": "대상 노드 선택",
|
"selectTargetNode": "대상 노드 선택",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "LoRA 이름 또는 문법을 입력해주세요",
|
"enterLoraName": "LoRA 이름 또는 문법을 입력해주세요",
|
||||||
"reconnectedSuccessfully": "LoRA가 성공적으로 다시 연결되었습니다",
|
"reconnectedSuccessfully": "LoRA가 성공적으로 다시 연결되었습니다",
|
||||||
"reconnectFailed": "LoRA 다시 연결 오류: {message}",
|
"reconnectFailed": "LoRA 다시 연결 오류: {message}",
|
||||||
|
"noPromptToSend": "보낼 프롬프트가 없습니다",
|
||||||
"cannotSend": "레시피를 전송할 수 없습니다: 레시피 ID 누락",
|
"cannotSend": "레시피를 전송할 수 없습니다: 레시피 ID 누락",
|
||||||
"sendFailed": "레시피를 워크플로로 전송하는데 실패했습니다",
|
"sendFailed": "레시피를 워크플로로 전송하는데 실패했습니다",
|
||||||
"sendError": "레시피를 워크플로로 전송하는 중 오류",
|
"sendError": "레시피를 워크플로로 전송하는 중 오류",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "Модель обновлена в workflow",
|
"modelUpdated": "Модель обновлена в workflow",
|
||||||
"modelFailed": "Не удалось обновить узел модели",
|
"modelFailed": "Не удалось обновить узел модели",
|
||||||
"embeddingAdded": "Embedding добавлен в workflow",
|
"embeddingAdded": "Embedding добавлен в workflow",
|
||||||
"embeddingFailed": "Не удалось добавить embedding"
|
"embeddingFailed": "Не удалось добавить embedding",
|
||||||
|
"promptSent": "Запрос отправлен в workflow",
|
||||||
|
"promptFailed": "Не удалось отправить запрос"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Рецепт",
|
"recipe": "Рецепт",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Эмбеддинг",
|
"embedding": "Эмбеддинг",
|
||||||
|
"prompt": "Запрос",
|
||||||
"replace": "Заменить",
|
"replace": "Заменить",
|
||||||
"append": "Добавить",
|
"append": "Добавить",
|
||||||
"selectTargetNode": "Выберите целевой узел",
|
"selectTargetNode": "Выберите целевой узел",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "Пожалуйста, введите название LoRA или синтаксис",
|
"enterLoraName": "Пожалуйста, введите название LoRA или синтаксис",
|
||||||
"reconnectedSuccessfully": "LoRA успешно переподключена",
|
"reconnectedSuccessfully": "LoRA успешно переподключена",
|
||||||
"reconnectFailed": "Ошибка переподключения LoRA: {message}",
|
"reconnectFailed": "Ошибка переподключения LoRA: {message}",
|
||||||
|
"noPromptToSend": "Нет запроса для отправки",
|
||||||
"cannotSend": "Невозможно отправить рецепт: отсутствует ID рецепта",
|
"cannotSend": "Невозможно отправить рецепт: отсутствует ID рецепта",
|
||||||
"sendFailed": "Не удалось отправить рецепт в workflow",
|
"sendFailed": "Не удалось отправить рецепт в workflow",
|
||||||
"sendError": "Ошибка отправки рецепта в workflow",
|
"sendError": "Ошибка отправки рецепта в workflow",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "模型已更新到工作流",
|
"modelUpdated": "模型已更新到工作流",
|
||||||
"modelFailed": "更新模型节点失败",
|
"modelFailed": "更新模型节点失败",
|
||||||
"embeddingAdded": "Embedding 已追加到工作流",
|
"embeddingAdded": "Embedding 已追加到工作流",
|
||||||
"embeddingFailed": "添加 Embedding 失败"
|
"embeddingFailed": "添加 Embedding 失败",
|
||||||
|
"promptSent": "提示词已发送到工作流",
|
||||||
|
"promptFailed": "提示词发送失败"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "配方",
|
"recipe": "配方",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "提示词",
|
||||||
"replace": "替换",
|
"replace": "替换",
|
||||||
"append": "追加",
|
"append": "追加",
|
||||||
"selectTargetNode": "选择目标节点",
|
"selectTargetNode": "选择目标节点",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "请输入 LoRA 名称或语法",
|
"enterLoraName": "请输入 LoRA 名称或语法",
|
||||||
"reconnectedSuccessfully": "LoRA 重新连接成功",
|
"reconnectedSuccessfully": "LoRA 重新连接成功",
|
||||||
"reconnectFailed": "LoRA 重新连接出错:{message}",
|
"reconnectFailed": "LoRA 重新连接出错:{message}",
|
||||||
|
"noPromptToSend": "没有可发送的提示词",
|
||||||
"cannotSend": "无法发送配方:缺少配方 ID",
|
"cannotSend": "无法发送配方:缺少配方 ID",
|
||||||
"sendFailed": "发送配方到工作流失败",
|
"sendFailed": "发送配方到工作流失败",
|
||||||
"sendError": "发送配方到工作流出错",
|
"sendError": "发送配方到工作流出错",
|
||||||
|
|||||||
@@ -1620,12 +1620,15 @@
|
|||||||
"modelUpdated": "模型已更新到工作流",
|
"modelUpdated": "模型已更新到工作流",
|
||||||
"modelFailed": "更新模型節點失敗",
|
"modelFailed": "更新模型節點失敗",
|
||||||
"embeddingAdded": "Embedding 已附加到工作流",
|
"embeddingAdded": "Embedding 已附加到工作流",
|
||||||
"embeddingFailed": "傳送 Embedding 到工作流失敗"
|
"embeddingFailed": "傳送 Embedding 到工作流失敗",
|
||||||
|
"promptSent": "提示詞已發送到工作流",
|
||||||
|
"promptFailed": "提示詞發送失敗"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "配方",
|
"recipe": "配方",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
"embedding": "Embedding",
|
"embedding": "Embedding",
|
||||||
|
"prompt": "提示詞",
|
||||||
"replace": "取代",
|
"replace": "取代",
|
||||||
"append": "附加",
|
"append": "附加",
|
||||||
"selectTargetNode": "選擇目標節點",
|
"selectTargetNode": "選擇目標節點",
|
||||||
@@ -1812,6 +1815,7 @@
|
|||||||
"enterLoraName": "請輸入 LoRA 名稱或語法",
|
"enterLoraName": "請輸入 LoRA 名稱或語法",
|
||||||
"reconnectedSuccessfully": "LoRA 重新連結成功",
|
"reconnectedSuccessfully": "LoRA 重新連結成功",
|
||||||
"reconnectFailed": "LoRA 重新連結錯誤:{message}",
|
"reconnectFailed": "LoRA 重新連結錯誤:{message}",
|
||||||
|
"noPromptToSend": "沒有可發送的提示詞",
|
||||||
"cannotSend": "無法傳送配方:缺少配方 ID",
|
"cannotSend": "無法傳送配方:缺少配方 ID",
|
||||||
"sendFailed": "傳送配方到工作流失敗",
|
"sendFailed": "傳送配方到工作流失敗",
|
||||||
"sendError": "傳送配方到工作流錯誤",
|
"sendError": "傳送配方到工作流錯誤",
|
||||||
|
|||||||
@@ -272,13 +272,25 @@
|
|||||||
margin-top: var(--space-2);
|
margin-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metadata-row.prompt-row .param-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-row.prompt-row .param-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.metadata-label {
|
.metadata-label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
display: block;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadata-prompt-wrapper {
|
.metadata-prompt-wrapper {
|
||||||
@@ -286,7 +298,7 @@
|
|||||||
background: var(--lora-surface);
|
background: var(--lora-surface);
|
||||||
border: 1px solid var(--lora-border);
|
border: 1px solid var(--lora-border);
|
||||||
border-radius: var(--border-radius-xs);
|
border-radius: var(--border-radius-xs);
|
||||||
padding: 6px 30px 6px 8px;
|
padding: 6px 8px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
max-height: 80px; /* Reduced from 120px */
|
max-height: 80px; /* Reduced from 120px */
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -302,22 +314,24 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-prompt-btn {
|
.copy-prompt-btn,
|
||||||
position: absolute;
|
.send-prompt-btn {
|
||||||
top: 6px;
|
|
||||||
right: 6px;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 3px;
|
padding: 3px 6px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
transition: var(--transition-base);
|
transition: var(--transition-base);
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-prompt-btn:hover {
|
.copy-prompt-btn:hover,
|
||||||
|
.send-prompt-btn:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: var(--lora-accent);
|
color: var(--lora-accent);
|
||||||
|
background: var(--lora-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar styling for metadata panel */
|
/* Scrollbar styling for metadata panel */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Recipe Modal Component
|
// Recipe Modal Component
|
||||||
import { showToast, copyToClipboard, sendLoraToWorkflow, sendModelPathToWorkflow, openCivitaiByMetadata } from '../utils/uiHelpers.js';
|
import { showToast, copyToClipboard, sendLoraToWorkflow, sendModelPathToWorkflow, openCivitaiByMetadata, stripLoraTags, sendPromptToWorkflow } from '../utils/uiHelpers.js';
|
||||||
import { translate } from '../utils/i18nHelpers.js';
|
import { translate } from '../utils/i18nHelpers.js';
|
||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
import { setSessionItem, removeSessionItem, getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
import { setSessionItem, removeSessionItem, getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
||||||
@@ -1200,6 +1200,40 @@ class RecipeModal {
|
|||||||
this.sendRecipeToWorkflow();
|
this.sendRecipeToWorkflow();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send prompt to workflow buttons
|
||||||
|
const sendPromptBtn = document.getElementById('sendPromptBtn');
|
||||||
|
const sendNegativePromptBtn = document.getElementById('sendNegativePromptBtn');
|
||||||
|
|
||||||
|
if (sendPromptBtn) {
|
||||||
|
sendPromptBtn.addEventListener('click', () => {
|
||||||
|
let promptText = this.currentRecipe?.gen_params?.prompt || '';
|
||||||
|
if (this.shouldStripLoraOnCopy()) {
|
||||||
|
promptText = RecipeModal.stripLoraTags(promptText);
|
||||||
|
}
|
||||||
|
if (!promptText.trim()) {
|
||||||
|
showToast('toast.recipes.noPromptToSend', {}, 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendPromptToWorkflow(promptText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendNegativePromptBtn) {
|
||||||
|
sendNegativePromptBtn.addEventListener('click', () => {
|
||||||
|
let negativePromptText = this.currentRecipe?.gen_params?.negative_prompt || '';
|
||||||
|
if (this.shouldStripLoraOnCopy()) {
|
||||||
|
negativePromptText = RecipeModal.stripLoraTags(negativePromptText);
|
||||||
|
}
|
||||||
|
if (!negativePromptText.trim()) {
|
||||||
|
showToast('toast.recipes.noPromptToSend', {}, 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendPromptToWorkflow(negativePromptText, {
|
||||||
|
actionTypeText: 'Negative Prompt',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1208,14 +1242,7 @@ class RecipeModal {
|
|||||||
* Cleans up artifacts like leading ", ", double commas, and extra whitespace.
|
* Cleans up artifacts like leading ", ", double commas, and extra whitespace.
|
||||||
*/
|
*/
|
||||||
static stripLoraTags(text) {
|
static stripLoraTags(text) {
|
||||||
return text
|
return stripLoraTags(text);
|
||||||
.replace(/<lora:[^>]*>/gi, '')
|
|
||||||
.replace(/<lora:[^&]*>/gi, '')
|
|
||||||
.replace(/,(\s*,)+/g, ',')
|
|
||||||
.replace(/^,\s*/, '')
|
|
||||||
.replace(/,\s*$/, '')
|
|
||||||
.replace(/\s{2,}/g, ' ')
|
|
||||||
.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldStripLoraOnCopy() {
|
shouldStripLoraOnCopy() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Media-specific utility functions for showcase components
|
* Media-specific utility functions for showcase components
|
||||||
* (Moved from uiHelpers.js to better organize code)
|
* (Moved from uiHelpers.js to better organize code)
|
||||||
*/
|
*/
|
||||||
import { showToast, copyToClipboard, getNSFWLevelName } from '../../../utils/uiHelpers.js';
|
import { showToast, copyToClipboard, getNSFWLevelName, sendPromptToWorkflow, stripLoraTags } from '../../../utils/uiHelpers.js';
|
||||||
import { state } from '../../../state/index.js';
|
import { state } from '../../../state/index.js';
|
||||||
import { getModelApiClient } from '../../../api/modelApiFactory.js';
|
import { getModelApiClient } from '../../../api/modelApiFactory.js';
|
||||||
import { NSFW_LEVELS, getMatureBlurThreshold } from '../../../utils/constants.js';
|
import { NSFW_LEVELS, getMatureBlurThreshold } from '../../../utils/constants.js';
|
||||||
@@ -318,6 +318,32 @@ export function initMetadataPanelHandlers(container) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle send prompt buttons
|
||||||
|
const sendBtns = metadataPanel.querySelectorAll('.send-prompt-btn');
|
||||||
|
sendBtns.forEach(sendBtn => {
|
||||||
|
const promptIndex = sendBtn.dataset.promptIndex;
|
||||||
|
const promptElement = wrapper.querySelector(`#prompt-${promptIndex}`);
|
||||||
|
|
||||||
|
sendBtn.addEventListener('click', async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!promptElement) return;
|
||||||
|
|
||||||
|
let promptText = promptElement.textContent || '';
|
||||||
|
if (!promptText.trim()) {
|
||||||
|
showToast('toast.recipes.noPromptToSend', {}, 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respect strip <lora> setting from global state
|
||||||
|
if (state.global.settings?.strip_lora_on_copy) {
|
||||||
|
promptText = stripLoraTags(promptText);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPromptToWorkflow(promptText);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Prevent panel scroll from causing modal scroll
|
// Prevent panel scroll from causing modal scroll
|
||||||
metadataPanel.addEventListener('wheel', (e) => {
|
metadataPanel.addEventListener('wheel', (e) => {
|
||||||
const isAtTop = metadataPanel.scrollTop === 0;
|
const isAtTop = metadataPanel.scrollTop === 0;
|
||||||
|
|||||||
@@ -53,12 +53,19 @@ export function generateMetadataPanel(hasParams, hasPrompts, prompt, negativePro
|
|||||||
prompt = escapeHtml(prompt);
|
prompt = escapeHtml(prompt);
|
||||||
content += `
|
content += `
|
||||||
<div class="metadata-row prompt-row">
|
<div class="metadata-row prompt-row">
|
||||||
<span class="metadata-label">Prompt:</span>
|
<div class="param-header">
|
||||||
|
<span class="metadata-label">Prompt:</span>
|
||||||
|
<div class="param-actions">
|
||||||
|
<button class="send-prompt-btn" data-prompt-index="${promptIndex}" title="Send Prompt to Workflow">
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
</button>
|
||||||
|
<button class="copy-prompt-btn" data-prompt-index="${promptIndex}" title="Copy Prompt">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="metadata-prompt-wrapper">
|
<div class="metadata-prompt-wrapper">
|
||||||
<div class="metadata-prompt">${prompt}</div>
|
<div class="metadata-prompt">${prompt}</div>
|
||||||
<button class="copy-prompt-btn" data-prompt-index="${promptIndex}">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-prompt" id="prompt-${promptIndex}" style="display:none;">${prompt}</div>
|
<div class="hidden-prompt" id="prompt-${promptIndex}" style="display:none;">${prompt}</div>
|
||||||
@@ -69,12 +76,19 @@ export function generateMetadataPanel(hasParams, hasPrompts, prompt, negativePro
|
|||||||
negativePrompt = escapeHtml(negativePrompt);
|
negativePrompt = escapeHtml(negativePrompt);
|
||||||
content += `
|
content += `
|
||||||
<div class="metadata-row prompt-row">
|
<div class="metadata-row prompt-row">
|
||||||
<span class="metadata-label">Negative Prompt:</span>
|
<div class="param-header">
|
||||||
|
<span class="metadata-label">Negative Prompt:</span>
|
||||||
|
<div class="param-actions">
|
||||||
|
<button class="send-prompt-btn" data-prompt-index="${negPromptIndex}" title="Send Negative Prompt to Workflow">
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
</button>
|
||||||
|
<button class="copy-prompt-btn" data-prompt-index="${negPromptIndex}" title="Copy Negative Prompt">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="metadata-prompt-wrapper">
|
<div class="metadata-prompt-wrapper">
|
||||||
<div class="metadata-prompt">${negativePrompt}</div>
|
<div class="metadata-prompt">${negativePrompt}</div>
|
||||||
<button class="copy-prompt-btn" data-prompt-index="${negPromptIndex}">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-prompt" id="prompt-${negPromptIndex}" style="display:none;">${negativePrompt}</div>
|
<div class="hidden-prompt" id="prompt-${negPromptIndex}" style="display:none;">${negativePrompt}</div>
|
||||||
|
|||||||
@@ -518,6 +518,22 @@ export function copyLoraSyntax(card) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip <lora:...> tags from prompt text and clean up residual punctuation/whitespace.
|
||||||
|
* Handles both unescaped (<lora:...>) and HTML-escaped (<lora:...>) variants.
|
||||||
|
* Cleans up artifacts like leading ", ", double commas, and extra whitespace.
|
||||||
|
*/
|
||||||
|
export function stripLoraTags(text) {
|
||||||
|
return text
|
||||||
|
.replace(/<lora:[^>]*>/gi, '')
|
||||||
|
.replace(/<lora:[^&]*>/gi, '')
|
||||||
|
.replace(/,(\s*,)+/g, ',')
|
||||||
|
.replace(/^,\s*/, '')
|
||||||
|
.replace(/,\s*$/, '')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchWorkflowRegistry() {
|
async function fetchWorkflowRegistry() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/lm/get-registry');
|
const response = await fetch('/api/lm/get-registry');
|
||||||
@@ -983,6 +999,63 @@ export async function sendEmbeddingToWorkflow(embeddingCode) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send prompt text to workflow text-capable nodes (replaces existing content).
|
||||||
|
* Uses the same target node discovery as sendEmbeddingToWorkflow.
|
||||||
|
* @param {string} promptText - The prompt/negative prompt text to send
|
||||||
|
* @param {Object} [options] - Optional messages overrides
|
||||||
|
* @param {string} [options.actionTypeText] - Label for the action type (default "Prompt")
|
||||||
|
* @param {string} [options.successMessage] - Success toast message
|
||||||
|
* @param {string} [options.failureMessage] - Failure toast message
|
||||||
|
* @param {string} [options.missingNodesMessage] - No nodes warning message
|
||||||
|
* @param {string} [options.missingTargetMessage] - No target selected warning message
|
||||||
|
* @returns {Promise<boolean>} Whether the send succeeded
|
||||||
|
*/
|
||||||
|
export async function sendPromptToWorkflow(promptText, options = {}) {
|
||||||
|
const registry = await fetchWorkflowRegistry();
|
||||||
|
if (!registry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textNodes = filterRegistryNodes(registry.nodes, (node) => {
|
||||||
|
if (!isNodeEnabled(node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
node.capabilities?.has_text_widget === true ||
|
||||||
|
node.marker_role === "send_prompt_target"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeKeys = Object.keys(textNodes);
|
||||||
|
if (nodeKeys.length === 0) {
|
||||||
|
showToast(options.missingNodesMessage || 'uiHelpers.workflow.noMatchingNodes', {}, 'warning');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
successMessage: options.successMessage || translate('uiHelpers.workflow.promptSent', {}, 'Prompt sent to workflow'),
|
||||||
|
failureMessage: options.failureMessage || translate('uiHelpers.workflow.promptFailed', {}, 'Failed to send prompt'),
|
||||||
|
missingTargetMessage: options.missingTargetMessage || translate('uiHelpers.workflow.noTargetNodeSelected', {}, 'No target node selected'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSend = (selectedNodeIds) =>
|
||||||
|
sendTextToNodes(selectedNodeIds, textNodes, promptText, 'replace', messages);
|
||||||
|
|
||||||
|
if (nodeKeys.length === 1) {
|
||||||
|
return await handleSend([nodeKeys[0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionType = options.actionTypeText || translate('uiHelpers.nodeSelector.prompt', {}, 'Prompt');
|
||||||
|
|
||||||
|
showNodeSelector(textNodes, {
|
||||||
|
actionType,
|
||||||
|
actionMode: translate('uiHelpers.nodeSelector.replace', {}, 'Replace'),
|
||||||
|
onSend: handleSend,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Global variable to track active node selector state
|
// Global variable to track active node selector state
|
||||||
let nodeSelectorState = {
|
let nodeSelectorState = {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<div class="param-header">
|
<div class="param-header">
|
||||||
<label>Prompt</label>
|
<label>Prompt</label>
|
||||||
<div class="param-actions">
|
<div class="param-actions">
|
||||||
|
<button class="copy-btn" id="sendPromptBtn" title="Send Prompt to Workflow">
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
</button>
|
||||||
<button class="copy-btn" id="copyPromptBtn" title="Copy Prompt">
|
<button class="copy-btn" id="copyPromptBtn" title="Copy Prompt">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -62,6 +65,9 @@
|
|||||||
<div class="param-header">
|
<div class="param-header">
|
||||||
<label>Negative Prompt</label>
|
<label>Negative Prompt</label>
|
||||||
<div class="param-actions">
|
<div class="param-actions">
|
||||||
|
<button class="copy-btn" id="sendNegativePromptBtn" title="Send Negative Prompt to Workflow">
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
</button>
|
||||||
<button class="copy-btn" id="copyNegativePromptBtn" title="Copy Negative Prompt">
|
<button class="copy-btn" id="copyNegativePromptBtn" title="Copy Negative Prompt">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user