| 1 | <html> |
| 2 | <head> |
| 3 | <title>Import Skills</title> |
| 4 | <script type="module"> |
| 5 | import { store } from "/components/settings/skills/skills-import-store.js"; |
| 6 | </script> |
| 7 | </head> |
| 8 | <body> |
| 9 | <div x-data> |
| 10 | <template x-if="$store.skillsImportStore"> |
| 11 | <div x-init="$store.skillsImportStore.init()" x-destroy="$store.skillsImportStore.onClose()"> |
| 12 | |
| 13 | <div class="section-title">Import Skills</div> |
| 14 | <div class="section-description"> |
| 15 | Add a zipped skill pack that contains one or more <code>SKILL.md</code> folders. |
| 16 | </div> |
| 17 | |
| 18 | <div class="upload-section"> |
| 19 | <label for="skills-file" class="upload-label"> |
| 20 | Select Skills Pack (.zip) |
| 21 | </label> |
| 22 | <input type="file" id="skills-file" accept=".zip" |
| 23 | @change="$store.skillsImportStore.handleFileUpload($event)"> |
| 24 | <div class="upload-hint"> |
| 25 | Upload a repository/archive that contains SKILL.md skill folders (see agentskills.io). |
| 26 | </div> |
| 27 | </div> |
| 28 | |
| 29 | <div class="options" x-show="$store.skillsImportStore.skillsFile"> |
| 30 | <label class="policy-label"> |
| 31 | <span class="policy-label-text">Limit to project:</span> |
| 32 | <select x-model="$store.skillsImportStore.projectKey" class="policy-dropdown" |
| 33 | @change="$store.skillsImportStore.previewImport()"> |
| 34 | <option value="">All</option> |
| 35 | <template x-for="project in $store.skillsImportStore.projects" :key="project.key"> |
| 36 | <option :value="project.key" x-text="project.label"></option> |
| 37 | </template> |
| 38 | </select> |
| 39 | </label> |
| 40 | |
| 41 | <label class="policy-label"> |
| 42 | <span class="policy-label-text">Namespace:</span> |
| 43 | <input class="text-input" type="text" placeholder="e.g. my-pack" |
| 44 | x-model="$store.skillsImportStore.namespace" |
| 45 | @change="$store.skillsImportStore.previewImport()"> |
| 46 | </label> |
| 47 | |
| 48 | <label class="policy-label"> |
| 49 | <span class="policy-label-text">Conflict policy:</span> |
| 50 | <select x-model="$store.skillsImportStore.conflict" class="policy-dropdown" |
| 51 | @change="$store.skillsImportStore.previewImport()"> |
| 52 | <option value="skip">Skip existing</option> |
| 53 | <option value="rename">Rename (add _2, _3...)</option> |
| 54 | <option value="overwrite">Overwrite existing</option> |
| 55 | </select> |
| 56 | </label> |
| 57 | |
| 58 | <div class="buttons"> |
| 59 | <button class="btn slim" @click="$store.skillsImportStore.scanSelectedFile()" |
| 60 | :disabled="$store.skillsImportStore.loading || $store.skillsScanStore?.preparingUpload">Scan Skills</button> |
| 61 | <button class="btn slim" @click="$store.skillsImportStore.previewImport()" |
| 62 | :disabled="$store.skillsImportStore.loading">Preview</button> |
| 63 | <button class="btn slim primary" @click="$store.skillsImportStore.performImport()" |
| 64 | :disabled="$store.skillsImportStore.loading">Import</button> |
| 65 | </div> |
| 66 | </div> |
| 67 | |
| 68 | <div x-show="$store.skillsImportStore.loading" class="loading"> |
| 69 | <span x-text="$store.skillsImportStore.loadingMessage || 'Processing...'"></span> |
| 70 | </div> |
| 71 | |
| 72 | <div x-show="$store.skillsImportStore.error" class="error"> |
| 73 | <span x-text="$store.skillsImportStore.error"></span> |
| 74 | </div> |
| 75 | |
| 76 | <div x-show="$store.skillsImportStore.preview" class="preview"> |
| 77 | <h4>Preview</h4> |
| 78 | <div class="preview-meta"> |
| 79 | <div>Namespace: <code x-text="$store.skillsImportStore.preview?.namespace"></code></div> |
| 80 | <div>Would import: <span x-text="$store.skillsImportStore.preview?.imported_count || 0"></span></div> |
| 81 | <div>Would skip: <span x-text="$store.skillsImportStore.preview?.skipped_count || 0"></span></div> |
| 82 | </div> |
| 83 | |
| 84 | <textarea class="preview-list" readonly |
| 85 | x-text="($store.skillsImportStore.preview?.imported || []).join('\n')"></textarea> |
| 86 | </div> |
| 87 | |
| 88 | <div x-show="$store.skillsImportStore.result" class="result"> |
| 89 | <h4>Import Complete</h4> |
| 90 | <div class="preview-meta"> |
| 91 | <div>Imported: <span x-text="$store.skillsImportStore.result?.imported_count || 0"></span></div> |
| 92 | <div>Skipped: <span x-text="$store.skillsImportStore.result?.skipped_count || 0"></span></div> |
| 93 | </div> |
| 94 | <div class="note"> |
| 95 | Skills are indexed automatically. If you don’t see them immediately, use the Restart button in the left pane. |
| 96 | </div> |
| 97 | </div> |
| 98 | |
| 99 | </div> |
| 100 | </template> |
| 101 | </div> |
| 102 | |
| 103 | <style> |
| 104 | .upload-section { |
| 105 | margin-bottom: 1rem; |
| 106 | padding: 1rem; |
| 107 | border: 2px dashed var(--color-border); |
| 108 | border-radius: 4px; |
| 109 | text-align: center; |
| 110 | } |
| 111 | |
| 112 | .upload-label { |
| 113 | display: block; |
| 114 | margin-bottom: 0.5rem; |
| 115 | font-weight: 600; |
| 116 | } |
| 117 | |
| 118 | .upload-hint { |
| 119 | margin-top: 0.5rem; |
| 120 | font-size: 0.85rem; |
| 121 | color: var(--color-secondary); |
| 122 | } |
| 123 | |
| 124 | .options { |
| 125 | margin: 1rem 0; |
| 126 | padding: 0.75rem; |
| 127 | background: var(--color-input); |
| 128 | border: 1px solid var(--color-border); |
| 129 | border-radius: 4px; |
| 130 | } |
| 131 | |
| 132 | .policy-label { |
| 133 | display: grid; |
| 134 | grid-template-columns: minmax(10rem, 14rem) 1fr; |
| 135 | align-items: center; |
| 136 | column-gap: 0.75rem; |
| 137 | row-gap: 0.25rem; |
| 138 | margin: 0.5rem 0; |
| 139 | } |
| 140 | |
| 141 | .policy-label-text { |
| 142 | font-weight: 600; |
| 143 | white-space: normal; |
| 144 | width: auto; |
| 145 | } |
| 146 | |
| 147 | .policy-dropdown, .text-input { |
| 148 | flex: 1; |
| 149 | padding: 0.5rem; |
| 150 | border: 1px solid var(--color-border); |
| 151 | border-radius: 4px; |
| 152 | background: var(--color-bg-primary); |
| 153 | color: var(--color-text-primary); |
| 154 | font-size: 0.9rem; |
| 155 | } |
| 156 | |
| 157 | .buttons { |
| 158 | margin-top: 0.75rem; |
| 159 | display: flex; |
| 160 | gap: 0.5rem; |
| 161 | } |
| 162 | |
| 163 | .loading { |
| 164 | width: 100%; |
| 165 | text-align: center; |
| 166 | margin-top: 1rem; |
| 167 | margin-bottom: 1rem; |
| 168 | color: var(--color-secondary); |
| 169 | } |
| 170 | |
| 171 | .error { |
| 172 | color: var(--color-error); |
| 173 | margin: 0.5rem 0; |
| 174 | padding: 0.5rem; |
| 175 | background: var(--color-error-bg); |
| 176 | border-radius: 4px; |
| 177 | } |
| 178 | |
| 179 | .preview, .result { |
| 180 | margin-top: 1rem; |
| 181 | padding: 0.75rem; |
| 182 | background: var(--color-bg-primary); |
| 183 | border: 1px solid var(--color-border); |
| 184 | border-radius: 4px; |
| 185 | } |
| 186 | |
| 187 | .preview-meta { |
| 188 | display: grid; |
| 189 | grid-template-columns: 1fr 1fr; |
| 190 | gap: 0.25rem 1rem; |
| 191 | margin-bottom: 0.5rem; |
| 192 | color: var(--color-text-secondary); |
| 193 | font-size: 0.9rem; |
| 194 | } |
| 195 | |
| 196 | .preview-list { |
| 197 | width: 100%; |
| 198 | height: 12em; |
| 199 | font-family: monospace; |
| 200 | font-size: 0.85em; |
| 201 | background: var(--color-bg-primary); |
| 202 | color: var(--color-text-primary); |
| 203 | border: 1px solid var(--color-border); |
| 204 | border-radius: 4px; |
| 205 | padding: 0.5em; |
| 206 | resize: vertical; |
| 207 | } |
| 208 | |
| 209 | .note { |
| 210 | margin-top: 0.5rem; |
| 211 | font-size: 0.9rem; |
| 212 | color: var(--color-text-secondary); |
| 213 | } |
| 214 | |
| 215 | @media (max-width: 700px) { |
| 216 | .policy-label { |
| 217 | grid-template-columns: 1fr; |
| 218 | } |
| 219 | |
| 220 | .policy-label-text { |
| 221 | width: auto; |
| 222 | white-space: normal; |
| 223 | } |
| 224 | } |
| 225 | </style> |
| 226 | </body> |
| 227 | </html> |