main
html 326 lines 8.54 KB
Raw
1 <html>
2 <head>
3 <title>Skills</title>
4 <script type="module" src="/plugins/_skills/webui/config-store.js"></script>
5 </head>
6 <body>
7 <div
8 x-data="createSkillsConfigModel(context, config)"
9 x-init="
10 initDefaults();
11 await loadCatalog();
12 $watch('context.projectName', async () => { await loadCatalog(); });
13 "
14 >
15 <div class="skills-layout">
16 <div class="section-title">Skills</div>
17
18 <div class="skills-toolbar">
19 <label class="skills-search">
20 <x-icon name="search"></x-icon>
21 <input
22 type="text"
23 x-model.trim="search"
24 placeholder="Search skills"
25 >
26 </label>
27
28 <div class="skills-actions">
29 <button type="button" class="button" @click="loadCatalog()" :disabled="loadingCatalog || mutatingChat">
30 <x-icon class="icon" name="refresh"></x-icon>
31 Refresh
32 </button>
33 </div>
34 </div>
35
36 <div class="skills-section">
37 <div class="skills-panel-title">Loaded skills</div>
38 <div class="skills-panel-subtitle" x-text="pinnedSubtitle()"></div>
39
40 <template x-if="!loadingCatalog && selectedSkills.length === 0">
41 <div class="skills-empty">No loaded skills yet.</div>
42 </template>
43
44 <div class="skills-selected-list">
45 <template x-for="entry in selectedSkills" :key="entryKey(entry)">
46 <div class="skills-row skills-pinned-row" :class="{ 'is-missing': isEntryMissing(entry) }">
47 <div class="skills-row-copy">
48 <div class="skills-row-title" x-text="labelForEntry(entry)"></div>
49 <div class="skills-row-meta" x-text="secondaryLabelForEntry(entry)"></div>
50 </div>
51 <div class="skills-row-actions">
52 <button
53 type="button"
54 class="button icon-button"
55 title="Open skill"
56 aria-label="Open skill"
57 @click="openSkill(entry)"
58 >
59 <x-icon class="icon" name="article"></x-icon>
60 </button>
61 </div>
62 </div>
63 </template>
64 </div>
65 </div>
66
67 <hr class="skills-divider">
68
69 <div class="skills-section">
70 <div class="skills-panel-title">All skills</div>
71 <div class="skills-panel-subtitle" x-text="allSkillsSubtitle()"></div>
72
73 <div class="skills-loading" x-show="loadingCatalog">
74 <x-icon class="spinning" name="progress_activity"></x-icon>
75 <span>Loading skills...</span>
76 </div>
77
78 <template x-if="!loadingCatalog && filteredCatalog.length === 0">
79 <div class="skills-empty">No skills matched your search.</div>
80 </template>
81
82 <div class="skills-list">
83 <template x-for="skill in filteredCatalog" :key="skill.path">
84 <div
85 class="skills-row skills-catalog-row"
86 :class="{
87 'is-pinned': isSelected(skill),
88 'is-hidden': isHidden(skill),
89 }"
90 >
91 <label class="skills-checkbox" :title="isSelected(skill) ? 'Loaded in this chat' : 'Load skill into this chat'">
92 <input
93 type="checkbox"
94 :checked="isSelected(skill)"
95 :disabled="isPinDisabled(skill)"
96 @change="togglePinnedSkill(skill, $event.target.checked)"
97 >
98 </label>
99 <div class="skills-row-copy">
100 <div class="skills-row-title" x-text="skill.name || '(unnamed skill)'"></div>
101 <div class="skills-row-description" x-text="skill.description || 'No description provided.'"></div>
102 <div
103 class="skills-row-state"
104 x-show="isHidden(skill)"
105 x-text="hiddenStateLabel()"
106 >
107 </div>
108 </div>
109 <div class="skills-row-actions">
110 <button
111 type="button"
112 class="button icon-button skills-visibility-button"
113 :class="{ 'is-hidden-state': isHidden(skill) }"
114 :title="visibilityButtonTitle(skill)"
115 :aria-label="visibilityButtonTitle(skill)"
116 :disabled="isVisibilityDisabled()"
117 @click.prevent.stop="toggleVisibility(skill)"
118 >
119 <x-icon class="icon" :name="visibilityButtonIcon(skill)"></x-icon>
120 </button>
121 <button
122 type="button"
123 class="button icon-button"
124 title="Open skill"
125 aria-label="Open skill"
126 @click.prevent.stop="openSkill(skill)"
127 >
128 <x-icon class="icon" name="article"></x-icon>
129 </button>
130 </div>
131 </div>
132 </template>
133 </div>
134 </div>
135 </div>
136 </div>
137
138 <style>
139 .skills-layout {
140 display: flex;
141 flex-direction: column;
142 gap: 1rem;
143 }
144
145 .skills-toolbar {
146 display: flex;
147 align-items: center;
148 gap: 0.75rem;
149 flex-wrap: wrap;
150 }
151
152 .skills-search {
153 flex: 1 1 18rem;
154 min-width: 14rem;
155 display: flex;
156 align-items: center;
157 gap: 0.65rem;
158 border: 1px solid var(--color-border);
159 border-radius: 0.5rem;
160 background: var(--color-bg-primary);
161 padding: var(--spacing-xs) var(--spacing-sm);
162 }
163
164 .skills-search input {
165 width: 100%;
166 border: none;
167 background: transparent;
168 outline: none;
169 }
170
171 .skills-actions {
172 display: flex;
173 align-items: center;
174 gap: 0.5rem;
175 flex-wrap: wrap;
176 margin-left: auto;
177 }
178
179 .skills-section {
180 display: flex;
181 flex-direction: column;
182 }
183
184 .skills-divider {
185 width: 100%;
186 border: 0;
187 border-top: 1px solid var(--color-border);
188 margin: 0;
189 }
190
191 .skills-help,
192 .skills-panel-subtitle,
193 .skills-row-meta,
194 .skills-row-description {
195 color: var(--color-text-secondary);
196 font-size: var(--font-size-small);
197 }
198
199 .skills-panel-title {
200 font-weight: 700;
201 margin-bottom: 0.3rem;
202 }
203
204 .skills-selected-list,
205 .skills-list {
206 display: flex;
207 flex-direction: column;
208 margin-top: 0.75rem;
209 }
210
211 .skills-row {
212 display: flex;
213 align-items: flex-start;
214 gap: 0.85rem;
215 padding: 0.95rem 0;
216 }
217
218 .skills-row + .skills-row {
219 border-top: 1px solid var(--color-border);
220 }
221
222 .skills-row.is-pinned .skills-row-title {
223 color: var(--color-text);
224 }
225
226 .skills-row.is-hidden {
227 opacity: 0.72;
228 }
229
230 .skills-row.is-missing .skills-row-title::after {
231 content: " (missing)";
232 color: var(--color-text-secondary);
233 font-weight: 500;
234 }
235
236 .skills-row-copy {
237 flex: 1 1 auto;
238 min-width: 0;
239 }
240
241 .skills-row-title {
242 font-weight: 650;
243 word-break: break-word;
244 }
245
246 .skills-row-meta {
247 margin-top: 0.25rem;
248 word-break: break-all;
249 }
250
251 .skills-row-actions {
252 display: flex;
253 align-items: center;
254 gap: 0.5rem;
255 flex: 0 0 auto;
256 margin-left: auto;
257 }
258
259 .skills-row-description {
260 margin-top: 0.3rem;
261 line-height: 1.45;
262 }
263
264 .skills-row-state {
265 margin-top: 0.4rem;
266 color: var(--color-text-secondary);
267 font-size: var(--font-size-small);
268 font-weight: 600;
269 }
270
271 .skills-checkbox {
272 display: inline-flex;
273 align-items: flex-start;
274 justify-content: center;
275 width: 1.25rem;
276 margin-top: 0.1rem;
277 }
278
279 .skills-checkbox input {
280 margin: 0.1rem 0 0;
281 accent-color: var(--color-primary);
282 }
283
284 .skills-visibility-button.is-hidden-state {
285 color: var(--color-text-secondary);
286 }
287
288 .skills-loading {
289 display: flex;
290 align-items: center;
291 gap: 0.5rem;
292 margin-top: 0.75rem;
293 color: var(--color-text-secondary);
294 }
295
296 .skills-empty {
297 color: var(--color-text-secondary);
298 margin-top: 0.75rem;
299 padding: 0.15rem 0 0;
300 }
301
302 .button, .button.cancel {
303 padding: var(--spacing-sm) !important;
304 }
305
306 .icon-button {
307 flex: 0 0 auto;
308 width: 2.5rem;
309 min-width: 2.5rem;
310 padding: 0;
311 display: inline-flex;
312 align-items: center;
313 justify-content: center;
314 }
315
316 .spinning {
317 animation: spin 1s linear infinite;
318 }
319
320 @keyframes spin {
321 from { transform: rotate(0deg); }
322 to { transform: rotate(360deg); }
323 }
324 </style>
325 </body>
326 </html>