blob: 8071783603d8af1efc549237de44f4126d2eeb1c [file] [log] [blame]
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001import { State, AgentMessage } 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 McCullough7e36a042025-06-25 08:45:18 +000023 // CSS animations that can't be easily replaced with Tailwind
24 connectedCallback() {
25 super.connectedCallback();
26 // Add custom CSS animations to the document head if not already present
27 if (!document.querySelector("#container-status-animations")) {
28 const style = document.createElement("style");
29 style.id = "container-status-animations";
30 style.textContent = `
31 @keyframes pulse-custom {
32 0% { transform: scale(1); opacity: 1; }
33 50% { transform: scale(1.05); opacity: 0.8; }
34 100% { transform: scale(1); opacity: 1; }
35 }
36 .pulse-custom {
37 animation: pulse-custom 1.5s ease-in-out;
38 background-color: rgba(38, 132, 255, 0.1);
39 border-radius: 3px;
40 }
41 `;
42 document.head.appendChild(style);
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000043 }
Sean McCullough7e36a042025-06-25 08:45:18 +000044 }
Sean McCullough86b56862025-04-18 13:04:03 -070045
46 constructor() {
47 super();
Philip Zeyligere66db3e2025-04-27 15:40:39 +000048 this._toggleInfoDetails = this._toggleInfoDetails.bind(this);
49
50 // Close the info panel when clicking outside of it
51 document.addEventListener("click", (event) => {
52 if (this.showDetails && !this.contains(event.target as Node)) {
53 this.showDetails = false;
54 this.requestUpdate();
55 }
56 });
57 }
58
59 /**
60 * Toggle the display of detailed information
61 */
62 private _toggleInfoDetails(event: Event) {
63 event.stopPropagation();
64 this.showDetails = !this.showDetails;
65 this.requestUpdate();
Sean McCullough86b56862025-04-18 13:04:03 -070066 }
67
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000068 /**
69 * Update the last commit information based on messages
70 */
71 public updateLastCommitInfo(newMessages: AgentMessage[]): void {
72 if (!newMessages || newMessages.length === 0) return;
73
74 // Process messages in chronological order (latest last)
75 for (const message of newMessages) {
76 if (
77 message.type === "commit" &&
78 message.commits &&
79 message.commits.length > 0
80 ) {
81 // Get the first commit from the list
82 const commit = message.commits[0];
83 if (commit) {
Philip Zeyliger9bca61e2025-05-22 12:40:06 -070084 // Check if the commit hash has changed
85 const hasChanged =
86 !this.lastCommit || this.lastCommit.hash !== commit.hash;
87
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000088 this.lastCommit = {
89 hash: commit.hash,
90 pushedBranch: commit.pushed_branch,
91 };
92 this.lastCommitCopied = false;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -070093
94 // Add pulse animation if the commit changed
95 if (hasChanged) {
96 // Find the last commit element
97 setTimeout(() => {
Sean McCullough7e36a042025-06-25 08:45:18 +000098 const lastCommitEl = this.querySelector(".last-commit-main");
Philip Zeyliger9bca61e2025-05-22 12:40:06 -070099 if (lastCommitEl) {
100 // Add the pulse class
Sean McCullough7e36a042025-06-25 08:45:18 +0000101 lastCommitEl.classList.add("pulse-custom");
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700102
103 // Remove the pulse class after animation completes
104 setTimeout(() => {
Sean McCullough7e36a042025-06-25 08:45:18 +0000105 lastCommitEl.classList.remove("pulse-custom");
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700106 }, 1500);
107 }
108 }, 0);
109 }
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000110 }
111 }
112 }
113 }
114
115 /**
116 * Copy commit info to clipboard when clicked
117 */
118 private copyCommitInfo(event: MouseEvent): void {
119 event.preventDefault();
120 event.stopPropagation();
121
122 if (!this.lastCommit) return;
123
124 const textToCopy =
125 this.lastCommit.pushedBranch || this.lastCommit.hash.substring(0, 8);
126
127 navigator.clipboard
128 .writeText(textToCopy)
129 .then(() => {
130 this.lastCommitCopied = true;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700131 // Reset the copied state after 1.5 seconds
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000132 setTimeout(() => {
133 this.lastCommitCopied = false;
Philip Zeyliger9bca61e2025-05-22 12:40:06 -0700134 }, 1500);
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000135 })
136 .catch((err) => {
137 console.error("Failed to copy commit info:", err);
138 });
139 }
140
Philip Zeyligerd1402952025-04-23 03:54:37 +0000141 formatHostname() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000142 // Only display outside hostname
Philip Zeyliger18532b22025-04-23 21:11:46 +0000143 const outsideHostname = this.state?.outside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000144
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000145 if (!outsideHostname) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000146 return this.state?.hostname;
147 }
148
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000149 return outsideHostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000150 }
151
152 formatWorkingDir() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000153 // Only display outside working directory
Philip Zeyliger18532b22025-04-23 21:11:46 +0000154 const outsideWorkingDir = this.state?.outside_working_dir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000155
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000156 if (!outsideWorkingDir) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000157 return this.state?.working_dir;
158 }
159
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000160 return outsideWorkingDir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000161 }
162
163 getHostnameTooltip() {
Philip Zeyliger18532b22025-04-23 21:11:46 +0000164 const outsideHostname = this.state?.outside_hostname;
165 const insideHostname = this.state?.inside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000166
167 if (
Philip Zeyliger18532b22025-04-23 21:11:46 +0000168 !outsideHostname ||
169 !insideHostname ||
170 outsideHostname === insideHostname
Philip Zeyligerd1402952025-04-23 03:54:37 +0000171 ) {
172 return "";
173 }
174
Philip Zeyliger18532b22025-04-23 21:11:46 +0000175 return `Outside: ${outsideHostname}, Inside: ${insideHostname}`;
176 }
177
178 getWorkingDirTooltip() {
179 const outsideWorkingDir = this.state?.outside_working_dir;
180 const insideWorkingDir = this.state?.inside_working_dir;
181
182 if (
183 !outsideWorkingDir ||
184 !insideWorkingDir ||
185 outsideWorkingDir === insideWorkingDir
186 ) {
187 return "";
188 }
189
190 return `Outside: ${outsideWorkingDir}, Inside: ${insideWorkingDir}`;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000191 }
192
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000193 copyToClipboard(text: string) {
194 navigator.clipboard
195 .writeText(text)
196 .then(() => {
197 // Could add a temporary success indicator here
198 })
199 .catch((err) => {
200 console.error("Could not copy text: ", err);
201 });
202 }
203
204 getSSHHostname() {
philip.zeyliger8773e682025-06-11 21:36:21 -0700205 // Use the ssh_connection_string from the state if available, otherwise fall back to generating it
206 return (
207 this.state?.ssh_connection_string || `sketch-${this.state?.session_id}`
208 );
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000209 }
210
philip.zeyligere8da7af2025-06-12 14:24:28 -0700211 getSSHConnectionString() {
212 // Return the connection string for VS Code remote SSH
213 const connectionString =
214 this.state?.ssh_connection_string || `sketch-${this.state?.session_id}`;
215 // If the connection string already contains user@, use it as-is
216 // Otherwise prepend root@ for VS Code remote SSH
217 if (connectionString.includes("@")) {
218 return connectionString;
219 } else {
220 return `root@${connectionString}`;
221 }
222 }
223
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000224 // Format GitHub repository URL to org/repo format
225 formatGitHubRepo(url) {
226 if (!url) return null;
227
228 // Common GitHub URL patterns
229 const patterns = [
230 // HTTPS URLs
Sean McCulloughc7c2cc12025-06-13 03:21:18 +0000231 /https:\/\/github\.com\/([^/]+)\/([^/\s]+?)(?:\.git)?$/,
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000232 // SSH URLs
Sean McCulloughc7c2cc12025-06-13 03:21:18 +0000233 /git@github\.com:([^/]+)\/([^/\s]+?)(?:\.git)?$/,
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000234 // Git protocol
Sean McCulloughc7c2cc12025-06-13 03:21:18 +0000235 /git:\/\/github\.com\/([^/]+)\/([^/\s]+?)(?:\.git)?$/,
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000236 ];
237
238 for (const pattern of patterns) {
239 const match = url.match(pattern);
240 if (match) {
241 return {
242 formatted: `${match[1]}/${match[2]}`,
243 url: `https://github.com/${match[1]}/${match[2]}`,
philip.zeyliger6d3de482025-06-10 19:38:14 -0700244 owner: match[1],
245 repo: match[2],
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000246 };
247 }
248 }
249
250 return null;
251 }
252
philip.zeyliger6d3de482025-06-10 19:38:14 -0700253 // Generate GitHub branch URL if linking is enabled
254 getGitHubBranchLink(branchName) {
255 if (!this.state?.link_to_github || !branchName) {
256 return null;
257 }
258
259 const github = this.formatGitHubRepo(this.state?.git_origin);
260 if (!github) {
261 return null;
262 }
263
264 return `https://github.com/${github.owner}/${github.repo}/tree/${branchName}`;
265 }
266
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000267 renderSSHSection() {
268 // Only show SSH section if we're in a Docker container and have session ID
269 if (!this.state?.session_id) {
270 return html``;
271 }
272
273 const sshHost = this.getSSHHostname();
philip.zeyligere8da7af2025-06-12 14:24:28 -0700274 const sshConnectionString = this.getSSHConnectionString();
275 const sshCommand = `ssh ${sshConnectionString}`;
276 const vscodeCommand = `code --remote ssh-remote+${sshConnectionString} /app -n`;
277 const vscodeURL = `vscode://vscode-remote/ssh-remote+${sshConnectionString}/app?windowId=_blank`;
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000278
279 if (!this.state?.ssh_available) {
280 return html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000281 <div class="mt-2.5 pt-2.5 border-t border-gray-300">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000282 <h3>Connect to Container</h3>
Sean McCullough7e36a042025-06-25 08:45:18 +0000283 <div
284 class="bg-orange-50 border-l-4 border-orange-500 p-3 mt-2 text-xs text-orange-800"
285 >
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000286 SSH connections are not available:
287 ${this.state?.ssh_error || "SSH configuration is missing"}
288 </div>
289 </div>
290 `;
291 }
292
293 return html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000294 <div class="mt-2.5 pt-2.5 border-t border-gray-300">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000295 <h3>Connect to Container</h3>
Sean McCullough7e36a042025-06-25 08:45:18 +0000296 <div class="flex items-center mb-2 gap-2.5">
297 <div
298 class="font-mono text-xs bg-gray-100 px-2 py-1 rounded border border-gray-300 flex-grow"
299 >
300 ${sshCommand}
301 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000302 <button
Sean McCullough7e36a042025-06-25 08:45:18 +0000303 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 +0000304 @click=${() => this.copyToClipboard(sshCommand)}
305 >
306 Copy
307 </button>
308 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000309 <div class="flex items-center mb-2 gap-2.5">
310 <div
311 class="font-mono text-xs bg-gray-100 px-2 py-1 rounded border border-gray-300 flex-grow"
312 >
313 ${vscodeCommand}
314 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000315 <button
Sean McCullough7e36a042025-06-25 08:45:18 +0000316 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 +0000317 @click=${() => this.copyToClipboard(vscodeCommand)}
318 >
319 Copy
320 </button>
321 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000322 <div class="flex items-center mb-2 gap-2.5">
323 <a
324 href="${vscodeURL}"
325 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"
326 title="${vscodeURL}"
327 >
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000328 <svg
Sean McCullough7e36a042025-06-25 08:45:18 +0000329 class="w-4 h-4"
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000330 xmlns="http://www.w3.org/2000/svg"
331 viewBox="0 0 24 24"
332 fill="none"
333 stroke="white"
334 stroke-width="2"
335 stroke-linecap="round"
336 stroke-linejoin="round"
337 >
338 <path
339 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"
340 />
341 </svg>
342 <span>Open in VSCode</span>
343 </a>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000344 </div>
345 </div>
346 `;
347 }
348
Sean McCullough86b56862025-04-18 13:04:03 -0700349 render() {
350 return html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000351 <div class="flex items-center relative">
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700352 <!-- Main visible info in two columns - github/hostname/dir and last commit -->
Sean McCullough7e36a042025-06-25 08:45:18 +0000353 <div class="flex flex-wrap gap-2 px-2.5 py-1 flex-1">
354 <div class="grid grid-cols-2 gap-2.5 w-full">
355 <!-- First column: GitHub repo (or hostname) and working dir -->
356 <div class="flex flex-col gap-0.5">
357 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
358 ${(() => {
359 const github = this.formatGitHubRepo(this.state?.git_origin);
360 if (github) {
361 return html`
362 <a
363 href="${github.url}"
364 target="_blank"
365 rel="noopener noreferrer"
366 class="github-link text-blue-600 no-underline hover:underline"
367 title="${this.state?.git_origin}"
368 >
369 ${github.formatted}
370 </a>
371 `;
372 } else {
373 return html`
374 <span
375 id="hostname"
376 class="text-xs font-semibold break-all cursor-default"
377 title="${this.getHostnameTooltip()}"
378 >
379 ${this.formatHostname()}
380 </span>
381 `;
382 }
383 })()}
384 </div>
385 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
386 <span
387 id="workingDir"
388 class="text-xs font-semibold break-all cursor-default"
389 title="${this.getWorkingDirTooltip()}"
390 >
391 ${this.formatWorkingDir()}
392 </span>
393 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000394 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000395
Sean McCullough7e36a042025-06-25 08:45:18 +0000396 <!-- Second column: Last Commit -->
397 <div class="flex flex-col gap-0.5 justify-start">
398 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
399 <span class="text-xs text-gray-600 font-medium"
400 >Last Commit</span
401 >
402 </div>
403 <div
404 class="flex items-center whitespace-nowrap mr-2.5 text-xs cursor-pointer relative pt-0 last-commit-main hover:text-blue-600"
405 @click=${(e: MouseEvent) => this.copyCommitInfo(e)}
406 title="Click to copy"
407 >
408 ${this.lastCommit
409 ? this.lastCommit.pushedBranch
410 ? (() => {
411 const githubLink = this.getGitHubBranchLink(
412 this.lastCommit.pushedBranch,
413 );
414 return html`
415 <div class="flex items-center gap-1.5">
416 <span
417 class="text-green-600 font-mono text-xs whitespace-nowrap overflow-hidden text-ellipsis"
418 title="Click to copy: ${this.lastCommit
419 .pushedBranch}"
420 @click=${(e) => this.copyCommitInfo(e)}
421 >${this.lastCommit.pushedBranch}</span
422 >
423 <span
424 class="ml-1 opacity-70 flex items-center hover:opacity-100"
425 >
426 ${this.lastCommitCopied
427 ? html`<svg
428 xmlns="http://www.w3.org/2000/svg"
429 width="16"
430 height="16"
431 viewBox="0 0 24 24"
432 fill="none"
433 stroke="currentColor"
434 stroke-width="2"
435 stroke-linecap="round"
436 stroke-linejoin="round"
437 class="align-middle"
438 >
439 <path d="M20 6L9 17l-5-5"></path>
440 </svg>`
441 : html`<svg
442 xmlns="http://www.w3.org/2000/svg"
443 width="16"
444 height="16"
445 viewBox="0 0 24 24"
446 fill="none"
447 stroke="currentColor"
448 stroke-width="2"
449 stroke-linecap="round"
450 stroke-linejoin="round"
451 class="align-middle"
452 >
453 <rect
454 x="9"
455 y="9"
456 width="13"
457 height="13"
458 rx="2"
459 ry="2"
460 ></rect>
461 <path
462 d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
463 ></path>
464 </svg>`}
465 </span>
466 ${githubLink
467 ? html`<a
468 href="${githubLink}"
469 target="_blank"
470 rel="noopener noreferrer"
471 class="text-gray-600 no-underline flex items-center transition-colors hover:text-blue-600"
472 title="Open ${this.lastCommit
473 .pushedBranch} on GitHub"
474 @click=${(e) => e.stopPropagation()}
philip.zeyliger6d3de482025-06-10 19:38:14 -0700475 >
Sean McCullough7e36a042025-06-25 08:45:18 +0000476 <svg
477 class="w-4 h-4"
478 viewBox="0 0 16 16"
479 width="16"
480 height="16"
481 >
482 <path
483 fill="currentColor"
484 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"
485 />
486 </svg>
487 </a>`
488 : ""}
489 </div>
490 `;
491 })()
492 : html`<span
493 class="text-gray-600 font-mono text-xs whitespace-nowrap overflow-hidden text-ellipsis"
494 >${this.lastCommit.hash.substring(0, 8)}</span
495 >`
496 : html`<span class="text-gray-500 italic text-xs">N/A</span>`}
497 </div>
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000498 </div>
499 </div>
Sean McCullough86b56862025-04-18 13:04:03 -0700500 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000501
502 <!-- Info toggle button -->
503 <button
Sean McCullough7e36a042025-06-25 08:45:18 +0000504 class="info-toggle ml-2 w-6 h-6 rounded-full flex items-center justify-center ${this
505 .showDetails
506 ? "bg-blue-500 text-white border-blue-600"
507 : "bg-gray-100 text-gray-600 border-gray-300"} border cursor-pointer font-bold italic transition-all hover:${this
508 .showDetails
509 ? "bg-blue-600"
510 : "bg-gray-200"}"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000511 @click=${this._toggleInfoDetails}
512 title="Show/hide details"
513 >
514 i
515 </button>
516
517 <!-- Expanded info panel -->
Sean McCullough7e36a042025-06-25 08:45:18 +0000518 <div
519 class="${this.showDetails
520 ? "block"
521 : "hidden"} absolute min-w-max top-full z-10 bg-white rounded-lg p-4 shadow-lg mt-1.5"
522 >
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000523 <!-- Last Commit section moved to main grid -->
524
Sean McCullough7e36a042025-06-25 08:45:18 +0000525 <div
526 class="grid grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-2 mt-2.5"
527 >
528 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
529 <span class="text-xs text-gray-600 mr-1 font-medium"
530 >Commit:</span
531 >
532 <span id="initialCommit" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000533 >${this.state?.initial_commit?.substring(0, 8)}</span
534 >
535 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000536 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
537 <span class="text-xs text-gray-600 mr-1 font-medium">Msgs:</span>
538 <span id="messageCount" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000539 >${this.state?.message_count}</span
540 >
541 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000542 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
543 <span class="text-xs text-gray-600 mr-1 font-medium"
544 >Session ID:</span
545 >
546 <span id="sessionId" class="text-xs font-semibold break-all"
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000547 >${this.state?.session_id || "N/A"}</span
548 >
549 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000550 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
551 <span class="text-xs text-gray-600 mr-1 font-medium"
552 >Hostname:</span
553 >
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700554 <span
555 id="hostnameDetail"
Sean McCullough7e36a042025-06-25 08:45:18 +0000556 class="text-xs font-semibold break-all cursor-default"
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700557 title="${this.getHostnameTooltip()}"
558 >
559 ${this.formatHostname()}
560 </span>
561 </div>
Philip Zeyliger72318392025-05-14 02:56:07 +0000562 ${this.state?.agent_state
563 ? html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000564 <div
565 class="flex items-center whitespace-nowrap mr-2.5 text-xs"
566 >
567 <span class="text-xs text-gray-600 mr-1 font-medium"
568 >Agent State:</span
569 >
570 <span
571 id="agentState"
572 class="text-xs font-semibold break-all"
Philip Zeyliger72318392025-05-14 02:56:07 +0000573 >${this.state?.agent_state}</span
574 >
575 </div>
576 `
577 : ""}
Sean McCullough7e36a042025-06-25 08:45:18 +0000578 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
579 <span class="text-xs text-gray-600 mr-1 font-medium"
580 >Input tokens:</span
581 >
582 <span id="inputTokens" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000583 >${formatNumber(
584 (this.state?.total_usage?.input_tokens || 0) +
585 (this.state?.total_usage?.cache_read_input_tokens || 0) +
586 (this.state?.total_usage?.cache_creation_input_tokens || 0),
587 )}</span
588 >
589 </div>
Sean McCullough7e36a042025-06-25 08:45:18 +0000590 <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
591 <span class="text-xs text-gray-600 mr-1 font-medium"
592 >Output tokens:</span
593 >
594 <span id="outputTokens" class="text-xs font-semibold break-all"
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000595 >${formatNumber(this.state?.total_usage?.output_tokens)}</span
596 >
597 </div>
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700598 ${(this.state?.total_usage?.total_cost_usd || 0) > 0
599 ? html`
Sean McCullough7e36a042025-06-25 08:45:18 +0000600 <div
601 class="flex items-center whitespace-nowrap mr-2.5 text-xs"
602 >
603 <span class="text-xs text-gray-600 mr-1 font-medium"
604 >Total cost:</span
605 >
606 <span id="totalCost" class="text-xs font-semibold break-all"
Josh Bleecher Snyder44f847a2025-06-05 14:33:50 -0700607 >$${(this.state?.total_usage?.total_cost_usd).toFixed(
608 2,
609 )}</span
610 >
611 </div>
612 `
613 : ""}
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000614 <div
Sean McCullough7e36a042025-06-25 08:45:18 +0000615 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 +0000616 >
Sean McCullough7e36a042025-06-25 08:45:18 +0000617 <a href="logs" class="text-blue-600">Logs</a> (<a
618 href="download"
619 class="text-blue-600"
620 >Download</a
621 >)
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000622 </div>
623 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000624
625 <!-- SSH Connection Information -->
626 ${this.renderSSHSection()}
Sean McCullough86b56862025-04-18 13:04:03 -0700627 </div>
628 </div>
629 `;
630 }
631}
632
633declare global {
634 interface HTMLElementTagNameMap {
635 "sketch-container-status": SketchContainerStatus;
636 }
637}