import { useSyncExternalStore } from "react" /** What the panel is talking to: the assistant, or a wren node in a flow. */ export type ChatTab = "build" | "control" /** * Whether the assistant is open, on which tab, and whether another panel is * open beside it. * * Module state rather than context because the chat is mounted once in the * sidebar and toggled from there, while the editor's own panels report in * from a different tree: the two only have to agree on who takes which half * of the lane. */ type State = { open: boolean; tab: ChatTab; panels: number } let state: State = { open: false, tab: "build", panels: 0 } const listeners = new Set<() => void>() function set(next: Partial) { state = { ...state, ...next } for (const listener of listeners) listener() } export const chatStore = { /** Open it, on a given tab or on whichever was last shown. */ open: (tab?: ChatTab) => set(tab ? { open: true, tab } : { open: true }), show: (tab: ChatTab) => set({ tab }), close: () => set({ open: false }), toggle: () => set({ open: !state.open }), /** A floating panel is open; returns how to say it closed again. */ registerPanel: () => { set({ panels: state.panels + 1 }) return () => set({ panels: state.panels - 1 }) }, } export function useChat(): State { return useSyncExternalStore( (listener) => { listeners.add(listener) return () => listeners.delete(listener) }, () => state, () => state, ) }