blob: fa530ed218aa7a8a4c3f02de9373f45b208af0d9 [file] [log] [blame]
Philip Zeyliger99a9a022025-04-27 15:15:25 +00001import { css, html, LitElement } from "lit";
2import { customElement, property } from "lit/decorators.js";
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -07003import { unsafeHTML } from "lit/directives/unsafe-html.js";
Philip Zeyliger99a9a022025-04-27 15:15:25 +00004
5@customElement("sketch-call-status")
6export class SketchCallStatus extends LitElement {
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -07007 @property()
Philip Zeyliger99a9a022025-04-27 15:15:25 +00008 llmCalls: number = 0;
9
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070010 @property()
Philip Zeyliger99a9a022025-04-27 15:15:25 +000011 toolCalls: string[] = [];
12
Sean McCulloughd9d45812025-04-30 16:53:41 -070013 @property()
14 agentState: string | null = null;
15
Philip Zeyliger72318392025-05-14 02:56:07 +000016 @property()
17 isIdle: boolean = false;
Autoformatter8c463622025-05-16 21:54:17 +000018
Philip Zeyliger5e357022025-05-16 04:50:34 +000019 @property()
20 isDisconnected: boolean = false;
Philip Zeyliger72318392025-05-14 02:56:07 +000021
Philip Zeyliger99a9a022025-04-27 15:15:25 +000022 static styles = css`
Josh Bleecher Snydere81233f2025-04-30 04:05:41 +000023 @keyframes gentle-pulse {
24 0% {
25 transform: scale(1);
26 opacity: 1;
27 }
28 50% {
29 transform: scale(1.15);
30 opacity: 0.8;
31 }
32 100% {
33 transform: scale(1);
34 opacity: 1;
35 }
36 }
37
Philip Zeyliger99a9a022025-04-27 15:15:25 +000038 .call-status-container {
39 display: flex;
Philip Zeyligerbd7b6252025-05-15 16:21:36 +000040 position: relative;
41 align-items: center;
42 padding: 0 10px;
43 }
44
45 .indicators-container {
46 display: flex;
Philip Zeyliger99a9a022025-04-27 15:15:25 +000047 align-items: center;
48 gap: 10px;
Philip Zeyligerbd7b6252025-05-15 16:21:36 +000049 position: relative;
Philip Zeyliger99a9a022025-04-27 15:15:25 +000050 }
51
52 .indicator {
53 display: flex;
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070054 justify-content: center;
Philip Zeyliger99a9a022025-04-27 15:15:25 +000055 align-items: center;
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070056 width: 32px;
57 height: 32px;
58 border-radius: 4px;
59 transition: all 0.2s ease;
Philip Zeyliger99a9a022025-04-27 15:15:25 +000060 position: relative;
61 }
62
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070063 /* LLM indicator (lightbulb) */
Philip Zeyliger99a9a022025-04-27 15:15:25 +000064 .llm-indicator {
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070065 background-color: transparent;
Autoformatter570c3f82025-04-29 18:26:50 +000066 color: #9ca3af; /* Gray when inactive */
Philip Zeyliger99a9a022025-04-27 15:15:25 +000067 }
68
69 .llm-indicator.active {
Autoformatter570c3f82025-04-29 18:26:50 +000070 background-color: #fef3c7; /* Light yellow */
71 color: #f59e0b; /* Yellow/amber when active */
Josh Bleecher Snydere81233f2025-04-30 04:05:41 +000072 animation: gentle-pulse 1.5s infinite ease-in-out;
Philip Zeyliger99a9a022025-04-27 15:15:25 +000073 }
74
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070075 /* Tool indicator (wrench) */
Philip Zeyliger99a9a022025-04-27 15:15:25 +000076 .tool-indicator {
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070077 background-color: transparent;
Autoformatter570c3f82025-04-29 18:26:50 +000078 color: #9ca3af; /* Gray when inactive */
Philip Zeyliger99a9a022025-04-27 15:15:25 +000079 }
80
81 .tool-indicator.active {
Autoformatter570c3f82025-04-29 18:26:50 +000082 background-color: #dbeafe; /* Light blue */
83 color: #3b82f6; /* Blue when active */
Josh Bleecher Snydere81233f2025-04-30 04:05:41 +000084 animation: gentle-pulse 1.5s infinite ease-in-out;
Philip Zeyliger99a9a022025-04-27 15:15:25 +000085 }
86
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -070087 svg {
88 width: 20px;
89 height: 20px;
Philip Zeyliger99a9a022025-04-27 15:15:25 +000090 }
Philip Zeyliger72318392025-05-14 02:56:07 +000091
92 .status-banner {
Philip Zeyligerbd7b6252025-05-15 16:21:36 +000093 position: absolute;
Philip Zeyliger72318392025-05-14 02:56:07 +000094 padding: 2px 5px;
95 border-radius: 3px;
96 font-size: 10px;
97 font-weight: bold;
98 text-align: center;
99 letter-spacing: 0.5px;
Philip Zeyliger5e357022025-05-16 04:50:34 +0000100 width: 104px; /* Wider to accommodate DISCONNECTED text */
Philip Zeyligerbd7b6252025-05-15 16:21:36 +0000101 left: 50%;
102 transform: translateX(-50%);
103 top: 60%; /* Position a little below center */
104 z-index: 10; /* Ensure it appears above the icons */
Philip Zeyliger5e357022025-05-16 04:50:34 +0000105 opacity: 0.9;
Philip Zeyliger72318392025-05-14 02:56:07 +0000106 }
107
108 .status-working {
109 background-color: #ffeecc;
110 color: #e65100;
111 }
112
113 .status-idle {
114 background-color: #e6f4ea;
115 color: #0d652d;
116 }
Autoformatter8c463622025-05-16 21:54:17 +0000117
Philip Zeyliger5e357022025-05-16 04:50:34 +0000118 .status-disconnected {
119 background-color: #ffebee; /* Light red */
120 color: #d32f2f; /* Red */
121 font-weight: bold;
122 }
Philip Zeyliger99a9a022025-04-27 15:15:25 +0000123 `;
124
Philip Zeyliger99a9a022025-04-27 15:15:25 +0000125 render() {
Philip Zeyligerc2b6bdf2025-04-29 11:17:47 -0700126 const lightbulbSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
127 <path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path>
128 <path d="M9 18h6"></path>
129 <path d="M10 22h4"></path>
130 </svg>`;
131
132 const wrenchSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
133 <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
134 </svg>`;
135
Sean McCulloughd9d45812025-04-30 16:53:41 -0700136 const agentState = `${this.agentState ? " (" + this.agentState + ")" : ""}`;
137
Philip Zeyliger5e357022025-05-16 04:50:34 +0000138 // Determine state - disconnected takes precedence, then working vs idle
139 let statusClass = "status-idle";
140 let statusText = "IDLE";
Autoformatter8c463622025-05-16 21:54:17 +0000141
Philip Zeyliger5e357022025-05-16 04:50:34 +0000142 if (this.isDisconnected) {
143 statusClass = "status-disconnected";
144 statusText = "DISCONNECTED";
145 } else if (!this.isIdle) {
146 statusClass = "status-working";
147 statusText = "WORKING";
148 }
Philip Zeyliger72318392025-05-14 02:56:07 +0000149
Philip Zeyliger99a9a022025-04-27 15:15:25 +0000150 return html`
151 <div class="call-status-container">
Philip Zeyligerbd7b6252025-05-15 16:21:36 +0000152 <div class="indicators-container">
153 <div
154 class="indicator llm-indicator ${this.llmCalls > 0 ? "active" : ""}"
155 title="${this.llmCalls > 0
156 ? `${this.llmCalls} LLM ${this.llmCalls === 1 ? "call" : "calls"} in progress`
157 : "No LLM calls in progress"}${agentState}"
158 >
159 ${unsafeHTML(lightbulbSVG)}
160 </div>
161 <div
162 class="indicator tool-indicator ${this.toolCalls.length > 0
163 ? "active"
164 : ""}"
165 title="${this.toolCalls.length > 0
166 ? `${this.toolCalls.length} tool ${this.toolCalls.length === 1 ? "call" : "calls"} in progress: ${this.toolCalls.join(", ")}`
167 : "No tool calls in progress"}${agentState}"
168 >
169 ${unsafeHTML(wrenchSVG)}
170 </div>
Philip Zeyliger99a9a022025-04-27 15:15:25 +0000171 </div>
Autoformatter8c463622025-05-16 21:54:17 +0000172 <div class="status-banner ${statusClass}">${statusText}</div>
Philip Zeyliger99a9a022025-04-27 15:15:25 +0000173 </div>
174 `;
175 }
176}
177
178declare global {
179 interface HTMLElementTagNameMap {
180 "sketch-call-status": SketchCallStatus;
181 }
182}