main
html 241 lines 7.8 KB
Raw
1 <script type="module">
2 import { store } from "/plugins/_context_window/webui/context-window-store.js";
3 </script>
4
5 <div x-data>
6 <template x-if="$store.contextWindow && $store.preferences.isUiControlVisible('contextWindowUsage')">
7 <div class="context-window-anchor"
8 x-create="$store.contextWindow.onMount($watch)"
9 x-destroy="$store.contextWindow.cleanup()"
10 @click.outside="$store.contextWindow.open = false"
11 @keydown.escape.window="$store.contextWindow.open = false">
12 <button type="button"
13 class="context-window-button"
14 :aria-expanded="$store.contextWindow.open"
15 :aria-label="$store.contextWindow.usage.ariaLabel"
16 @click="$store.contextWindow.toggle()">
17 <svg viewBox="0 0 36 36" aria-hidden="true">
18 <circle class="context-window-ring-track" cx="18" cy="18" r="15.5"></circle>
19 <circle class="context-window-ring-value" cx="18" cy="18" r="15.5" pathLength="100"
20 :stroke-dasharray="$store.contextWindow.usage.ringDasharray"></circle>
21 </svg>
22 <span x-text="$store.contextWindow.usage.ringLabel"></span>
23 </button>
24
25 <div class="context-window-popover"
26 x-show="$store.contextWindow.open"
27 x-transition.opacity
28 style="display: none;"
29 role="dialog"
30 aria-label="Context window usage">
31 <div class="context-window-header">
32 <strong>Context window</strong>
33 <div class="context-window-summary">
34 <span class="context-window-summary-tokens" x-text="$store.contextWindow.usage.summaryTokens"></span>
35 <span class="context-window-summary-percent" x-text="$store.contextWindow.usage.summaryPercent"></span>
36 </div>
37 </div>
38 <div class="context-window-meter" aria-hidden="true">
39 <span :style="$store.contextWindow.usage.meterStyle"></span>
40 </div>
41 <template x-if="$store.contextWindow.usage.hasBreakdown">
42 <div class="context-window-rows">
43 <template x-for="row in $store.contextWindow.usage.rows" :key="row.key">
44 <div class="context-window-row">
45 <span x-text="row.label"></span>
46 <span class="context-window-value context-window-tokens" x-text="row.tokensLabel"></span>
47 <span class="context-window-value" x-text="row.percentLabel"></span>
48 </div>
49 </template>
50 </div>
51 </template>
52 <template x-if="$store.contextWindow.usage.missingBreakdown">
53 <div class="context-window-empty">Breakdown available after the next message.</div>
54 </template>
55 <template x-if="$store.contextWindow.usage.provider.hasData">
56 <div class="context-window-provider">
57 <template x-if="$store.contextWindow.usage.provider.price.hasData">
58 <div class="context-window-provider-row context-window-provider-price">
59 <span>Price</span>
60 <span class="context-window-value" x-text="$store.contextWindow.usage.provider.price.label"></span>
61 </div>
62 </template>
63 <template x-if="$store.contextWindow.usage.provider.cache.hasData">
64 <div class="context-window-provider-row">
65 <span>Cache hit</span>
66 <span class="context-window-value" x-text="$store.contextWindow.usage.provider.cache.label"></span>
67 </div>
68 </template>
69 <template x-if="$store.contextWindow.usage.provider.tokens">
70 <div class="context-window-provider-row">
71 <span>Tokens In/Out</span>
72 <span class="context-window-value" x-text="$store.contextWindow.usage.provider.tokens"></span>
73 </div>
74 </template>
75 </div>
76 </template>
77 </div>
78 </div>
79 </template>
80 </div>
81
82 <style>
83 .context-window-anchor {
84 position: static;
85 display: flex;
86 align-items: center;
87 }
88 .context-window-button {
89 position: relative;
90 display: grid;
91 place-items: center;
92 width: 1.8rem;
93 height: 1.8rem;
94 padding: 0;
95 border: 0;
96 border-radius: 50%;
97 background: transparent;
98 color: var(--color-text);
99 cursor: pointer;
100 opacity: .82;
101 }
102 .context-window-button:hover {
103 background: var(--color-background-hover);
104 opacity: 1;
105 }
106 .context-window-button svg {
107 position: absolute;
108 inset: 2px;
109 width: calc(100% - 4px);
110 height: calc(100% - 4px);
111 transform: rotate(-90deg);
112 fill: none;
113 stroke-width: 3;
114 }
115 .context-window-ring-track {
116 stroke: color-mix(in srgb, var(--color-text) 18%, transparent);
117 }
118 .context-window-ring-value {
119 stroke: var(--color-highlight);
120 stroke-linecap: round;
121 transition: stroke-dasharray .18s ease;
122 }
123 .context-window-button > span {
124 position: relative;
125 z-index: 1;
126 font-family: var(--font-family-main);
127 font-size: .42rem;
128 font-weight: 600;
129 letter-spacing: -.03em;
130 }
131 .context-window-popover {
132 position: absolute;
133 right: 0;
134 bottom: calc(100% + 6px);
135 z-index: 1050;
136 width: min(19rem, calc(100vw - 2rem));
137 padding: .9rem;
138 border: 1px solid var(--color-border);
139 border-radius: 12px;
140 background-color: color-mix(in srgb, var(--color-panel) 90%, black 10%);
141 box-shadow: 0 16px 38px rgba(0, 0, 0, .3);
142 }
143 .context-window-header,
144 .context-window-row,
145 .context-window-provider-row {
146 display: grid;
147 align-items: center;
148 }
149 .context-window-header {
150 gap: .4rem;
151 font-size: .86rem;
152 }
153 .context-window-summary,
154 .context-window-value {
155 font-family: var(--font-family-main);
156 font-variant-numeric: tabular-nums;
157 }
158 .context-window-summary {
159 display: flex;
160 align-items: baseline;
161 justify-content: space-between;
162 gap: .75rem;
163 color: color-mix(in srgb, var(--color-text) 72%, transparent);
164 font-size: .76rem;
165 }
166 .context-window-summary-percent {
167 color: var(--color-text);
168 }
169 .context-window-summary-tokens,
170 .context-window-tokens {
171 font-family: var(--font-family-code);
172 }
173 .context-window-provider {
174 margin-top: .7rem;
175 padding-top: .7rem;
176 border-top: 1px solid var(--color-border);
177 }
178 .context-window-provider-row {
179 grid-template-columns: minmax(0, 1fr) auto;
180 gap: .75rem;
181 min-height: 1.35rem;
182 color: color-mix(in srgb, var(--color-text) 72%, transparent);
183 font-size: .76rem;
184 }
185 .context-window-provider-price {
186 margin-bottom: .25rem;
187 color: var(--color-text);
188 font-size: .82rem;
189 }
190 .context-window-provider-price .context-window-value {
191 font-size: 1rem;
192 font-weight: 600;
193 }
194 .context-window-meter {
195 height: .45rem;
196 margin: .4rem 0 .75rem;
197 overflow: hidden;
198 border-radius: 999px;
199 background: color-mix(in srgb, var(--color-text) 9%, transparent);
200 }
201 .context-window-meter > span {
202 display: block;
203 height: 100%;
204 border-radius: inherit;
205 background: var(--color-highlight);
206 transition: width .18s ease;
207 }
208 .context-window-rows {
209 display: grid;
210 gap: .3rem;
211 }
212 .context-window-empty {
213 padding: .35rem 0 .15rem;
214 color: color-mix(in srgb, var(--color-text) 62%, transparent);
215 font-size: .8rem;
216 }
217 .context-window-row {
218 grid-template-columns: minmax(0, 1fr) auto 3rem;
219 gap: .5rem;
220 min-height: 1.5rem;
221 color: color-mix(in srgb, var(--color-text) 78%, transparent);
222 font-size: .8rem;
223 }
224 .context-window-value {
225 color: var(--color-text);
226 text-align: right;
227 }
228 .context-window-tokens {
229 color: color-mix(in srgb, var(--color-text) 62%, transparent);
230 }
231 @media (max-width: 25rem) {
232 .context-window-popover {
233 right: 1.25rem;
234 width: min(17rem, calc(100vw - 3rem));
235 }
236 }
237 .light-mode .context-window-popover {
238 background: var(--color-panel);
239 box-shadow: 0 4px 14px rgba(0, 0, 0, .12);
240 }
241 </style>