blob: c6b862d67222fee7b990c688c744bd0685ea6655 [file] [log] [blame]
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001import { State, AgentMessage } from "../types";
Sean McCulloughb29f8912025-04-20 15:39:11 -07002import { LitElement, css, 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 McCullough86b56862025-04-18 13:04:03 -07005
6@customElement("sketch-container-status")
7export class SketchContainerStatus extends LitElement {
8 // Header bar: Container status details
9
10 @property()
11 state: State;
12
Philip Zeyligere66db3e2025-04-27 15:40:39 +000013 @state()
14 showDetails: boolean = false;
15
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000016 @state()
17 lastCommit: { hash: string; pushedBranch?: string } | null = null;
18
19 @state()
20 lastCommitCopied: boolean = false;
21
Sean McCullough86b56862025-04-18 13:04:03 -070022 // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
23 // Note that these styles only apply to the scope of this web component's
24 // shadow DOM node, so they won't leak out or collide with CSS declared in
25 // other components or the containing web page (...unless you want it to do that).
26 static styles = css`
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000027 /* Last commit display styling */
28 .last-commit {
29 display: flex;
30 flex-direction: column;
31 padding: 3px 8px;
32 cursor: pointer;
33 position: relative;
34 margin: 4px 0;
35 transition: color 0.2s ease;
36 }
37
38 .last-commit:hover {
39 color: #0366d6;
40 }
41
Philip Zeyliger9bca61e2025-05-22 12:40:06 -070042 /* Pulse animation for new commits */
43 @keyframes pulse {
44 0% {
45 transform: scale(1);
46 opacity: 1;
47 }
48 50% {
49 transform: scale(1.05);
50 opacity: 0.8;
51 }
52 100% {
53 transform: scale(1);
54 opacity: 1;
55 }
56 }
57
58 .pulse {
59 animation: pulse 1.5s ease-in-out;
60 background-color: rgba(38, 132, 255, 0.1);
61 border-radius: 3px;
62 }
63
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000064 .last-commit-title {
65 color: #666;
66 font-family: system-ui, sans-serif;
67 font-size: 11px;
68 font-weight: 500;
69 line-height: 1.2;
70 }
71
72 .last-commit-hash {
73 font-family: monospace;
74 font-size: 12px;
75 white-space: nowrap;
76 overflow: hidden;
77 text-overflow: ellipsis;
78 }
79
80 /* Styles for the last commit in main grid */
81 .last-commit-column {
82 justify-content: flex-start;
83 }
84
85 .info-label {
86 color: #666;
87 font-family: system-ui, sans-serif;
88 font-size: 11px;
89 font-weight: 500;
90 }
91
92 .last-commit-main {
93 cursor: pointer;
94 position: relative;
95 padding-top: 0;
96 }
97
98 .last-commit-main:hover {
99 color: #0366d6;
100 }
101
102 .main-grid-commit {
103 font-family: monospace;
104 font-size: 12px;
105 white-space: nowrap;
106 overflow: hidden;
107 text-overflow: ellipsis;
108 }
109
110 .commit-hash-indicator {
111 color: #666;
112 }
113
114 .commit-branch-indicator {
115 color: #28a745;
116 }
117
118 .no-commit-indicator {
119 color: #999;
120 font-style: italic;
121 font-size: 12px;
122 }
123
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700124 .copied-indicator {
125 position: absolute;
126 top: 0;
127 left: 0;
128 background: rgba(0, 0, 0, 0.7);
129 color: white;
130 padding: 2px 6px;
131 border-radius: 3px;
132 font-size: 11px;
133 pointer-events: none;
134 z-index: 10;
135 }
136
137 .copy-icon {
138 margin-left: 4px;
139 opacity: 0.7;
140 }
141
142 .copy-icon svg {
143 vertical-align: middle;
144 }
145
146 .last-commit-main:hover .copy-icon {
147 opacity: 1;
148 }
149
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000150 .info-container {
151 display: flex;
152 align-items: center;
153 position: relative;
Sean McCullough86b56862025-04-18 13:04:03 -0700154 }
155
156 .info-grid {
157 display: flex;
158 flex-wrap: wrap;
159 gap: 8px;
160 background: #f9f9f9;
161 border-radius: 4px;
162 padding: 4px 10px;
163 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
164 flex: 1;
165 }
166
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000167 .info-expanded {
168 position: absolute;
169 top: 100%;
170 right: 0;
171 z-index: 10;
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000172 min-width: 400px;
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000173 background: white;
174 border-radius: 8px;
175 padding: 10px 15px;
176 box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
177 margin-top: 5px;
178 display: none;
179 }
180
181 .info-expanded.active {
182 display: block;
183 }
184
Sean McCullough86b56862025-04-18 13:04:03 -0700185 .info-item {
186 display: flex;
187 align-items: center;
188 white-space: nowrap;
189 margin-right: 10px;
190 font-size: 13px;
191 }
192
193 .info-label {
194 font-size: 11px;
195 color: #555;
196 margin-right: 3px;
197 font-weight: 500;
198 }
199
200 .info-value {
201 font-size: 11px;
202 font-weight: 600;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000203 word-break: break-all;
Sean McCullough86b56862025-04-18 13:04:03 -0700204 }
205
Philip Zeyligerd1402952025-04-23 03:54:37 +0000206 [title] {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000207 cursor: default;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000208 }
209
Sean McCullough86b56862025-04-18 13:04:03 -0700210 .cost {
211 color: #2e7d32;
212 }
213
214 .info-item a {
215 --tw-text-opacity: 1;
216 color: rgb(37 99 235 / var(--tw-text-opacity, 1));
217 text-decoration: inherit;
218 }
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000219
220 .info-toggle {
221 margin-left: 8px;
222 width: 24px;
223 height: 24px;
224 border-radius: 50%;
225 display: flex;
226 align-items: center;
227 justify-content: center;
228 background: #f0f0f0;
229 border: 1px solid #ddd;
230 cursor: pointer;
231 font-weight: bold;
232 font-style: italic;
233 color: #555;
234 transition: all 0.2s ease;
235 }
236
237 .info-toggle:hover {
238 background: #e0e0e0;
239 }
240
241 .info-toggle.active {
242 background: #4a90e2;
243 color: white;
244 border-color: #3a80d2;
245 }
246
247 .main-info-grid {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000248 display: grid;
249 grid-template-columns: 1fr 1fr 1fr;
250 gap: 10px;
251 width: 100%;
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000252 }
253
254 .info-column {
255 display: flex;
256 flex-direction: column;
257 gap: 2px;
258 }
259
260 .detailed-info-grid {
261 display: grid;
262 grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
263 gap: 8px;
264 margin-top: 10px;
265 }
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000266
267 .ssh-section {
268 margin-top: 10px;
269 padding-top: 10px;
270 border-top: 1px solid #eee;
271 }
272
273 .ssh-command {
274 display: flex;
275 align-items: center;
276 margin-bottom: 8px;
277 gap: 10px;
278 }
279
280 .ssh-command-text {
281 font-family: monospace;
282 font-size: 12px;
283 background: #f5f5f5;
284 padding: 4px 8px;
285 border-radius: 4px;
286 border: 1px solid #e0e0e0;
287 flex-grow: 1;
288 }
289
290 .copy-button {
291 background: #f0f0f0;
292 border: 1px solid #ddd;
293 border-radius: 4px;
294 padding: 3px 6px;
295 font-size: 11px;
296 cursor: pointer;
297 transition: all 0.2s;
298 }
299
300 .copy-button:hover {
301 background: #e0e0e0;
302 }
303
304 .ssh-warning {
305 background: #fff3e0;
306 border-left: 3px solid #ff9800;
307 padding: 8px 12px;
308 margin-top: 8px;
309 font-size: 12px;
310 color: #e65100;
311 }
312
313 .vscode-link {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000314 color: white;
315 text-decoration: none;
316 background-color: #0066b8;
317 padding: 4px 8px;
318 border-radius: 4px;
319 display: flex;
320 align-items: center;
321 gap: 6px;
322 font-size: 12px;
323 transition: all 0.2s ease;
324 }
325
326 .vscode-link:hover {
327 background-color: #005091;
328 }
329
330 .vscode-icon {
331 width: 16px;
332 height: 16px;
333 }
334
335 .github-link {
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000336 color: #2962ff;
337 text-decoration: none;
338 }
339
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000340 .github-link:hover {
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000341 text-decoration: underline;
342 }
Sean McCullough86b56862025-04-18 13:04:03 -0700343 `;
344
345 constructor() {
346 super();
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000347 this._toggleInfoDetails = this._toggleInfoDetails.bind(this);
348
349 // Close the info panel when clicking outside of it
350 document.addEventListener("click", (event) => {
351 if (this.showDetails && !this.contains(event.target as Node)) {
352 this.showDetails = false;
353 this.requestUpdate();
354 }
355 });
356 }
357
358 /**
359 * Toggle the display of detailed information
360 */
361 private _toggleInfoDetails(event: Event) {
362 event.stopPropagation();
363 this.showDetails = !this.showDetails;
364 this.requestUpdate();
Sean McCullough86b56862025-04-18 13:04:03 -0700365 }
366
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000367 /**
368 * Update the last commit information based on messages
369 */
370 public updateLastCommitInfo(newMessages: AgentMessage[]): void {
371 if (!newMessages || newMessages.length === 0) return;
372
373 // Process messages in chronological order (latest last)
374 for (const message of newMessages) {
375 if (
376 message.type === "commit" &&
377 message.commits &&
378 message.commits.length > 0
379 ) {
380 // Get the first commit from the list
381 const commit = message.commits[0];
382 if (commit) {
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700383 // Check if the commit hash has changed
384 const hasChanged =
385 !this.lastCommit || this.lastCommit.hash !== commit.hash;
386
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000387 this.lastCommit = {
388 hash: commit.hash,
389 pushedBranch: commit.pushed_branch,
390 };
391 this.lastCommitCopied = false;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700392
393 // Add pulse animation if the commit changed
394 if (hasChanged) {
395 // Find the last commit element
396 setTimeout(() => {
397 const lastCommitEl =
398 this.shadowRoot?.querySelector(".last-commit-main");
399 if (lastCommitEl) {
400 // Add the pulse class
401 lastCommitEl.classList.add("pulse");
402
403 // Remove the pulse class after animation completes
404 setTimeout(() => {
405 lastCommitEl.classList.remove("pulse");
406 }, 1500);
407 }
408 }, 0);
409 }
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000410 }
411 }
412 }
413 }
414
415 /**
416 * Copy commit info to clipboard when clicked
417 */
418 private copyCommitInfo(event: MouseEvent): void {
419 event.preventDefault();
420 event.stopPropagation();
421
422 if (!this.lastCommit) return;
423
424 const textToCopy =
425 this.lastCommit.pushedBranch || this.lastCommit.hash.substring(0, 8);
426
427 navigator.clipboard
428 .writeText(textToCopy)
429 .then(() => {
430 this.lastCommitCopied = true;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700431 // Reset the copied state after 1.5 seconds
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000432 setTimeout(() => {
433 this.lastCommitCopied = false;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700434 }, 1500);
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000435 })
436 .catch((err) => {
437 console.error("Failed to copy commit info:", err);
438 });
439 }
440
Philip Zeyligerd1402952025-04-23 03:54:37 +0000441 formatHostname() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000442 // Only display outside hostname
Philip Zeyliger18532b22025-04-23 21:11:46 +0000443 const outsideHostname = this.state?.outside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000444
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000445 if (!outsideHostname) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000446 return this.state?.hostname;
447 }
448
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000449 return outsideHostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000450 }
451
452 formatWorkingDir() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000453 // Only display outside working directory
Philip Zeyliger18532b22025-04-23 21:11:46 +0000454 const outsideWorkingDir = this.state?.outside_working_dir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000455
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000456 if (!outsideWorkingDir) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000457 return this.state?.working_dir;
458 }
459
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000460 return outsideWorkingDir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000461 }
462
463 getHostnameTooltip() {
Philip Zeyliger18532b22025-04-23 21:11:46 +0000464 const outsideHostname = this.state?.outside_hostname;
465 const insideHostname = this.state?.inside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000466
467 if (
Philip Zeyliger18532b22025-04-23 21:11:46 +0000468 !outsideHostname ||
469 !insideHostname ||
470 outsideHostname === insideHostname
Philip Zeyligerd1402952025-04-23 03:54:37 +0000471 ) {
472 return "";
473 }
474
Philip Zeyliger18532b22025-04-23 21:11:46 +0000475 return `Outside: ${outsideHostname}, Inside: ${insideHostname}`;
476 }
477
478 getWorkingDirTooltip() {
479 const outsideWorkingDir = this.state?.outside_working_dir;
480 const insideWorkingDir = this.state?.inside_working_dir;
481
482 if (
483 !outsideWorkingDir ||
484 !insideWorkingDir ||
485 outsideWorkingDir === insideWorkingDir
486 ) {
487 return "";
488 }
489
490 return `Outside: ${outsideWorkingDir}, Inside: ${insideWorkingDir}`;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000491 }
492
Sean McCullough86b56862025-04-18 13:04:03 -0700493 // See https://lit.dev/docs/components/lifecycle/
494 connectedCallback() {
495 super.connectedCallback();
496 // register event listeners
497 }
498
499 // See https://lit.dev/docs/components/lifecycle/
500 disconnectedCallback() {
501 super.disconnectedCallback();
502 // unregister event listeners
503 }
504
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000505 copyToClipboard(text: string) {
506 navigator.clipboard
507 .writeText(text)
508 .then(() => {
509 // Could add a temporary success indicator here
510 })
511 .catch((err) => {
512 console.error("Could not copy text: ", err);
513 });
514 }
515
516 getSSHHostname() {
517 return `sketch-${this.state?.session_id}`;
518 }
519
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000520 // Format GitHub repository URL to org/repo format
521 formatGitHubRepo(url) {
522 if (!url) return null;
523
524 // Common GitHub URL patterns
525 const patterns = [
526 // HTTPS URLs
527 /https:\/\/github\.com\/([^/]+)\/([^/\s.]+)(?:\.git)?/,
528 // SSH URLs
529 /git@github\.com:([^/]+)\/([^/\s.]+)(?:\.git)?/,
530 // Git protocol
531 /git:\/\/github\.com\/([^/]+)\/([^/\s.]+)(?:\.git)?/,
532 ];
533
534 for (const pattern of patterns) {
535 const match = url.match(pattern);
536 if (match) {
537 return {
538 formatted: `${match[1]}/${match[2]}`,
539 url: `https://github.com/${match[1]}/${match[2]}`,
540 };
541 }
542 }
543
544 return null;
545 }
546
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000547 renderSSHSection() {
548 // Only show SSH section if we're in a Docker container and have session ID
549 if (!this.state?.session_id) {
550 return html``;
551 }
552
553 const sshHost = this.getSSHHostname();
554 const sshCommand = `ssh ${sshHost}`;
555 const vscodeCommand = `code --remote ssh-remote+root@${sshHost} /app -n`;
556 const vscodeURL = `vscode://vscode-remote/ssh-remote+root@${sshHost}/app?windowId=_blank`;
557
558 if (!this.state?.ssh_available) {
559 return html`
560 <div class="ssh-section">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000561 <h3>Connect to Container</h3>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000562 <div class="ssh-warning">
563 SSH connections are not available:
564 ${this.state?.ssh_error || "SSH configuration is missing"}
565 </div>
566 </div>
567 `;
568 }
569
570 return html`
571 <div class="ssh-section">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000572 <h3>Connect to Container</h3>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000573 <div class="ssh-command">
574 <div class="ssh-command-text">${sshCommand}</div>
575 <button
576 class="copy-button"
577 @click=${() => this.copyToClipboard(sshCommand)}
578 >
579 Copy
580 </button>
581 </div>
582 <div class="ssh-command">
583 <div class="ssh-command-text">${vscodeCommand}</div>
584 <button
585 class="copy-button"
586 @click=${() => this.copyToClipboard(vscodeCommand)}
587 >
588 Copy
589 </button>
590 </div>
591 <div class="ssh-command">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000592 <a href="${vscodeURL}" class="vscode-link" title="${vscodeURL}">
593 <svg
594 class="vscode-icon"
595 xmlns="http://www.w3.org/2000/svg"
596 viewBox="0 0 24 24"
597 fill="none"
598 stroke="white"
599 stroke-width="2"
600 stroke-linecap="round"
601 stroke-linejoin="round"
602 >
603 <path
604 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"
605 />
606 </svg>
607 <span>Open in VSCode</span>
608 </a>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000609 </div>
610 </div>
611 `;
612 }
613
Sean McCullough86b56862025-04-18 13:04:03 -0700614 render() {
615 return html`
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000616 <div class="info-container">
617 <!-- Main visible info in two columns - hostname/dir and repo/cost -->
618 <div class="main-info-grid">
619 <!-- First column: hostname and working dir -->
620 <div class="info-column">
621 <div class="info-item">
622 <span
623 id="hostname"
624 class="info-value"
625 title="${this.getHostnameTooltip()}"
626 >
627 ${this.formatHostname()}
628 </span>
629 </div>
630 <div class="info-item">
631 <span
632 id="workingDir"
633 class="info-value"
634 title="${this.getWorkingDirTooltip()}"
635 >
636 ${this.formatWorkingDir()}
637 </span>
638 </div>
639 </div>
640
Philip Zeyliger72318392025-05-14 02:56:07 +0000641 <!-- Second column: git repo, agent state, and cost -->
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000642 <div class="info-column">
643 ${this.state?.git_origin
644 ? html`
645 <div class="info-item">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000646 ${(() => {
647 const github = this.formatGitHubRepo(
648 this.state?.git_origin,
649 );
650 if (github) {
651 return html`
652 <a
653 href="${github.url}"
654 target="_blank"
655 rel="noopener noreferrer"
656 class="github-link"
657 title="${this.state?.git_origin}"
658 >
659 ${github.formatted}
660 </a>
661 `;
662 } else {
663 return html`
664 <span id="gitOrigin" class="info-value"
665 >${this.state?.git_origin}</span
666 >
667 `;
668 }
669 })()}
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000670 </div>
671 `
672 : ""}
Philip Zeyliger72318392025-05-14 02:56:07 +0000673
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000674 <div class="info-item">
675 <span id="totalCost" class="info-value cost"
676 >$${(this.state?.total_usage?.total_cost_usd || 0).toFixed(
677 2,
678 )}</span
679 >
680 </div>
681 </div>
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000682
683 <!-- Third column: Last Commit -->
684 <div class="info-column last-commit-column">
685 <div class="info-item">
686 <span class="info-label">Last Commit</span>
687 </div>
688 <div
689 class="info-item last-commit-main"
690 @click=${(e: MouseEvent) => this.copyCommitInfo(e)}
691 title="Click to copy"
692 >
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000693 ${this.lastCommit
694 ? this.lastCommit.pushedBranch
695 ? html`<span class="commit-branch-indicator main-grid-commit"
696 >${this.lastCommit.pushedBranch}</span
697 >`
698 : html`<span class="commit-hash-indicator main-grid-commit"
699 >${this.lastCommit.hash.substring(0, 8)}</span
700 >`
701 : html`<span class="no-commit-indicator">N/A</span>`}
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700702 <span class="copy-icon">
703 ${this.lastCommitCopied
704 ? html`<svg
705 xmlns="http://www.w3.org/2000/svg"
706 width="16"
707 height="16"
708 viewBox="0 0 24 24"
709 fill="none"
710 stroke="currentColor"
711 stroke-width="2"
712 stroke-linecap="round"
713 stroke-linejoin="round"
714 >
715 <path d="M20 6L9 17l-5-5"></path>
716 </svg>`
717 : html`<svg
718 xmlns="http://www.w3.org/2000/svg"
719 width="16"
720 height="16"
721 viewBox="0 0 24 24"
722 fill="none"
723 stroke="currentColor"
724 stroke-width="2"
725 stroke-linecap="round"
726 stroke-linejoin="round"
727 >
728 <rect
729 x="9"
730 y="9"
731 width="13"
732 height="13"
733 rx="2"
734 ry="2"
735 ></rect>
736 <path
737 d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
738 ></path>
739 </svg>`}
740 </span>
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000741 </div>
742 </div>
Sean McCullough86b56862025-04-18 13:04:03 -0700743 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000744
745 <!-- Info toggle button -->
746 <button
747 class="info-toggle ${this.showDetails ? "active" : ""}"
748 @click=${this._toggleInfoDetails}
749 title="Show/hide details"
750 >
751 i
752 </button>
753
754 <!-- Expanded info panel -->
755 <div class="info-expanded ${this.showDetails ? "active" : ""}">
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000756 <!-- Last Commit section moved to main grid -->
757
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000758 <div class="detailed-info-grid">
759 <div class="info-item">
760 <span class="info-label">Commit:</span>
761 <span id="initialCommit" class="info-value"
762 >${this.state?.initial_commit?.substring(0, 8)}</span
763 >
764 </div>
765 <div class="info-item">
766 <span class="info-label">Msgs:</span>
767 <span id="messageCount" class="info-value"
768 >${this.state?.message_count}</span
769 >
770 </div>
771 <div class="info-item">
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000772 <span class="info-label">Session ID:</span>
773 <span id="sessionId" class="info-value"
774 >${this.state?.session_id || "N/A"}</span
775 >
776 </div>
Philip Zeyliger72318392025-05-14 02:56:07 +0000777 ${this.state?.agent_state
778 ? html`
779 <div class="info-item">
780 <span class="info-label">Agent State:</span>
781 <span id="agentState" class="info-value"
782 >${this.state?.agent_state}</span
783 >
784 </div>
785 `
786 : ""}
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000787 <div class="info-item">
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000788 <span class="info-label">Input tokens:</span>
789 <span id="inputTokens" class="info-value"
790 >${formatNumber(
791 (this.state?.total_usage?.input_tokens || 0) +
792 (this.state?.total_usage?.cache_read_input_tokens || 0) +
793 (this.state?.total_usage?.cache_creation_input_tokens || 0),
794 )}</span
795 >
796 </div>
797 <div class="info-item">
798 <span class="info-label">Output tokens:</span>
799 <span id="outputTokens" class="info-value"
800 >${formatNumber(this.state?.total_usage?.output_tokens)}</span
801 >
802 </div>
803 <div
804 class="info-item"
805 style="grid-column: 1 / -1; margin-top: 5px; border-top: 1px solid #eee; padding-top: 5px;"
806 >
807 <a href="logs">Logs</a> (<a href="download">Download</a>)
808 </div>
809 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000810
811 <!-- SSH Connection Information -->
812 ${this.renderSSHSection()}
Sean McCullough86b56862025-04-18 13:04:03 -0700813 </div>
814 </div>
815 `;
816 }
817}
818
819declare global {
820 interface HTMLElementTagNameMap {
821 "sketch-container-status": SketchContainerStatus;
822 }
823}