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