blob: 5672be97387e399ff277105e56412207adb2bfda [file] [log] [blame]
banksean23a35b82025-07-20 21:18:31 +00001import { html } from "lit";
Philip Zeyligere08c7ff2025-06-06 13:22:12 -07002import { customElement, property, state } from "lit/decorators.js";
3import { ConnectionStatus, DataManager } from "../data";
4import { AgentMessage, State } from "../types";
5import { aggregateAgentMessages } from "./aggregateAgentMessages";
banksean23a35b82025-07-20 21:18:31 +00006import { SketchTailwindElement } from "./sketch-tailwind-element";
Philip Zeyligere08c7ff2025-06-06 13:22:12 -07007
8import "./mobile-title";
9import "./mobile-chat";
10import "./mobile-chat-input";
philip.zeyliger6b8b7662025-06-16 03:06:30 +000011import "./mobile-diff";
Philip Zeyligere08c7ff2025-06-06 13:22:12 -070012
13@customElement("mobile-shell")
banksean23a35b82025-07-20 21:18:31 +000014export class MobileShell extends SketchTailwindElement {
Philip Zeyligere08c7ff2025-06-06 13:22:12 -070015 private dataManager = new DataManager();
16
17 @state()
18 state: State | null = null;
19
20 @property({ attribute: false })
21 messages: AgentMessage[] = [];
22
23 @state()
24 connectionStatus: ConnectionStatus = "disconnected";
25
philip.zeyliger6b8b7662025-06-16 03:06:30 +000026 @state()
27 currentView: "chat" | "diff" = "chat";
28
Philip Zeyligere08c7ff2025-06-06 13:22:12 -070029 connectedCallback() {
30 super.connectedCallback();
31 this.setupDataManager();
32 }
33
34 disconnectedCallback() {
35 super.disconnectedCallback();
36 // Remove event listeners
37 this.dataManager.removeEventListener(
38 "dataChanged",
39 this.handleDataChanged.bind(this),
40 );
41 this.dataManager.removeEventListener(
42 "connectionStatusChanged",
43 this.handleConnectionStatusChanged.bind(this),
44 );
45 }
46
47 private setupDataManager() {
48 // Add event listeners
49 this.dataManager.addEventListener(
50 "dataChanged",
51 this.handleDataChanged.bind(this),
52 );
53 this.dataManager.addEventListener(
54 "connectionStatusChanged",
55 this.handleConnectionStatusChanged.bind(this),
56 );
57
58 // Initialize the data manager - it will automatically connect to /stream?from=0
59 this.dataManager.initialize();
60 }
61
62 private handleDataChanged(eventData: {
63 state: State;
64 newMessages: AgentMessage[];
65 }) {
66 const { state, newMessages } = eventData;
67
68 if (state) {
69 this.state = state;
70 }
71
72 // Update messages using the same pattern as main app shell
73 this.messages = aggregateAgentMessages(this.messages, newMessages);
74 }
75
76 private handleConnectionStatusChanged(
77 status: ConnectionStatus,
philip.zeyliger26bc6592025-06-30 20:15:30 -070078 _errorMessage?: string,
Philip Zeyligere08c7ff2025-06-06 13:22:12 -070079 ) {
80 this.connectionStatus = status;
81 }
82
83 private handleSendMessage = async (
84 event: CustomEvent<{ message: string }>,
85 ) => {
86 const message = event.detail.message.trim();
87 if (!message) {
88 return;
89 }
90
91 try {
92 // Send the message to the server
93 const response = await fetch("chat", {
94 method: "POST",
95 headers: {
96 "Content-Type": "application/json",
97 },
98 body: JSON.stringify({ message }),
99 });
100
101 if (!response.ok) {
102 console.error("Failed to send message:", response.statusText);
103 }
104 } catch (error) {
105 console.error("Error sending message:", error);
106 }
107 };
108
Autoformatterf964b502025-06-17 04:30:35 +0000109 private handleViewChange = (
110 event: CustomEvent<{ view: "chat" | "diff" }>,
111 ) => {
philip.zeyliger6b8b7662025-06-16 03:06:30 +0000112 this.currentView = event.detail.view;
113 };
114
Philip Zeyligere08c7ff2025-06-06 13:22:12 -0700115 render() {
116 const isThinking =
117 this.state?.outstanding_llm_calls > 0 ||
118 (this.state?.outstanding_tool_calls?.length ?? 0) > 0;
119
120 return html`
banksean23a35b82025-07-20 21:18:31 +0000121 <div
122 class="flex flex-col bg-white font-sans w-screen overflow-hidden"
123 style="height: 100dvh; height: 100vh; height: calc(var(--vh, 1vh) * 100); min-height: 100vh; min-height: -webkit-fill-available;"
124 >
Philip Zeyligere08c7ff2025-06-06 13:22:12 -0700125 <mobile-title
banksean23a35b82025-07-20 21:18:31 +0000126 class="flex-shrink-0"
Philip Zeyligere08c7ff2025-06-06 13:22:12 -0700127 .connectionStatus=${this.connectionStatus}
128 .isThinking=${isThinking}
Philip Zeyliger0113be52025-06-07 23:53:41 +0000129 .skabandAddr=${this.state?.skaband_addr}
philip.zeyliger6b8b7662025-06-16 03:06:30 +0000130 .currentView=${this.currentView}
131 .slug=${this.state?.slug || ""}
132 @view-change=${this.handleViewChange}
Philip Zeyligere08c7ff2025-06-06 13:22:12 -0700133 ></mobile-title>
134
philip.zeyliger6b8b7662025-06-16 03:06:30 +0000135 ${this.currentView === "chat"
136 ? html`
137 <mobile-chat
banksean23a35b82025-07-20 21:18:31 +0000138 class="flex-1 overflow-hidden min-h-0"
philip.zeyliger6b8b7662025-06-16 03:06:30 +0000139 .messages=${this.messages}
140 .isThinking=${isThinking}
141 ></mobile-chat>
142 `
banksean23a35b82025-07-20 21:18:31 +0000143 : html`<mobile-diff
144 class="flex-1 overflow-hidden min-h-0"
145 ></mobile-diff>`}
Philip Zeyligere08c7ff2025-06-06 13:22:12 -0700146
147 <mobile-chat-input
banksean23a35b82025-07-20 21:18:31 +0000148 class="flex-shrink-0 min-h-[64px]"
Philip Zeyligere08c7ff2025-06-06 13:22:12 -0700149 .disabled=${this.connectionStatus !== "connected"}
150 @send-message=${this.handleSendMessage}
151 ></mobile-chat-input>
152 </div>
153 `;
154 }
155}