main
html 382 lines 10.7 KB
Raw
1 <html>
2 <head>
3 <title>Compact Button Extension</title>
4 <script type="module" src="/plugins/_chat_compaction/webui/compact-store.js"></script>
5 </head>
6 <body>
7 <div x-data="{
8 get disabled() {
9 return !$store.chats?.selected || $store.compactStore?.compacting || $store.chatInput?.running;
10 },
11 get disabledReason() {
12 if (!$store.chats?.selected) return 'No active chat selected';
13 if ($store.compactStore?.compacting) return 'Compaction in progress';
14 if ($store.chatInput?.running) return 'Cannot compact while agent is running';
15 return 'Compact chat history into a single summary';
16 }
17 }">
18 <template x-if="$store.compactStore">
19 <button
20 class="text-button"
21 @click="$store.compactStore.fetchStats()"
22 :disabled="disabled"
23 :title="disabledReason"
24 >
25 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" aria-hidden="true">
26 <polyline points="4 14 10 14 10 20"/><line x1="3" y1="21" x2="10" y2="14"/>
27 <polyline points="20 10 14 10 14 4"/><line x1="21" y1="3" x2="14" y2="10"/>
28 </svg>
29 <p>Compact</p>
30 </button>
31 </template>
32 </div>
33
34 <!-- Modal with unique class names to avoid global CSS collision -->
35 <div x-data x-show="$store.compactStore?.showModal" style="display: none;">
36 <div class="cmpct-overlay" @click="$store.compactStore?.closeModal()">
37 <div class="cmpct-dialog" @click.stop>
38 <div class="cmpct-header">
39 <h3>Compact Chat History</h3>
40 <button class="cmpct-close" @click="$store.compactStore?.closeModal()">
41 <x-icon name="close"></x-icon>
42 </button>
43 </div>
44
45 <div class="cmpct-body">
46 <template x-if="$store.compactStore?.stats">
47 <div>
48 <p class="cmpct-desc">
49 This will summarize your entire conversation into a single optimized message.
50 </p>
51
52 <div class="cmpct-stats">
53 <div class="cmpct-stat">
54 <span class="cmpct-stat-label">Messages</span>
55 <span class="cmpct-stat-value" x-text="$store.compactStore?.stats?.message_count"></span>
56 </div>
57 <div class="cmpct-stat">
58 <span class="cmpct-stat-label">Tokens</span>
59 <span class="cmpct-stat-value" x-text="$store.compactStore?.stats?.token_count?.toLocaleString()"></span>
60 </div>
61 </div>
62
63 <!-- Model selection -->
64 <div class="cmpct-model-section">
65 <div class="cmpct-model-label">Model</div>
66
67 <div class="cmpct-model-controls">
68 <select class="cmpct-select"
69 x-model="$store.compactStore.selectedPresetName">
70 <option value="">Current</option>
71 <template x-for="preset in $store.compactStore.presets" :key="preset.name">
72 <option :value="preset.name" x-text="preset.name"></option>
73 </template>
74 </select>
75
76 <div class="cmpct-toggle">
77 <button :class="{ active: $store.compactStore.useChatModel }"
78 @click="$store.compactStore.useChatModel = true">Chat</button>
79 <button :class="{ active: !$store.compactStore.useChatModel }"
80 @click="$store.compactStore.useChatModel = false">Utility</button>
81 </div>
82 </div>
83
84 <div class="cmpct-model-name" x-text="$store.compactStore.selectedModelDisplay"></div>
85 </div>
86
87 <div class="cmpct-info">
88 <x-icon name="check_circle"></x-icon>
89 <span>The context will be replaced with a compacted summary. The original conversation will be backed up.</span>
90 </div>
91 </div>
92 </template>
93
94 <template x-if="!$store.compactStore?.stats">
95 <div class="cmpct-loading">
96 <span class="cmpct-spinner"></span>
97 <span>Loading statistics...</span>
98 </div>
99 </template>
100 </div>
101
102 <div class="cmpct-footer">
103 <button class="cmpct-btn cmpct-btn-cancel" @click="$store.compactStore?.closeModal()">
104 Cancel
105 </button>
106 <button
107 class="cmpct-btn cmpct-btn-danger"
108 @click="$store.compactStore?.compact()"
109 :disabled="$store.compactStore?.compacting || !$store.compactStore?.stats"
110 >
111 <template x-if="$store.compactStore?.compacting">
112 <span class="cmpct-spinner"></span>
113 </template>
114 <span>Compact</span>
115 </button>
116 </div>
117 </div>
118 </div>
119 </div>
120
121 <style>
122 /* All classes prefixed with cmpct- to avoid global CSS collision */
123 .cmpct-overlay {
124 position: fixed;
125 top: 0; left: 0; right: 0; bottom: 0;
126 background: rgba(0, 0, 0, 0.5);
127 display: flex;
128 align-items: center;
129 justify-content: center;
130 z-index: 5002;
131 white-space: normal;
132 }
133
134 .cmpct-dialog {
135 background: var(--color-panel, #1a1a1a);
136 border-radius: 12px;
137 box-shadow: 0 4px 23px rgba(0, 0, 0, 0.3);
138 width: 90%;
139 max-width: 560px;
140 max-height: calc(100vh - 24px);
141 overflow: hidden;
142 display: flex;
143 flex-direction: column;
144 white-space: normal;
145 }
146
147 .cmpct-header {
148 display: flex;
149 align-items: center;
150 justify-content: space-between;
151 padding: 16px 20px;
152 border-bottom: 1px solid var(--color-border, #333);
153 }
154
155 .cmpct-header h3 {
156 margin: 0;
157 font-size: 1.1rem;
158 font-weight: 600;
159 color: var(--color-text, #e5e5e5);
160 }
161
162 .cmpct-close {
163 background: transparent;
164 border: none;
165 cursor: pointer;
166 padding: 4px;
167 border-radius: 4px;
168 color: var(--color-text-secondary, #999);
169 transition: color 0.2s;
170 }
171
172 .cmpct-close:hover {
173 color: var(--color-text, #e5e5e5);
174 }
175
176 .cmpct-body {
177 padding: 20px;
178 min-height: 0;
179 overflow-y: auto;
180 }
181
182 .cmpct-desc {
183 margin: 0 0 16px 0;
184 color: var(--color-text-secondary, #999);
185 font-size: 0.9rem;
186 }
187
188 .cmpct-stats {
189 display: grid;
190 grid-template-columns: repeat(2, 1fr);
191 gap: 12px;
192 margin-bottom: 16px;
193 }
194
195 .cmpct-stat {
196 display: flex;
197 flex-direction: column;
198 align-items: center;
199 padding: 12px 8px;
200 background: var(--color-background-muted, #252525);
201 border-radius: 8px;
202 }
203
204 .cmpct-stat-label {
205 font-size: 0.7rem;
206 color: var(--color-text-secondary, #999);
207 text-transform: uppercase;
208 letter-spacing: 0.05em;
209 margin-bottom: 4px;
210 }
211
212 .cmpct-stat-value {
213 font-size: 1.1rem;
214 font-weight: 600;
215 color: var(--color-text, #e5e5e5);
216 }
217
218 /* Model selection section */
219 .cmpct-model-section {
220 margin-bottom: 16px;
221 }
222
223 .cmpct-model-label {
224 font-size: 0.7rem;
225 color: var(--color-text-secondary, #999);
226 text-transform: uppercase;
227 letter-spacing: 0.05em;
228 margin-bottom: 8px;
229 }
230
231 .cmpct-model-controls {
232 display: flex;
233 gap: 8px;
234 margin-bottom: 6px;
235 }
236
237 .cmpct-select {
238 flex: 1;
239 min-width: 0;
240 padding: 6px 10px;
241 border-radius: 6px;
242 border: 1px solid var(--color-border, #333);
243 background: var(--color-background-muted, #252525);
244 color: var(--color-text, #e5e5e5);
245 font-size: 0.85rem;
246 cursor: pointer;
247 }
248
249 .cmpct-select:focus {
250 outline: none;
251 border-color: var(--color-primary, #3b82f6);
252 }
253
254 .cmpct-toggle {
255 display: flex;
256 border-radius: 6px;
257 border: 1px solid var(--color-border, #333);
258 overflow: hidden;
259 flex-shrink: 0;
260 }
261
262 .cmpct-toggle button {
263 padding: 6px 14px;
264 border: none;
265 background: var(--color-background-muted, #252525);
266 color: var(--color-text-secondary, #999);
267 font-size: 0.8rem;
268 font-weight: 500;
269 cursor: pointer;
270 transition: all 0.15s;
271 }
272
273 .cmpct-toggle button:first-child {
274 border-right: 1px solid var(--color-border, #333);
275 }
276
277 .cmpct-toggle button.active {
278 background: var(--color-primary, #3b82f6);
279 color: white;
280 }
281
282 .cmpct-toggle button:hover:not(.active) {
283 background: var(--color-background-hover, #444);
284 }
285
286 .cmpct-model-name {
287 font-size: 0.85rem;
288 color: var(--color-text, #e5e5e5);
289 opacity: 0.7;
290 white-space: nowrap;
291 overflow: hidden;
292 text-overflow: ellipsis;
293 }
294
295 .cmpct-info {
296 display: flex;
297 align-items: flex-start;
298 gap: 8px;
299 padding: 10px 12px;
300 background: rgba(34, 197, 94, 0.1);
301 border: 1px solid rgba(34, 197, 94, 0.25);
302 border-radius: 8px;
303 color: #4ade80;
304 font-size: 0.8rem;
305 }
306
307 .cmpct-info .material-symbols-outlined {
308 font-size: 1.1rem;
309 flex-shrink: 0;
310 }
311
312 .cmpct-loading {
313 display: flex;
314 align-items: center;
315 justify-content: center;
316 gap: 8px;
317 padding: 30px;
318 color: var(--color-text-secondary, #999);
319 }
320
321 .cmpct-footer {
322 display: flex;
323 justify-content: flex-end;
324 align-items: center;
325 gap: 8px;
326 padding: 12px 20px;
327 border-top: 1px solid var(--color-border, #333);
328 flex-shrink: 0;
329 }
330
331 .cmpct-btn {
332 padding: 8px 16px;
333 border-radius: 6px;
334 font-size: 0.9rem;
335 font-weight: 500;
336 cursor: pointer;
337 transition: all 0.2s;
338 display: flex;
339 align-items: center;
340 gap: 6px;
341 border: none;
342 }
343
344 .cmpct-btn:disabled {
345 opacity: 0.6;
346 cursor: not-allowed;
347 }
348
349 .cmpct-btn-cancel {
350 background: var(--color-background-muted, #333);
351 color: var(--color-text, #e5e5e5);
352 }
353
354 .cmpct-btn-cancel:hover:not(:disabled) {
355 background: var(--color-background-hover, #444);
356 }
357
358 .cmpct-btn-danger {
359 background: #dc2626;
360 color: white;
361 }
362
363 .cmpct-btn-danger:hover:not(:disabled) {
364 background: #b91c1c;
365 }
366
367 .cmpct-spinner {
368 width: 16px;
369 height: 16px;
370 border: 2px solid currentColor;
371 border-top-color: transparent;
372 border-radius: 50%;
373 animation: cmpct-spin 0.8s linear infinite;
374 display: inline-block;
375 }
376
377 @keyframes cmpct-spin {
378 to { transform: rotate(360deg); }
379 }
380 </style>
381 </body>
382 </html>