@@ -1065,6 +1065,14 @@ export function createLoraElement(node, loraStr, arrayIdx, loraIdx, availableLor
10651065 metadataBtn . onclick = async ( ) => await showLoraMetadataModal ( node , arrayIdx , parsed . name ) ;
10661066 moreOptionsContent . appendChild ( metadataBtn ) ;
10671067
1068+ // 4. Edit Trigger Words Button
1069+ const editTriggersBtn = document . createElement ( "button" ) ;
1070+ editTriggersBtn . className = "cb-button" ;
1071+ editTriggersBtn . style . cssText = `width: 100%; background: linear-gradient(135deg, #336633, #446644); border-left: 4px solid #66cc66; margin-top: 4px;` ;
1072+ editTriggersBtn . textContent = "✏️ Edit Trigger Words" ;
1073+ editTriggersBtn . onclick = async ( ) => await showEditTriggersModal ( node , arrayIdx , parsed . name ) ;
1074+ moreOptionsContent . appendChild ( editTriggersBtn ) ;
1075+
10681076 moreOptionsSection . appendChild ( moreOptionsContent ) ;
10691077 contentDiv . appendChild ( moreOptionsSection ) ;
10701078 }
@@ -1590,6 +1598,177 @@ async function showModelMetadataModal(node, arrayIdx, modelName, modelType, forc
15901598 modal . appendChild ( closeBtn ) ;
15911599}
15921600
1601+ // ============================================================
1602+ // Trigger Word Editor Modal
1603+ // ============================================================
1604+ async function showEditTriggersModal ( node , arrayIdx , loraName ) {
1605+ // Build modal overlay (same pattern as metadata modals)
1606+ const overlay = document . createElement ( "div" ) ;
1607+ overlay . style . cssText = "position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.85); z-index: 10000; display: flex; align-items: center; justify-content: center;" ;
1608+
1609+ const modal = document . createElement ( "div" ) ;
1610+ modal . style . cssText = "background: #1a1a1a; border: 2px solid #66cc66; border-radius: 12px; padding: 25px; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; position: relative;" ;
1611+
1612+ const closeModal = ( ) => { if ( document . body . contains ( overlay ) ) document . body . removeChild ( overlay ) ; } ;
1613+ overlay . onclick = ( e ) => { if ( e . target === overlay ) closeModal ( ) ; } ;
1614+ document . addEventListener ( "keydown" , function escHandler ( e ) {
1615+ if ( e . key === "Escape" ) { closeModal ( ) ; document . removeEventListener ( "keydown" , escHandler ) ; }
1616+ } ) ;
1617+
1618+ // Close X button
1619+ const closeX = document . createElement ( "button" ) ;
1620+ closeX . textContent = "✕" ;
1621+ closeX . style . cssText = "position: absolute; top: 10px; right: 15px; background: none; border: none; color: #ff4444; font-size: 20px; cursor: pointer;" ;
1622+ closeX . onclick = closeModal ;
1623+ modal . appendChild ( closeX ) ;
1624+
1625+ // Title
1626+ const title = document . createElement ( "h3" ) ;
1627+ title . textContent = "✏️ Edit Trigger Words" ;
1628+ title . style . cssText = "margin: 0 0 5px 0; color: #66cc66;" ;
1629+ modal . appendChild ( title ) ;
1630+
1631+ const subtitle = document . createElement ( "div" ) ;
1632+ subtitle . style . cssText = "font-size: 12px; color: #888; margin-bottom: 15px;" ;
1633+ subtitle . textContent = loraName . split ( '/' ) . pop ( ) ;
1634+ modal . appendChild ( subtitle ) ;
1635+
1636+ const status = document . createElement ( "div" ) ;
1637+ status . textContent = "🔄 Loading trigger words..." ;
1638+ status . style . cssText = "margin-bottom: 15px; color: #aaa; font-size: 12px;" ;
1639+ modal . appendChild ( status ) ;
1640+
1641+ const chipsContainer = document . createElement ( "div" ) ;
1642+ chipsContainer . style . cssText = "display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px; min-height: 30px; padding: 8px; background: #111; border: 1px solid #333; border-radius: 6px;" ;
1643+ modal . appendChild ( chipsContainer ) ;
1644+
1645+ // Input row
1646+ const inputRow = document . createElement ( "div" ) ;
1647+ inputRow . style . cssText = "display: flex; gap: 6px; margin-bottom: 15px;" ;
1648+ const input = document . createElement ( "input" ) ;
1649+ input . className = "cb-input" ;
1650+ input . type = "text" ;
1651+ input . placeholder = "Add new trigger word..." ;
1652+ input . style . cssText = "flex: 1; padding: 6px 10px; font-size: 12px;" ;
1653+ const addWordBtn = document . createElement ( "button" ) ;
1654+ addWordBtn . className = "cb-button primary" ;
1655+ addWordBtn . textContent = "+ Add" ;
1656+ addWordBtn . style . cssText = "padding: 6px 12px; font-size: 12px;" ;
1657+ inputRow . appendChild ( input ) ;
1658+ inputRow . appendChild ( addWordBtn ) ;
1659+ modal . appendChild ( inputRow ) ;
1660+
1661+ // Button row
1662+ const btnRow = document . createElement ( "div" ) ;
1663+ btnRow . style . cssText = "display: flex; gap: 8px; justify-content: flex-end; margin-top: 10px;" ;
1664+
1665+ const saveBtn = document . createElement ( "button" ) ;
1666+ saveBtn . className = "cb-button" ;
1667+ saveBtn . style . cssText = "background: #336633; border: 1px solid #66cc66; color: #88ff88; padding: 8px 20px;" ;
1668+ saveBtn . textContent = "💾 Save" ;
1669+
1670+ const cancelBtn = document . createElement ( "button" ) ;
1671+ cancelBtn . className = "cb-button" ;
1672+ cancelBtn . style . cssText = "background: #333; color: #aaa; padding: 8px 20px;" ;
1673+ cancelBtn . textContent = "Cancel" ;
1674+ cancelBtn . onclick = closeModal ;
1675+
1676+ btnRow . appendChild ( cancelBtn ) ;
1677+ btnRow . appendChild ( saveBtn ) ;
1678+ modal . appendChild ( btnRow ) ;
1679+
1680+ overlay . appendChild ( modal ) ;
1681+ document . body . appendChild ( overlay ) ;
1682+
1683+ // State
1684+ let currentTriggers = [ ] ;
1685+
1686+ function renderTriggerChips ( ) {
1687+ chipsContainer . innerHTML = "" ;
1688+ if ( currentTriggers . length === 0 ) {
1689+ const empty = document . createElement ( "span" ) ;
1690+ empty . style . cssText = "color: #666; font-size: 11px; font-style: italic;" ;
1691+ empty . textContent = "No trigger words" ;
1692+ chipsContainer . appendChild ( empty ) ;
1693+ return ;
1694+ }
1695+ currentTriggers . forEach ( ( trigger , idx ) => {
1696+ const chip = document . createElement ( "span" ) ;
1697+ chip . style . cssText = "background: #224422; border: 1px solid #448844; color: #aaffaa; padding: 3px 8px; border-radius: 12px; font-size: 11px; display: flex; align-items: center; gap: 4px;" ;
1698+ chip . textContent = trigger ;
1699+ const removeBtn = document . createElement ( "span" ) ;
1700+ removeBtn . textContent = "×" ;
1701+ removeBtn . style . cssText = "cursor: pointer; color: #ff6666; font-weight: bold; margin-left: 2px;" ;
1702+ removeBtn . onclick = ( ) => {
1703+ currentTriggers . splice ( idx , 1 ) ;
1704+ renderTriggerChips ( ) ;
1705+ } ;
1706+ chip . appendChild ( removeBtn ) ;
1707+ chipsContainer . appendChild ( chip ) ;
1708+ } ) ;
1709+ }
1710+
1711+ function addTriggerWord ( ) {
1712+ const word = input . value . trim ( ) ;
1713+ if ( word && ! currentTriggers . includes ( word ) ) {
1714+ currentTriggers . push ( word ) ;
1715+ renderTriggerChips ( ) ;
1716+ input . value = "" ;
1717+ }
1718+ input . focus ( ) ;
1719+ }
1720+
1721+ addWordBtn . onclick = addTriggerWord ;
1722+ input . onkeydown = ( e ) => { if ( e . key === "Enter" ) { e . preventDefault ( ) ; addTriggerWord ( ) ; } } ;
1723+
1724+ saveBtn . onclick = async ( ) => {
1725+ saveBtn . disabled = true ;
1726+ saveBtn . textContent = "💾 Saving..." ;
1727+ try {
1728+ const resp = await fetch ( "/configbuilder/save_lora_triggers" , {
1729+ method : "POST" ,
1730+ headers : { "Content-Type" : "application/json" } ,
1731+ body : JSON . stringify ( { lora_name : loraName , triggers : currentTriggers } )
1732+ } ) ;
1733+ if ( resp . ok ) {
1734+ status . textContent = "✅ Trigger words saved!" ;
1735+ status . style . color = "#66ff66" ;
1736+ setTimeout ( closeModal , 800 ) ;
1737+ } else {
1738+ const err = await resp . json ( ) ;
1739+ status . textContent = "❌ Error: " + ( err . error || "Save failed" ) ;
1740+ status . style . color = "#ff6666" ;
1741+ saveBtn . disabled = false ;
1742+ saveBtn . textContent = "💾 Save" ;
1743+ }
1744+ } catch ( e ) {
1745+ status . textContent = "❌ Error: " + e . message ;
1746+ status . style . color = "#ff6666" ;
1747+ saveBtn . disabled = false ;
1748+ saveBtn . textContent = "💾 Save" ;
1749+ }
1750+ } ;
1751+
1752+ // Fetch current triggers
1753+ try {
1754+ const resp = await fetch ( `/configbuilder/get_lora_triggers?lora_name=${ encodeURIComponent ( loraName ) } ` ) ;
1755+ if ( resp . ok ) {
1756+ const data = await resp . json ( ) ;
1757+ currentTriggers = data . triggers || [ ] ;
1758+ status . textContent = `Loaded ${ currentTriggers . length } trigger word(s)` ;
1759+ status . style . color = "#88ff88" ;
1760+ } else {
1761+ status . textContent = "⚠️ No triggers found (you can add new ones)" ;
1762+ status . style . color = "#ffaa00" ;
1763+ }
1764+ } catch ( e ) {
1765+ status . textContent = "⚠️ Could not load triggers: " + e . message ;
1766+ status . style . color = "#ffaa00" ;
1767+ }
1768+ renderTriggerChips ( ) ;
1769+ input . focus ( ) ;
1770+ }
1771+
15931772
15941773// --- RENDER MODELS AND LORAS SECTIONS ---
15951774
0 commit comments