blob: 5152c667f74b41e821bd172aa7dc40b739d1556a [file] [log] [blame]
Sean McCulloughc37e0662025-07-03 08:46:21 -07001import { State, AgentMessage, Usage } from "../types";
Sean McCullough7e36a042025-06-25 08:45:18 +00002import { html } from "lit";
Philip Zeyligere66db3e2025-04-27 15:40:39 +00003import { customElement, property, state } from "lit/decorators.js";
Josh Bleecher Snydera0801ad2025-04-25 19:34:53 +00004import { formatNumber } from "../utils";
Sean McCullough7e36a042025-06-25 08:45:18 +00005import { SketchTailwindElement } from "./sketch-tailwind-element";
Sean McCullough86b56862025-04-18 13:04:03 -07006
7@customElement("sketch-container-status")
Sean McCullough7e36a042025-06-25 08:45:18 +00008export class SketchContainerStatus extends SketchTailwindElement {
Sean McCullough86b56862025-04-18 13:04:03 -07009 // Header bar: Container status details
10
11 @property()
12 state: State;
13
Philip Zeyligere66db3e2025-04-27 15:40:39 +000014 @state()
15 showDetails: boolean = false;
16
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000017 @state()
18 lastCommit: { hash: string; pushedBranch?: string } | null = null;
19
20 @state()
21 lastCommitCopied: boolean = false;
22
Sean McCulloughc37e0662025-07-03 08:46:21 -070023 @state()
24 latestUsage: Usage | null = null;
25
Sean McCullough7e36a042025-06-25 08:45:18 +000026 // CSS animations that can't be easily replaced with Tailwind
27 connectedCallback() {
28 super.connectedCallback();
29 // Add custom CSS animations to the document head if not already present
30 if (!document.querySelector("#container-status-animations")) {
31 const style = document.createElement("style");
32 style.id = "container-status-animations";
33 style.textContent = `
34 @keyframes pulse-custom {
35 0% { transform: scale(1); opacity: 1; }
36 50% { transform: scale(1.05); opacity: 0.8; }
37 100% { transform: scale(1); opacity: 1; }
38 }
39 .pulse-custom {
40 animation: pulse-custom 1.5s ease-in-out;
41 background-color: rgba(38, 132, 255, 0.1);
42 border-radius: 3px;
43 }
44 `;
45 document.head.appendChild(style);
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000046 }
Sean McCullough7e36a042025-06-25 08:45:18 +000047 }
Sean McCullough86b56862025-04-18 13:04:03 -070048
49 constructor() {
50 super();
Philip Zeyligere66db3e2025-04-27 15:40:39 +000051 this._toggleInfoDetails = this._toggleInfoDetails.bind(this);
52
53 // Close the info panel when clicking outside of it
54 document.addEventListener("click", (event) => {
55 if (this.showDetails && !this.contains(event.target as Node)) {
56 this.showDetails = false;
57 this.requestUpdate();
58 }
59 });
60 }
61
62 /**
63 * Toggle the display of detailed information
64 */
65 private _toggleInfoDetails(event: Event) {
66 event.stopPropagation();
67 this.showDetails = !this.showDetails;
68 this.requestUpdate();
Sean McCullough86b56862025-04-18 13:04:03 -070069 }
70
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000071 /**
72 * Update the last commit information based on messages
73 */
74 public updateLastCommitInfo(newMessages: AgentMessage[]): void {
75 if (!newMessages || newMessages.length === 0) return;
76
77 // Process messages in chronological order (latest last)
78 for (const message of newMessages) {
79 if (
80 message.type === "commit" &&
81 message.commits &&
82 message.commits.length > 0
83 ) {
84 // Get the first commit from the list
85 const commit = message.commits[0];
86 if (commit) {
Philip Zeyliger9bca61e2025-05-22 12:40:06 -070087 // Check if the commit hash has changed
88 const hasChanged =
89 !this.lastCommit || this.lastCommit.hash !== commit.hash;
90
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000091 this.lastCommit = {
92 hash: commit.hash,
93 pushedBranch: commit.pushed_branch,
94 };
95 this.lastCommitCopied = false;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -070096
97 // Add pulse animation if the commit changed
98 if (hasChanged) {
99 // Find the last commit element
100 setTimeout(() => {
Sean McCullough7e36a042025-06-25 08:45:18 +0000101 const lastCommitEl = this.querySelector(".last-commit-main");
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700102 if (lastCommitEl) {
103 // Add the pulse class
Sean McCullough7e36a042025-06-25 08:45:18 +0000104 lastCommitEl.classList.add("pulse-custom");
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700105
106 // Remove the pulse class after animation completes
107 setTimeout(() => {
Sean McCullough7e36a042025-06-25 08:45:18 +0000108 lastCommitEl.classList.remove("pulse-custom");
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700109 }, 1500);
110 }
111 }, 0);
112 }
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000113 }
114 }
115 }
116 }
117
118 /**
119 * Copy commit info to clipboard when clicked
120 */
121 private copyCommitInfo(event: MouseEvent): void {
122 event.preventDefault();
123 event.stopPropagation();
124
125 if (!this.lastCommit) return;
126
127 const textToCopy =
128 this.lastCommit.pushedBranch || this.lastCommit.hash.substring(0, 8);
129
130 navigator.clipboard
131 .writeText(textToCopy)
132 .then(() => {
133 this.lastCommitCopied = true;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700134 // Reset the copied state after 1.5 seconds
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000135 setTimeout(() => {
136 this.lastCommitCopied = false;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700137 }, 1500);
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000138 })
139 .catch((err) => {
140 console.error("Failed to copy commit info:", err);
141 });
142 }
143
Philip Zeyligerd1402952025-04-23 03:54:37 +0000144 formatHostname() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000145 // Only display outside hostname
Philip Zeyliger18532b22025-04-23 21:11:46 +0000146 const outsideHostname = this.state?.outside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000147
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000148 if (!outsideHostname) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000149 return this.state?.hostname;
150 }
151
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000152 return outsideHostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000153 }
154
155 formatWorkingDir() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000156 // Only display outside working directory
Philip Zeyliger18532b22025-04-23 21:11:46 +0000157 const outsideWorkingDir = this.state?.outside_working_dir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000158
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000159 if (!outsideWorkingDir) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000160 return this.state?.working_dir;
161 }
162
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000163 return outsideWorkingDir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000164 }
165
166 getHostnameTooltip() {
Philip Zeyliger18532b22025-04-23 21:11:46 +0000167 const outsideHostname = this.state?.outside_hostname;
168 const insideHostname = this.state?.inside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000169
170 if (
Philip Zeyliger18532b22025-04-23 21:11:46 +0000171 !outsideHostname ||
172 !insideHostname ||
173 outsideHostname === insideHostname
Philip Zeyligerd1402952025-04-23 03:54:37 +0000174 ) {
175 return "";
176 }
177
Philip Zeyliger18532b22025-04-23 21:11:46 +0000178 return `Outside: ${outsideHostname}, Inside: ${insideHostname}`;
179 }
180
181 getWorkingDirTooltip() {
182 const outsideWorkingDir = this.state?.outside_working_dir;
183 const insideWorkingDir = this.state?.inside_working_dir;
184
185 if (
186 !outsideWorkingDir ||
187 !insideWorkingDir ||
188 outsideWorkingDir === insideWorkingDir
189 ) {
190 return "";
191 }
192
193 return `Outside: ${outsideWorkingDir}, Inside: ${insideWorkingDir}`;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000194 }
195
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000196 copyToClipboard(text: string) {
197 navigator.clipboard
198 .writeText(text)
199 .then(() => {
200 // Could add a temporary success indicator here
201 })
202 .catch((err) => {
203 console.error("Could not copy text: ", err);
204 });
205 }
206
207 getSSHHostname() {
philip.zeyliger8773e682025-06-11 21:36:21 -0700208 // Use the ssh_connection_string from the state if available, otherwise fall back to generating it
209 return (
210 this.state?.ssh_connection_string || `sketch-${this.state?.session_id}`
211 );
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000212 }
213
philip.zeyligere8da7af2025-06-12 14:24:28 -0700214 getSSHConnectionString() {
215 // Return the connection string for VS Code remote SSH
216 const connectionString =
217 this.state?.ssh_connection_string || `sketch-${this.state?.session_id}`;
218 // If the connection string already contains user@, use it as-is
219 // Otherwise prepend root@ for VS Code remote SSH
220 if (connectionString.includes("@")) {
221 return connectionString;
222 } else {
223 return `root@${connectionString}`;
224 }
225 }
226
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000227 // Format GitHub repository URL to org/repo format
228 formatGitHubRepo(url) {
229 if (!url) return null;
230
231 // Common GitHub URL patterns
232 const patterns = [
233 // HTTPS URLs
Sean McCulloughc7c2cc12025-06-13 03:21:18 +0000234 /https:\/\/github\.com\/([^/]+)\/([^/\s]+?)(?:\.git)?$/,
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000235 // SSH URLs
Sean McCulloughc7c2cc12025-06-13 03:21:18 +0000236 /git@github\.com:([^/]+)\/([^/\s]+?)(?:\.git)?$/,
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000237 // Git protocol
Sean McCulloughc7c2cc12025-06-13 03:21:18 +0000238 /git:\/\/github\.com\/([^/]+)\/([^/\s]+?)(?:\.git)?$/,
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000239 ];
240
241 for (const pattern of patterns) {
242 const match = url.match(pattern);
243 if (match) {
244 return {
245 formatted: `${match[1]}/${match[2]}`,
246 url: `https://github.com/${match[1]}/${match[2]}`,
philip.zeyliger6d3de482025-06-10 19:38:14 -0700247 owner: match[1],
248 repo: match[2],
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000249 };
250 }
251 }
252
253 return null;
254 }
255
philip.zeyliger6d3de482025-06-10 19:38:14 -0700256 // Generate GitHub branch URL if linking is enabled
257 getGitHubBranchLink(branchName) {
258 if (!this.state?.link_to_github || !branchName) {
259 return null;
260 }
261
262 const github = this.formatGitHubRepo(this.state?.git_origin);
263 if (!github) {
264 return null;
265 }
266
267 return `https://github.com/${github.owner}/${github.repo}/tree/${branchName}`;
268 }
269
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000270 renderSSHSection() {
271 // Only show SSH section if we're in a Docker container and have session ID
272 if (!this.state?.session_id) {
273 return html``;
274 }
275
philip.zeyliger26bc6592025-06-30 20:15:30 -0700276 const _sshHost = this.getSSHHostname();
philip.zeyligere8da7af2025-06-12 14:24:28 -0700277 const sshConnectionString = this.getSSHConnectionString();
278 const sshCommand = `ssh ${sshConnectionString}`;
279 const vscodeCommand = `code --remote ssh-remote+${sshConnectionString} /app -n`;
280 const vscodeURL = `vscode://vscode-remote/ssh-remote+${sshConnectionString}/app?windowId=_blank`;
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000281
282 if (!this.state?.ssh_available) {
283 return html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000284 <div class="mt-2.5 pt-2.5 border-t border-gray-300">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000285 <h3>Connect to Container</h3>
Sean McCullough7e36a042025-06-25 08:45:18 +0000286 <div
287 class="bg-orange-50 border-l-4 border-orange-500 p-3 mt-2 text-xs text-orange-800"
288 >
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000289 SSH connections are not available:
290 ${this.state?.ssh_error || "SSH configuration is missing"}
291 </div>
292 </div>
293 `;
294 }
295
296 return html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000297 <div class="mt-2.5 pt-2.5 border-t border-gray-300">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000298 <h3>Connect to Container</h3>
Sean McCullough7e36a042025-06-25 08:45:18 +0000299 <div class="flex items-center mb-2 gap-2.5">
300 <div
301 class="font-mono text-xs bg-gray-100 px-2 py-1 rounded border border-gray-300 flex-grow"
302 >
303 ${sshCommand}
304 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000305 <button
Sean McCullough7e36a042025-06-25 08:45:18 +0000306 class="bg-gray-100 border border-gray-300 rounded px-1.5 py-0.5 text-xs cursor-pointer transition-colors hover:bg-gray-200"
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000307 @click=${() => this.copyToClipboard(sshCommand)}
308 >
309 Copy
310 </button>
311 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000312 <div class="flex items-center mb-2 gap-2.5">
313 <div
314 class="font-mono text-xs bg-gray-100 px-2 py-1 rounded border border-gray-300 flex-grow"
315 >
316 ${vscodeCommand}
317 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000318 <button
Sean McCullough7e36a042025-06-25 08:45:18 +0000319 class="bg-gray-100 border border-gray-300 rounded px-1.5 py-0.5 text-xs cursor-pointer transition-colors hover:bg-gray-200"
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000320 @click=${() => this.copyToClipboard(vscodeCommand)}
321 >
322 Copy
323 </button>
324 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000325 <div class="flex items-center mb-2 gap-2.5">
326 <a
327 href="${vscodeURL}"
328 class="text-white no-underline bg-blue-500 px-2 py-1 rounded flex items-center gap-1.5 text-xs transition-colors hover:bg-blue-800"
329 title="${vscodeURL}"
330 >
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000331 <svg
Sean McCullough7e36a042025-06-25 08:45:18 +0000332 class="w-4 h-4"
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000333 xmlns="http://www.w3.org/2000/svg"
334 viewBox="0 0 24 24"
335 fill="none"
336 stroke="white"
337 stroke-width="2"
338 stroke-linecap="round"
339 stroke-linejoin="round"
340 >
341 <path
342 d="M16.5 9.4 7.55 4.24a.35.35 0 0 0-.41.01l-1.23.93a.35.35 0 0 0-.14.29v13.04c0 .12.07.23.17.29l1.24.93c.13.1.31.09.43-.01L16.5 14.6l-6.39 4.82c-.16.12-.38.12-.55.01l-1.33-1.01a.35.35 0 0 1-.14-.28V5.88c0-.12.07-.23.18-.29l1.23-.93c.14-.1.32-.1.46 0l6.54 4.92-6.54 4.92c-.14.1-.32.1-.46 0l-1.23-.93a.35.35 0 0 1-.18-.29V5.88c0-.12.07-.23.17-.29l1.33-1.01c.16-.12.39-.11.55.01l6.39 4.81z"
343 />
344 </svg>
345 <span>Open in VSCode</span>
346 </a>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000347 </div>
348 </div>
349 `;
350 }
351
Sean McCullough86b56862025-04-18 13:04:03 -0700352 render() {
353 return html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000354 <div class="flex items-center relative">
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700355 <!-- Main visible info in two columns - github/hostname/dir and last commit -->
Sean McCullough7e36a042025-06-25 08:45:18 +0000356 <div class="flex flex-wrap gap-2 px-2.5 py-1 flex-1">
Sean McCullough49577492025-06-26 17:13:28 -0700357 <div class="flex gap-2.5 w-full">
Sean McCullough7e36a042025-06-25 08:45:18 +0000358 <!-- First column: GitHub repo (or hostname) and working dir -->
359 <div class="flex flex-col gap-0.5">
360 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
361 ${(() => {
362 const github = this.formatGitHubRepo(this.state?.git_origin);
363 if (github) {
364 return html`
365 <a
366 href="${github.url}"
367 target="_blank"
368 rel="noopener noreferrer"
369 class="github-link text-blue-600 no-underline hover:underline"
370 title="${this.state?.git_origin}"
371 >
372 ${github.formatted}
373 </a>
374 `;
375 } else {
376 return html`
377 <span
378 id="hostname"
379 class="text-xs font-semibold break-all cursor-default"
380 title="${this.getHostnameTooltip()}"
381 >
382 ${this.formatHostname()}
383 </span>
384 `;
385 }
386 })()}
387 </div>
388 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
389 <span
390 id="workingDir"
391 class="text-xs font-semibold break-all cursor-default"
392 title="${this.getWorkingDirTooltip()}"
393 >
394 ${this.formatWorkingDir()}
395 </span>
396 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000397 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000398
Sean McCullough7e36a042025-06-25 08:45:18 +0000399 <!-- Second column: Last Commit -->
400 <div class="flex flex-col gap-0.5 justify-start">
401 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
402 <span class="text-xs text-gray-600 font-medium"
403 >Last Commit</span
404 >
405 </div>
406 <div
407 class="flex items-center whitespace-nowrap mr-2.5 text-xs cursor-pointer relative pt-0 last-commit-main hover:text-blue-600"
408 @click=${(e: MouseEvent) => this.copyCommitInfo(e)}
409 title="Click to copy"
410 >
411 ${this.lastCommit
412 ? this.lastCommit.pushedBranch
413 ? (() => {
414 const githubLink = this.getGitHubBranchLink(
415 this.lastCommit.pushedBranch,
416 );
417 return html`
418 <div class="flex items-center gap-1.5">
419 <span
420 class="text-green-600 font-mono text-xs whitespace-nowrap overflow-hidden text-ellipsis"
421 title="Click to copy: ${this.lastCommit
422 .pushedBranch}"
423 @click=${(e) => this.copyCommitInfo(e)}
424 >${this.lastCommit.pushedBranch}</span
425 >
426 <span
427 class="ml-1 opacity-70 flex items-center hover:opacity-100"
428 >
429 ${this.lastCommitCopied
430 ? html`<svg
431 xmlns="http://www.w3.org/2000/svg"
432 width="16"
433 height="16"
434 viewBox="0 0 24 24"
435 fill="none"
436 stroke="currentColor"
437 stroke-width="2"
438 stroke-linecap="round"
439 stroke-linejoin="round"
440 class="align-middle"
441 >
442 <path d="M20 6L9 17l-5-5"></path>
443 </svg>`
444 : html`<svg
445 xmlns="http://www.w3.org/2000/svg"
446 width="16"
447 height="16"
448 viewBox="0 0 24 24"
449 fill="none"
450 stroke="currentColor"
451 stroke-width="2"
452 stroke-linecap="round"
453 stroke-linejoin="round"
454 class="align-middle"
455 >
456 <rect
457 x="9"
458 y="9"
459 width="13"
460 height="13"
461 rx="2"
462 ry="2"
463 ></rect>
464 <path
465 d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
466 ></path>
467 </svg>`}
468 </span>
469 ${githubLink
470 ? html`<a
471 href="${githubLink}"
472 target="_blank"
473 rel="noopener noreferrer"
474 class="text-gray-600 no-underline flex items-center transition-colors hover:text-blue-600"
475 title="Open ${this.lastCommit
476 .pushedBranch} on GitHub"
477 @click=${(e) => e.stopPropagation()}
philip.zeyliger6d3de482025-06-10 19:38:14 -0700478 >
Sean McCullough7e36a042025-06-25 08:45:18 +0000479 <svg
480 class="w-4 h-4"
481 viewBox="0 0 16 16"
482 width="16"
483 height="16"
484 >
485 <path
486 fill="currentColor"
487 d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
488 />
489 </svg>
490 </a>`
491 : ""}
492 </div>
493 `;
494 })()
495 : html`<span
496 class="text-gray-600 font-mono text-xs whitespace-nowrap overflow-hidden text-ellipsis"
497 >${this.lastCommit.hash.substring(0, 8)}</span
498 >`
499 : html`<span class="text-gray-500 italic text-xs">N/A</span>`}
500 </div>
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000501 </div>
502 </div>
Sean McCullough86b56862025-04-18 13:04:03 -0700503 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000504
505 <!-- Info toggle button -->
506 <button
Sean McCullough7e36a042025-06-25 08:45:18 +0000507 class="info-toggle ml-2 w-6 h-6 rounded-full flex items-center justify-center ${this
508 .showDetails
509 ? "bg-blue-500 text-white border-blue-600"
510 : "bg-gray-100 text-gray-600 border-gray-300"} border cursor-pointer font-bold italic transition-all hover:${this
511 .showDetails
512 ? "bg-blue-600"
513 : "bg-gray-200"}"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000514 @click=${this._toggleInfoDetails}
515 title="Show/hide details"
516 >
517 i
518 </button>
519
520 <!-- Expanded info panel -->
Sean McCullough7e36a042025-06-25 08:45:18 +0000521 <div
522 class="${this.showDetails
523 ? "block"
524 : "hidden"} absolute min-w-max top-full z-10 bg-white rounded-lg p-4 shadow-lg mt-1.5"
525 >
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000526 <!-- Last Commit section moved to main grid -->
527
Sean McCullough7e36a042025-06-25 08:45:18 +0000528 <div
Sean McCulloughc37e0662025-07-03 08:46:21 -0700529 class="grid gap-2 mt-2.5"
530 style="grid-template-columns: auto auto"
Sean McCullough7e36a042025-06-25 08:45:18 +0000531 >
532 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
533 <span class="text-xs text-gray-600 mr-1 font-medium"
534 >Commit:</span
535 >
536 <span id="initialCommit" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000537 >${this.state?.initial_commit?.substring(0, 8)}</span
538 >
539 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000540 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
541 <span class="text-xs text-gray-600 mr-1 font-medium">Msgs:</span>
542 <span id="messageCount" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000543 >${this.state?.message_count}</span
544 >
545 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000546 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
547 <span class="text-xs text-gray-600 mr-1 font-medium"
548 >Session ID:</span
549 >
550 <span id="sessionId" class="text-xs font-semibold break-all"
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000551 >${this.state?.session_id || "N/A"}</span
552 >
553 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000554 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
555 <span class="text-xs text-gray-600 mr-1 font-medium"
556 >Hostname:</span
557 >
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700558 <span
559 id="hostnameDetail"
Sean McCullough7e36a042025-06-25 08:45:18 +0000560 class="text-xs font-semibold break-all cursor-default"
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700561 title="${this.getHostnameTooltip()}"
562 >
563 ${this.formatHostname()}
564 </span>
565 </div>
Philip Zeyliger72318392025-05-14 02:56:07 +0000566 ${this.state?.agent_state
567 ? html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000568 <div
569 class="flex items-center whitespace-nowrap mr-2.5 text-xs"
570 >
571 <span class="text-xs text-gray-600 mr-1 font-medium"
572 >Agent State:</span
573 >
574 <span
575 id="agentState"
576 class="text-xs font-semibold break-all"
Philip Zeyliger72318392025-05-14 02:56:07 +0000577 >${this.state?.agent_state}</span
578 >
579 </div>
580 `
581 : ""}
Sean McCullough7e36a042025-06-25 08:45:18 +0000582 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
583 <span class="text-xs text-gray-600 mr-1 font-medium"
584 >Input tokens:</span
585 >
586 <span id="inputTokens" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000587 >${formatNumber(
588 (this.state?.total_usage?.input_tokens || 0) +
589 (this.state?.total_usage?.cache_read_input_tokens || 0) +
590 (this.state?.total_usage?.cache_creation_input_tokens || 0),
591 )}</span
592 >
593 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000594 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
595 <span class="text-xs text-gray-600 mr-1 font-medium"
Sean McCulloughc37e0662025-07-03 08:46:21 -0700596 >Context size:</span
597 >
598 <span id="contextWindow" class="text-xs font-semibold break-all"
599 >${formatNumber(
600 (this.latestUsage?.input_tokens || 0) +
601 (this.latestUsage?.cache_read_input_tokens || 0) +
602 (this.latestUsage?.cache_creation_input_tokens || 0),
603 )}</span
604 >
605 </div>
606 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
607 <span class="text-xs text-gray-600 mr-1 font-medium"
Sean McCullough7e36a042025-06-25 08:45:18 +0000608 >Output tokens:</span
609 >
610 <span id="outputTokens" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000611 >${formatNumber(this.state?.total_usage?.output_tokens)}</span
612 >
613 </div>
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700614 ${(this.state?.total_usage?.total_cost_usd || 0) > 0
615 ? html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000616 <div
617 class="flex items-center whitespace-nowrap mr-2.5 text-xs"
618 >
619 <span class="text-xs text-gray-600 mr-1 font-medium"
620 >Total cost:</span
621 >
622 <span id="totalCost" class="text-xs font-semibold break-all"
philip.zeyliger26bc6592025-06-30 20:15:30 -0700623 >$${(
624 this.state?.total_usage?.total_cost_usd ?? 0
625 ).toFixed(2)}</span
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700626 >
627 </div>
628 `
629 : ""}
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000630 <div
Sean McCullough7e36a042025-06-25 08:45:18 +0000631 class="flex items-center whitespace-nowrap mr-2.5 text-xs col-span-full mt-1.5 border-t border-gray-300 pt-1.5"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000632 >
Sean McCullough7e36a042025-06-25 08:45:18 +0000633 <a href="logs" class="text-blue-600">Logs</a> (<a
634 href="download"
635 class="text-blue-600"
636 >Download</a
637 >)
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000638 </div>
639 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000640
641 <!-- SSH Connection Information -->
642 ${this.renderSSHSection()}
Sean McCullough86b56862025-04-18 13:04:03 -0700643 </div>
644 </div>
645 `;
646 }
647}
648
649declare global {
650 interface HTMLElementTagNameMap {
651 "sketch-container-status": SketchContainerStatus;
652 }
653}