| 1 | <html> |
| 2 | <head> |
| 3 | <title>Slash Command</title> |
| 4 | <script type="module"> |
| 5 | import { store } from "/plugins/_commands/webui/commands-store.js"; |
| 6 | </script> |
| 7 | </head> |
| 8 | <body> |
| 9 | <div x-data> |
| 10 | <template x-if="$store.commandsManager"> |
| 11 | <div class="commands-editor" |
| 12 | x-create="$el.closest('.modal')?.querySelector('.modal-title') && ($el.closest('.modal').querySelector('.modal-title').textContent = $store.commandsManager.editorTitle)"> |
| 13 | <div class="commands-editor-header"> |
| 14 | <div class="commands-editor-copy"> |
| 15 | <div class="commands-editor-title" x-text="$store.commandsManager.editorTitle"></div> |
| 16 | <div class="commands-editor-subtitle"> |
| 17 | Saving writes a <code>.command.yaml</code> config plus a template file in <span x-text="$store.commandsManager.selectedScopeLabel"></span>. |
| 18 | </div> |
| 19 | </div> |
| 20 | </div> |
| 21 | |
| 22 | <div class="commands-editor-grid"> |
| 23 | <label class="commands-field"> |
| 24 | <span>Command name</span> |
| 25 | <input type="text" |
| 26 | x-model="$store.commandsManager.editor.name" |
| 27 | placeholder="explain-code" |
| 28 | autocomplete="off"> |
| 29 | </label> |
| 30 | |
| 31 | <label class="commands-field"> |
| 32 | <span>Description</span> |
| 33 | <input type="text" |
| 34 | x-model="$store.commandsManager.editor.description" |
| 35 | placeholder="Explain code clearly with examples."> |
| 36 | </label> |
| 37 | </div> |
| 38 | |
| 39 | <label class="commands-field"> |
| 40 | <span>Argument hint or example</span> |
| 41 | <input type="text" |
| 42 | x-model="$store.commandsManager.editor.argumentHint" |
| 43 | placeholder="Optional free-form text after /command"> |
| 44 | </label> |
| 45 | |
| 46 | <div class="commands-editor-grid"> |
| 47 | <label class="commands-field"> |
| 48 | <span>Command type</span> |
| 49 | <select x-model="$store.commandsManager.editor.commandType" |
| 50 | @change="$store.commandsManager.setEditorType($store.commandsManager.editor.commandType)"> |
| 51 | <option value="text">Text template (.txt)</option> |
| 52 | <option value="script">Python hook (.py)</option> |
| 53 | </select> |
| 54 | </label> |
| 55 | |
| 56 | <template x-if="$store.commandsManager.editor.commandType === 'script'"> |
| 57 | <label class="commands-field commands-field-checkbox"> |
| 58 | <span>Script context</span> |
| 59 | <div class="commands-checkbox-inline"> |
| 60 | <input type="checkbox" x-model="$store.commandsManager.editor.includeHistory"> |
| 61 | <span>Include chat history payload</span> |
| 62 | </div> |
| 63 | </label> |
| 64 | </template> |
| 65 | </div> |
| 66 | |
| 67 | <label class="commands-field commands-field-body"> |
| 68 | <span x-text="$store.commandsManager.editorBodyLabel"></span> |
| 69 | <textarea x-model="$store.commandsManager.editor.body" |
| 70 | rows="14" |
| 71 | :placeholder="$store.commandsManager.editor.commandType === 'script' |
| 72 | ? 'Implement run(payload) and return { text, effects }.' |
| 73 | : 'Write reusable text. Use {raw}, {args.positional.0}, {args.flags.some_flag}.'"></textarea> |
| 74 | </label> |
| 75 | |
| 76 | <template x-if="$store.commandsManager.editor.commandType === 'text'"> |
| 77 | <div class="commands-editor-help"> |
| 78 | <span class="commands-help-chip">{raw}</span> |
| 79 | <span class="commands-help-chip">{args.positional.0}</span> |
| 80 | <span class="commands-help-chip">{args.flags.git_url}</span> |
| 81 | <span class="commands-help-copy"> |
| 82 | Trailing command input is parsed once and exposed to template placeholders and python payloads. |
| 83 | </span> |
| 84 | </div> |
| 85 | </template> |
| 86 | |
| 87 | <template x-if="$store.commandsManager.editor.commandType === 'script'"> |
| 88 | <div class="commands-editor-help"> |
| 89 | <span class="commands-help-chip">run(payload)</span> |
| 90 | <span class="commands-help-chip">payload.arguments</span> |
| 91 | <span class="commands-help-chip">result.effects</span> |
| 92 | <span class="commands-help-copy"> |
| 93 | Return a string or <code>{ text, effects }</code>. Effects support <code>replace_input</code>, <code>append_input</code>, and <code>toast</code>. |
| 94 | </span> |
| 95 | </div> |
| 96 | </template> |
| 97 | |
| 98 | <template x-if="$store.commandsManager.editor.path"> |
| 99 | <div class="commands-editor-path"> |
| 100 | <x-icon name="description"></x-icon> |
| 101 | <span x-text="$store.commandsManager.editor.path"></span> |
| 102 | </div> |
| 103 | </template> |
| 104 | </div> |
| 105 | </template> |
| 106 | </div> |
| 107 | |
| 108 | <div class="modal-footer" data-modal-footer> |
| 109 | <button class="btn btn-ok" |
| 110 | @click="$store.commandsManager.saveEditor()" |
| 111 | :disabled="$store.commandsManager.saving"> |
| 112 | Save |
| 113 | </button> |
| 114 | <button class="btn btn-cancel" @click="$store.commandsManager.closeEditor()"> |
| 115 | Cancel |
| 116 | </button> |
| 117 | </div> |
| 118 | |
| 119 | <style> |
| 120 | .commands-editor { |
| 121 | display: flex; |
| 122 | flex-direction: column; |
| 123 | gap: 1rem; |
| 124 | padding: 1rem 1.1rem 1.2rem; |
| 125 | } |
| 126 | |
| 127 | .commands-editor-header { |
| 128 | display: flex; |
| 129 | justify-content: space-between; |
| 130 | gap: 1rem; |
| 131 | align-items: flex-start; |
| 132 | } |
| 133 | |
| 134 | .commands-editor-copy { |
| 135 | min-width: 0; |
| 136 | } |
| 137 | |
| 138 | .commands-editor-title { |
| 139 | font-size: 1.02rem; |
| 140 | font-weight: 600; |
| 141 | } |
| 142 | |
| 143 | .commands-editor-subtitle { |
| 144 | margin-top: 0.25rem; |
| 145 | color: var(--color-text-secondary); |
| 146 | font-size: 0.88rem; |
| 147 | line-height: 1.45; |
| 148 | } |
| 149 | |
| 150 | .commands-editor-grid { |
| 151 | display: grid; |
| 152 | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| 153 | gap: 0.85rem; |
| 154 | } |
| 155 | |
| 156 | .commands-field { |
| 157 | display: flex; |
| 158 | flex-direction: column; |
| 159 | gap: 0.4rem; |
| 160 | } |
| 161 | |
| 162 | .commands-field span { |
| 163 | font-size: 0.82rem; |
| 164 | font-weight: 600; |
| 165 | color: var(--color-text-secondary); |
| 166 | } |
| 167 | |
| 168 | .commands-field textarea, |
| 169 | .commands-field input { |
| 170 | width: 100%; |
| 171 | } |
| 172 | |
| 173 | .commands-field select { |
| 174 | width: 100%; |
| 175 | } |
| 176 | |
| 177 | .commands-field-checkbox { |
| 178 | justify-content: flex-end; |
| 179 | } |
| 180 | |
| 181 | .commands-checkbox-inline { |
| 182 | display: inline-flex; |
| 183 | align-items: center; |
| 184 | gap: 0.45rem; |
| 185 | color: var(--color-text-primary); |
| 186 | font-size: 0.86rem; |
| 187 | font-weight: 500; |
| 188 | } |
| 189 | |
| 190 | .commands-checkbox-inline input { |
| 191 | width: auto; |
| 192 | } |
| 193 | |
| 194 | .commands-field-body textarea { |
| 195 | min-height: 18rem; |
| 196 | resize: vertical; |
| 197 | font-family: "Roboto Mono", monospace; |
| 198 | } |
| 199 | |
| 200 | .commands-editor-help { |
| 201 | display: flex; |
| 202 | flex-wrap: wrap; |
| 203 | gap: 0.5rem; |
| 204 | align-items: center; |
| 205 | } |
| 206 | |
| 207 | .commands-help-chip { |
| 208 | display: inline-flex; |
| 209 | align-items: center; |
| 210 | padding: 0.22rem 0.55rem; |
| 211 | border-radius: 999px; |
| 212 | background: color-mix(in srgb, var(--color-highlight) 12%, transparent); |
| 213 | border: 1px solid color-mix(in srgb, var(--color-highlight) 24%, transparent); |
| 214 | font-family: "Roboto Mono", monospace; |
| 215 | font-size: 0.8rem; |
| 216 | } |
| 217 | |
| 218 | .commands-help-copy { |
| 219 | color: var(--color-text-secondary); |
| 220 | font-size: 0.85rem; |
| 221 | } |
| 222 | |
| 223 | .commands-editor-path { |
| 224 | display: inline-flex; |
| 225 | align-items: center; |
| 226 | gap: 0.45rem; |
| 227 | color: var(--color-text-secondary); |
| 228 | font-family: "Roboto Mono", monospace; |
| 229 | font-size: 0.78rem; |
| 230 | word-break: break-all; |
| 231 | } |
| 232 | |
| 233 | @media (max-width: 760px) { |
| 234 | .commands-editor-grid { |
| 235 | grid-template-columns: 1fr; |
| 236 | } |
| 237 | } |
| 238 | </style> |
| 239 | </body> |
| 240 | </html> |