| import { html } from "lit"; |
| import { customElement, property, state } from "lit/decorators.js"; |
| import { ConnectionStatus, DataManager } from "../data"; |
| import { AgentMessage, State } from "../types"; |
| import { aggregateAgentMessages } from "./aggregateAgentMessages"; |
| import { SketchTailwindElement } from "./sketch-tailwind-element"; |
| |
| import "./mobile-title"; |
| import "./mobile-chat"; |
| import "./mobile-chat-input"; |
| import "./mobile-diff"; |
| |
| @customElement("mobile-shell") |
| export class MobileShell extends SketchTailwindElement { |
| private dataManager = new DataManager(); |
| |
| @state() |
| state: State | null = null; |
| |
| @property({ attribute: false }) |
| messages: AgentMessage[] = []; |
| |
| @state() |
| connectionStatus: ConnectionStatus = "disconnected"; |
| |
| @state() |
| currentView: "chat" | "diff" = "chat"; |
| |
| connectedCallback() { |
| super.connectedCallback(); |
| this.setupDataManager(); |
| } |
| |
| disconnectedCallback() { |
| super.disconnectedCallback(); |
| // Remove event listeners |
| this.dataManager.removeEventListener( |
| "dataChanged", |
| this.handleDataChanged.bind(this), |
| ); |
| this.dataManager.removeEventListener( |
| "connectionStatusChanged", |
| this.handleConnectionStatusChanged.bind(this), |
| ); |
| } |
| |
| private setupDataManager() { |
| // Add event listeners |
| this.dataManager.addEventListener( |
| "dataChanged", |
| this.handleDataChanged.bind(this), |
| ); |
| this.dataManager.addEventListener( |
| "connectionStatusChanged", |
| this.handleConnectionStatusChanged.bind(this), |
| ); |
| |
| // Initialize the data manager - it will automatically connect to /stream?from=0 |
| this.dataManager.initialize(); |
| } |
| |
| private handleDataChanged(eventData: { |
| state: State; |
| newMessages: AgentMessage[]; |
| }) { |
| const { state, newMessages } = eventData; |
| |
| if (state) { |
| this.state = state; |
| } |
| |
| // Update messages using the same pattern as main app shell |
| this.messages = aggregateAgentMessages(this.messages, newMessages); |
| } |
| |
| private handleConnectionStatusChanged( |
| status: ConnectionStatus, |
| _errorMessage?: string, |
| ) { |
| this.connectionStatus = status; |
| } |
| |
| private handleSendMessage = async ( |
| event: CustomEvent<{ message: string }>, |
| ) => { |
| const message = event.detail.message.trim(); |
| if (!message) { |
| return; |
| } |
| |
| try { |
| // Send the message to the server |
| const response = await fetch("chat", { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify({ message }), |
| }); |
| |
| if (!response.ok) { |
| console.error("Failed to send message:", response.statusText); |
| } |
| } catch (error) { |
| console.error("Error sending message:", error); |
| } |
| }; |
| |
| private handleViewChange = ( |
| event: CustomEvent<{ view: "chat" | "diff" }>, |
| ) => { |
| this.currentView = event.detail.view; |
| }; |
| |
| render() { |
| const isThinking = |
| this.state?.outstanding_llm_calls > 0 || |
| (this.state?.outstanding_tool_calls?.length ?? 0) > 0; |
| |
| return html` |
| <div |
| class="flex flex-col bg-white font-sans w-screen overflow-hidden" |
| style="height: 100dvh; height: 100vh; height: calc(var(--vh, 1vh) * 100); min-height: 100vh; min-height: -webkit-fill-available;" |
| > |
| <mobile-title |
| class="flex-shrink-0" |
| .connectionStatus=${this.connectionStatus} |
| .isThinking=${isThinking} |
| .skabandAddr=${this.state?.skaband_addr} |
| .currentView=${this.currentView} |
| .slug=${this.state?.slug || ""} |
| @view-change=${this.handleViewChange} |
| ></mobile-title> |
| |
| ${this.currentView === "chat" |
| ? html` |
| <mobile-chat |
| class="flex-1 overflow-hidden min-h-0" |
| .messages=${this.messages} |
| .isThinking=${isThinking} |
| ></mobile-chat> |
| ` |
| : html`<mobile-diff |
| class="flex-1 overflow-hidden min-h-0" |
| ></mobile-diff>`} |
| |
| <mobile-chat-input |
| class="flex-shrink-0 min-h-[64px]" |
| .disabled=${this.connectionStatus !== "connected"} |
| @send-message=${this.handleSendMessage} |
| ></mobile-chat-input> |
| </div> |
| `; |
| } |
| } |