'use strict';

var chunkXACCHZH2_cjs = require('./chunk-XACCHZH2.cjs');
var chunkOLISEQHS_cjs = require('./chunk-OLISEQHS.cjs');
var react = require('react');
var lib = require('@djangocfg/ui-core/lib');
var consola = require('consola');
var hooks = require('@djangocfg/ui-core/hooks');
var zustand = require('zustand');
var middleware = require('zustand/middleware');
var jsxRuntime = require('react/jsx-runtime');
var lucideReact = require('lucide-react');
var components = require('@djangocfg/ui-core/components');

// src/tools/Chat/types.ts
var DEFAULT_LABELS = {
  send: "Send",
  cancel: "Stop",
  copy: "Copy",
  regenerate: "Regenerate",
  edit: "Edit",
  delete: "Delete",
  retry: "Retry",
  newChat: "New chat",
  loadMore: "Load more",
  jumpToLatest: "Jump to latest",
  attach: "Attach files",
  voice: "Voice input",
  errorGeneric: "Something went wrong",
  cancelledSuffix: "[cancelled]"
};

// src/tools/Chat/config.ts
var STORAGE_KEYS = {
  mode: "djc-chat-mode",
  sidebarWidth: "djc-chat-sidebar-width",
  composerHistory: "djc-chat-composer-history"
};
var CSS_VARS = {
  reserve: "--djc-chat-reserve"
};
var DEFAULT_Z_INDEX = 9e3;
var LIMITS = {
  /** Max characters per single message. */
  messageMaxLength: 8e3,
  /** Max attachments per message. */
  attachmentsMax: 10,
  /** Composer history slots. */
  composerHistorySize: 50,
  /** Coalesce stream tokens within this window before dispatching. */
  streamCoalesceMs: 16,
  /** Default history page size. */
  pageSize: 50,
  /** Virtualize list when >= this many messages (host-controlled threshold). */
  virtualizeThreshold: 50,
  /** SSE idle timeout. */
  sseIdleMs: 45e3
};
var DEFAULT_SIDEBAR = {
  width: 420,
  min: 320,
  max: 720
};
var HOTKEYS = {
  send: "mod+enter",
  cancel: "esc",
  newChat: "mod+shift+n",
  toggleOpen: "mod+/",
  focusComposer: "mod+l"
};
var CHAT_EVENT_NAME = "djc:chat:send";

// src/tools/Chat/core/reducer.ts
var initialState = {
  sessionId: null,
  messages: [],
  isLoading: false,
  isStreaming: false,
  isLoadingMore: false,
  hasMore: true,
  oldestCursor: null,
  error: null
};
function updateLastStreaming(messages, patch) {
  const idx = findLastStreamingIndex(messages);
  if (idx === -1) return messages;
  const next = messages.slice();
  next[idx] = patch(messages[idx]);
  return next;
}
chunkOLISEQHS_cjs.__name(updateLastStreaming, "updateLastStreaming");
function findLastStreamingIndex(messages) {
  for (let i = messages.length - 1; i >= 0; i -= 1) {
    if (messages[i].isStreaming) return i;
  }
  return -1;
}
chunkOLISEQHS_cjs.__name(findLastStreamingIndex, "findLastStreamingIndex");
function patchMessageById(messages, id, patch) {
  const idx = messages.findIndex((m) => m.id === id);
  if (idx === -1) return messages;
  const next = messages.slice();
  next[idx] = patch(messages[idx]);
  return next;
}
chunkOLISEQHS_cjs.__name(patchMessageById, "patchMessageById");
function reducer(state, action) {
  switch (action.type) {
    case "SESSION_SET":
      return {
        ...state,
        sessionId: action.sessionId,
        messages: action.messages ?? state.messages,
        hasMore: action.hasMore ?? state.hasMore,
        oldestCursor: action.cursor ?? state.oldestCursor,
        error: null
      };
    case "HISTORY_LOAD_START":
      return { ...state, isLoading: true, error: null };
    case "HISTORY_LOAD_DONE":
      return {
        ...state,
        isLoading: false,
        messages: action.messages,
        hasMore: action.hasMore,
        oldestCursor: action.cursor
      };
    case "HISTORY_MORE_START":
      return { ...state, isLoadingMore: true };
    case "HISTORY_MORE_DONE":
      return {
        ...state,
        isLoadingMore: false,
        // Older messages prepend to the top.
        messages: [...action.messages, ...state.messages],
        hasMore: action.hasMore,
        oldestCursor: action.cursor
      };
    case "MESSAGE_USER_ADD":
      return {
        ...state,
        messages: [...state.messages, action.message],
        error: null
      };
    case "STREAM_START": {
      if (state.isStreaming) {
        if (typeof console !== "undefined") {
          console.warn("[chat] STREAM_START while already streaming, ignoring");
        }
        return state;
      }
      const placeholder = {
        id: action.id,
        role: "assistant",
        content: "",
        createdAt: action.createdAt ?? Date.now(),
        isStreaming: true
      };
      return {
        ...state,
        isStreaming: true,
        messages: [...state.messages, placeholder]
      };
    }
    case "STREAM_CHUNK": {
      const messages = updateLastStreaming(state.messages, (m) => ({
        ...m,
        content: m.content + action.delta
      }));
      return { ...state, messages };
    }
    case "STREAM_TOOL_ACTIVITY": {
      const messages = updateLastStreaming(state.messages, (m) => ({
        ...m,
        toolActivity: action.tool
      }));
      return { ...state, messages };
    }
    case "TOOL_CALL_START": {
      const messages = patchMessageById(state.messages, action.messageId, (m) => ({
        ...m,
        toolCalls: [...m.toolCalls ?? [], action.toolCall]
      }));
      return { ...state, messages };
    }
    case "TOOL_CALL_DELTA": {
      const messages = patchMessageById(state.messages, action.messageId, (m) => {
        if (!m.toolCalls) return m;
        return {
          ...m,
          toolCalls: m.toolCalls.map(
            (tc) => tc.id === action.toolId ? { ...tc, streamingText: (tc.streamingText ?? "") + action.delta } : tc
          )
        };
      });
      return { ...state, messages };
    }
    case "TOOL_CALL_END": {
      const messages = patchMessageById(state.messages, action.messageId, (m) => {
        if (!m.toolCalls) return m;
        return {
          ...m,
          toolCalls: m.toolCalls.map(
            (tc) => tc.id === action.toolId ? {
              ...tc,
              output: action.output,
              status: action.status,
              streamingText: void 0,
              endedAt: Date.now()
            } : tc
          )
        };
      });
      return { ...state, messages };
    }
    case "STREAM_DONE": {
      const patched = patchMessageById(state.messages, action.id, (m) => ({
        ...m,
        isStreaming: false,
        tokensIn: action.tokensIn ?? m.tokensIn,
        tokensOut: action.tokensOut ?? m.tokensOut,
        sources: action.sources ?? m.sources
      }));
      const msg = patched.find((m) => m.id === action.id);
      const messages = msg && !msg.content && !msg.toolCalls?.length ? patched.filter((m) => m.id !== action.id) : patched;
      return { ...state, isStreaming: false, messages };
    }
    case "STREAM_CANCELLED": {
      const suffix = action.label ?? "[cancelled]";
      const messages = patchMessageById(state.messages, action.id, (m) => ({
        ...m,
        isStreaming: false,
        content: action.partialText + (action.partialText ? `

_${suffix}_` : `_${suffix}_`)
      }));
      return { ...state, isStreaming: false, messages };
    }
    case "STREAM_ERROR": {
      const messages = action.id ? patchMessageById(state.messages, action.id, (m) => ({
        ...m,
        isStreaming: false,
        isError: true
      })) : state.messages;
      return { ...state, isStreaming: false, error: action.message, messages };
    }
    case "STREAM_CANCEL_PLACEHOLDER":
      return {
        ...state,
        isStreaming: false,
        messages: state.messages.filter((m) => m.id !== action.id)
      };
    case "STREAM_RESUME_EXISTING": {
      const lastIdx = (() => {
        for (let i = state.messages.length - 1; i >= 0; i--) {
          if (state.messages[i].role === "assistant") return i;
        }
        return -1;
      })();
      if (lastIdx === -1) return { ...state, isStreaming: true };
      const msgs = state.messages.slice();
      msgs[lastIdx] = { ...msgs[lastIdx], isStreaming: true };
      return { ...state, isStreaming: true, messages: msgs };
    }
    case "MESSAGE_EDIT": {
      const messages = patchMessageById(state.messages, action.id, (m) => ({
        ...m,
        content: action.content,
        version: (m.version ?? 0) + 1
      }));
      return { ...state, messages };
    }
    case "MESSAGE_DELETE":
      return {
        ...state,
        messages: state.messages.filter((m) => m.id !== action.id)
      };
    case "MESSAGES_CLEAR":
      return {
        ...state,
        messages: [],
        isStreaming: false,
        error: null
      };
    case "ERROR_SET":
      return { ...state, error: action.error };
    case "ATTACHMENT_PROGRESS": {
      const messages = patchMessageById(state.messages, action.messageId, (m) => {
        if (!m.attachments) return m;
        return {
          ...m,
          attachments: m.attachments.map(
            (a) => a.id === action.attachmentId ? {
              ...a,
              progress: action.progress ?? a.progress,
              status: action.status ?? a.status
            } : a
          )
        };
      });
      return { ...state, messages };
    }
    default: {
      return state;
    }
  }
}
chunkOLISEQHS_cjs.__name(reducer, "reducer");

// src/tools/Chat/core/ids.ts
var counter = 0;
function createId(prefix = "m") {
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
    return `${prefix}_${crypto.randomUUID()}`;
  }
  counter += 1;
  return `${prefix}_${Date.now().toString(36)}_${counter.toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}
chunkOLISEQHS_cjs.__name(createId, "createId");
var SCOPES = ["bootstrap", "transport", "stream", "lifecycle", "tools", "error"];
var cache = /* @__PURE__ */ new Map();
function buildLogger(enabled) {
  const root = consola.consola.withTag("chat");
  const subs = Object.fromEntries(
    SCOPES.map((scope) => [scope, root.withTag(scope)])
  );
  if (!enabled) {
    for (const scope of SCOPES) {
      if (scope === "error") continue;
      subs[scope].level = -999;
    }
  }
  return { ...subs, enabled };
}
chunkOLISEQHS_cjs.__name(buildLogger, "buildLogger");
function getChatLogger(debug) {
  const enabled = debug ?? lib.isDev;
  let logger = cache.get(enabled);
  if (!logger) {
    logger = buildLogger(enabled);
    cache.set(enabled, logger);
  }
  return logger;
}
chunkOLISEQHS_cjs.__name(getChatLogger, "getChatLogger");

// src/tools/Chat/core/markdown.ts
function createTokenBuffer(onFlush, windowMs = LIMITS.streamCoalesceMs) {
  let pending = "";
  let timer = null;
  let closed = false;
  const flush = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    if (pending) {
      const out = pending;
      pending = "";
      onFlush(out);
    }
  }, "flush");
  return {
    push(delta) {
      if (closed || !delta) return;
      pending += delta;
      if (windowMs <= 0) {
        flush();
        return;
      }
      if (timer === null) {
        timer = setTimeout(flush, windowMs);
      }
    },
    flush,
    close() {
      closed = true;
      flush();
    }
  };
}
chunkOLISEQHS_cjs.__name(createTokenBuffer, "createTokenBuffer");

// src/tools/Chat/hooks/useChat.ts
function useChat(config) {
  const [state, dispatch] = react.useReducer(reducer, initialState);
  const stateRef = react.useRef(state);
  stateRef.current = state;
  const abortRef = react.useRef(null);
  const lastErrorRef = react.useRef(null);
  const streamingMsgIdRef = react.useRef(null);
  const bootstrapRef = react.useRef(null);
  const { transport, autoCreateSession = true, streaming = true, pageSize = LIMITS.pageSize } = config;
  const log = getChatLogger(config.debug);
  react.useEffect(() => {
    let cancelled = false;
    if (stateRef.current.sessionId) {
      return;
    }
    if (config.initialSessionId || autoCreateSession) {
      dispatch({ type: "HISTORY_LOAD_START" });
    }
    log.bootstrap.info("start", {
      mode: config.initialSessionId ? "resume" : autoCreateSession ? "create" : "idle",
      initialSessionId: config.initialSessionId
    });
    const run = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(async () => {
      const t0 = performance.now();
      try {
        if (config.initialSessionId) {
          if (!cancelled) {
            dispatch({
              type: "SESSION_SET",
              sessionId: config.initialSessionId
            });
          }
          const page = await transport.loadHistory(config.initialSessionId, null, pageSize);
          if (cancelled) {
            log.bootstrap.debug("cancelled (post-loadHistory)");
            return config.initialSessionId;
          }
          dispatch({
            type: "HISTORY_LOAD_DONE",
            messages: page.messages,
            hasMore: page.hasMore,
            cursor: page.nextCursor
          });
          log.bootstrap.success("resumed", {
            sessionId: config.initialSessionId,
            messages: page.messages.length,
            hasMore: page.hasMore,
            elapsedMs: Math.round(performance.now() - t0)
          });
          return config.initialSessionId;
        }
        if (autoCreateSession) {
          const info = await transport.createSession({ metadata: config.metadata });
          dispatch({
            type: "SESSION_SET",
            sessionId: info.sessionId,
            messages: info.messages ?? [],
            hasMore: info.hasMore ?? false,
            cursor: info.cursor ?? null
          });
          dispatch({
            type: "HISTORY_LOAD_DONE",
            messages: info.messages ?? [],
            hasMore: info.hasMore ?? false,
            cursor: info.cursor ?? null
          });
          log.bootstrap.success(cancelled ? "created (post-cancel)" : "created", {
            sessionId: info.sessionId,
            resumed: info.resumed ?? false,
            cancelled,
            elapsedMs: Math.round(performance.now() - t0)
          });
          return info.sessionId;
        }
        log.bootstrap.debug("idle (no initialSessionId, autoCreateSession=false)");
        return null;
      } catch (err) {
        const e = err instanceof Error ? err : new Error(String(err));
        if (cancelled) {
          log.bootstrap.debug("cancelled (in catch)", { message: e.message });
          return null;
        }
        lastErrorRef.current = e;
        dispatch({ type: "ERROR_SET", error: e.message });
        config.onError?.(e);
        log.error.error("bootstrap failed", { message: e.message, elapsedMs: Math.round(performance.now() - t0) });
        return null;
      }
    }, "run");
    bootstrapRef.current = run();
    return () => {
      cancelled = true;
    };
  }, []);
  const awaitSession = react.useCallback(async () => {
    if (stateRef.current.sessionId) return stateRef.current.sessionId;
    if (bootstrapRef.current) {
      const id = await bootstrapRef.current;
      if (id) return id;
    }
    return stateRef.current.sessionId;
  }, []);
  const consumeStream = react.useCallback(
    async (sessionId, content, attachments) => {
      const ctrl = new AbortController();
      abortRef.current = ctrl;
      const assistantId = createId("a");
      streamingMsgIdRef.current = assistantId;
      const iterator = transport.stream(sessionId, content, {
        signal: ctrl.signal,
        attachments,
        metadata: config.metadata
      });
      let peekedEvent = null;
      dispatch({ type: "STREAM_START", id: assistantId });
      config.onStreamStart?.(assistantId);
      log.stream.info("start", { sessionId, assistantId, chars: content.length });
      const tokenBuffer = createTokenBuffer(
        (delta) => dispatch({ type: "STREAM_CHUNK", delta })
      );
      let chunkCount = 0;
      let charsReceived = 0;
      const t0 = performance.now();
      try {
        const firstResult = await iterator.next();
        if (!firstResult.done) {
          const ev = firstResult.value;
          if (ev.type === "resume_start") {
            dispatch({ type: "STREAM_CANCEL_PLACEHOLDER", id: assistantId });
            dispatch({ type: "STREAM_RESUME_EXISTING" });
          } else {
            peekedEvent = ev;
          }
        }
        if (peekedEvent) handleEvent(peekedEvent);
        for await (const ev of iterator) {
          if (ctrl.signal.aborted) break;
          handleEvent(ev);
        }
        tokenBuffer.flush();
        if (stateRef.current.isStreaming) {
          dispatch({ type: "STREAM_DONE", id: assistantId });
        }
        const finalMsg = stateRef.current.messages.find((m) => m.id === assistantId);
        if (finalMsg) config.onMessageEnd?.(finalMsg);
        log.stream.success("done", {
          assistantId,
          chunks: chunkCount,
          chars: charsReceived,
          elapsedMs: Math.round(performance.now() - t0)
        });
      } catch (err) {
        tokenBuffer.close();
        if (ctrl.signal.aborted) {
          const partial = stateRef.current.messages.find((m) => m.id === assistantId)?.content ?? "";
          dispatch({ type: "STREAM_CANCELLED", id: assistantId, partialText: partial });
          log.stream.warn("cancelled", { assistantId, partialChars: partial.length });
          return;
        }
        const e = err instanceof Error ? err : new Error(String(err));
        lastErrorRef.current = e;
        dispatch({ type: "STREAM_ERROR", id: assistantId, message: e.message });
        config.onError?.(e);
        log.error.error("stream failed", { assistantId, message: e.message });
      } finally {
        tokenBuffer.close();
        if (abortRef.current === ctrl) abortRef.current = null;
        streamingMsgIdRef.current = null;
      }
      function handleEvent(ev) {
        switch (ev.type) {
          case "message_start":
            ev.messageId;
            log.stream.debug("message_start", { messageId: ev.messageId });
            return;
          case "chunk":
            tokenBuffer.push(ev.delta);
            chunkCount += 1;
            charsReceived += ev.delta.length;
            return;
          case "tool_activity":
            tokenBuffer.flush();
            dispatch({ type: "STREAM_TOOL_ACTIVITY", tool: ev.tool });
            log.tools.debug("activity", { tool: ev.tool, status: ev.status });
            return;
          case "tool_call_start": {
            tokenBuffer.flush();
            const toolCall = {
              id: ev.toolId,
              name: ev.name,
              input: ev.input,
              status: "running",
              startedAt: Date.now(),
              sourceHostname: ev.sourceHostname
            };
            dispatch({
              type: "TOOL_CALL_START",
              messageId: assistantId,
              toolCall
            });
            log.tools.info("call_start", { toolId: ev.toolId, name: ev.name });
            return;
          }
          case "tool_call_delta":
            dispatch({
              type: "TOOL_CALL_DELTA",
              messageId: assistantId,
              toolId: ev.toolId,
              delta: ev.delta
            });
            return;
          case "tool_call_end":
            dispatch({
              type: "TOOL_CALL_END",
              messageId: assistantId,
              toolId: ev.toolId,
              output: ev.output,
              status: ev.status
            });
            log.tools.info("call_end", { toolId: ev.toolId, status: ev.status });
            return;
          case "message_end":
            tokenBuffer.flush();
            dispatch({
              type: "STREAM_DONE",
              id: assistantId,
              tokensIn: ev.tokensIn,
              tokensOut: ev.tokensOut,
              sources: ev.sources
            });
            log.stream.debug("message_end", {
              tokensIn: ev.tokensIn,
              tokensOut: ev.tokensOut,
              sources: ev.sources?.length ?? 0
            });
            return;
          case "error":
            tokenBuffer.flush();
            dispatch({
              type: "STREAM_ERROR",
              id: assistantId,
              message: ev.message
            });
            log.error.error("stream event error", { code: ev.code, message: ev.message });
            return;
        }
      }
      chunkOLISEQHS_cjs.__name(handleEvent, "handleEvent");
    },
    [transport, config]
  );
  const consumeBuffered = react.useCallback(
    async (sessionId, content, attachments) => {
      const ctrl = new AbortController();
      abortRef.current = ctrl;
      try {
        const reply = await transport.send(sessionId, content, {
          signal: ctrl.signal,
          attachments,
          metadata: config.metadata
        });
        const placeholderId = createId("a");
        dispatch({ type: "STREAM_START", id: placeholderId });
        config.onStreamStart?.(placeholderId);
        dispatch({ type: "STREAM_CHUNK", delta: reply.content });
        dispatch({ type: "STREAM_DONE", id: placeholderId });
        config.onMessageEnd?.(reply);
      } catch (err) {
        const e = err instanceof Error ? err : new Error(String(err));
        lastErrorRef.current = e;
        dispatch({ type: "STREAM_ERROR", message: e.message });
        config.onError?.(e);
      } finally {
        if (abortRef.current === ctrl) abortRef.current = null;
      }
    },
    [transport, config]
  );
  const sendMessage = react.useCallback(
    async (content, attachments) => {
      log.lifecycle.info("sendMessage", {
        chars: content.length,
        attachments: attachments?.length ?? 0,
        hasSession: !!stateRef.current.sessionId
      });
      const sessionId = await awaitSession();
      if (!sessionId) {
        const e = new Error("No active session");
        lastErrorRef.current = e;
        dispatch({ type: "ERROR_SET", error: e.message });
        config.onError?.(e);
        log.error.error("sendMessage aborted: no session");
        return;
      }
      if (!content.trim() && !(attachments && attachments.length > 0)) {
        log.lifecycle.debug("sendMessage skipped (empty)");
        return;
      }
      if (stateRef.current.isStreaming) {
        log.lifecycle.debug("sendMessage skipped (already streaming)");
        return;
      }
      const userMsg = {
        id: createId("u"),
        role: "user",
        content,
        createdAt: Date.now(),
        attachments,
        sender: config.userPersona
      };
      dispatch({ type: "MESSAGE_USER_ADD", message: userMsg });
      config.onMessageSent?.(userMsg);
      if (streaming) {
        await consumeStream(sessionId, content, attachments);
      } else {
        await consumeBuffered(sessionId, content, attachments);
      }
    },
    [streaming, consumeStream, consumeBuffered, config, awaitSession]
  );
  const cancelStream = react.useCallback(() => {
    abortRef.current?.abort();
  }, []);
  const regenerate = react.useCallback(
    async (messageId) => {
      log.lifecycle.info("regenerate", { messageId: messageId ?? "(last)" });
      const messages = stateRef.current.messages;
      let targetUserIdx = -1;
      if (messageId) {
        const idx = messages.findIndex((m) => m.id === messageId);
        if (idx !== -1) {
          targetUserIdx = messages[idx].role === "user" ? idx : findPreviousUserIndex(messages, idx);
        }
      } else {
        targetUserIdx = findLastUserIndex(messages);
      }
      if (targetUserIdx === -1) return;
      const userMsg = messages[targetUserIdx];
      for (let i = messages.length - 1; i > targetUserIdx; i -= 1) {
        dispatch({ type: "MESSAGE_DELETE", id: messages[i].id });
      }
      const sessionId = await awaitSession();
      if (!sessionId) return;
      if (streaming) {
        await consumeStream(sessionId, userMsg.content, userMsg.attachments);
      } else {
        await consumeBuffered(sessionId, userMsg.content, userMsg.attachments);
      }
    },
    [streaming, consumeStream, consumeBuffered, awaitSession]
  );
  const editMessage = react.useCallback(
    async (id, content) => {
      dispatch({ type: "MESSAGE_EDIT", id, content });
      const msg = stateRef.current.messages.find((m) => m.id === id);
      if (msg?.role === "user") {
        await regenerate(id);
      }
    },
    [regenerate]
  );
  const deleteMessage = react.useCallback((id) => {
    dispatch({ type: "MESSAGE_DELETE", id });
  }, []);
  const clearMessages = react.useCallback(() => {
    abortRef.current?.abort();
    dispatch({ type: "MESSAGES_CLEAR" });
  }, []);
  const loadMore = react.useCallback(async () => {
    const sessionId = stateRef.current.sessionId;
    if (!sessionId) return;
    if (stateRef.current.isLoadingMore || !stateRef.current.hasMore) return;
    dispatch({ type: "HISTORY_MORE_START" });
    try {
      const page = await transport.loadHistory(
        sessionId,
        stateRef.current.oldestCursor,
        pageSize
      );
      dispatch({
        type: "HISTORY_MORE_DONE",
        messages: page.messages,
        hasMore: page.hasMore,
        cursor: page.nextCursor
      });
    } catch (err) {
      const e = err instanceof Error ? err : new Error(String(err));
      lastErrorRef.current = e;
      dispatch({ type: "ERROR_SET", error: e.message });
      config.onError?.(e);
    }
  }, [transport, pageSize, config]);
  const newSession = react.useCallback(async () => {
    log.lifecycle.info("newSession", { previous: stateRef.current.sessionId });
    abortRef.current?.abort();
    const previous = stateRef.current.sessionId;
    if (previous) {
      try {
        await transport.closeSession(previous);
      } catch {
      }
    }
    dispatch({ type: "MESSAGES_CLEAR" });
    try {
      const info = await transport.createSession({ metadata: config.metadata });
      dispatch({
        type: "SESSION_SET",
        sessionId: info.sessionId,
        messages: info.messages ?? [],
        hasMore: info.hasMore ?? false,
        cursor: info.cursor ?? null
      });
      log.lifecycle.success("newSession ok", { sessionId: info.sessionId });
    } catch (err) {
      const e = err instanceof Error ? err : new Error(String(err));
      lastErrorRef.current = e;
      dispatch({ type: "ERROR_SET", error: e.message });
      config.onError?.(e);
      log.error.error("newSession failed", { message: e.message });
    }
  }, [transport, config]);
  return {
    ...state,
    sendMessage,
    cancelStream,
    regenerate,
    editMessage,
    deleteMessage,
    clearMessages,
    loadMore,
    newSession,
    lastError: lastErrorRef.current
  };
}
chunkOLISEQHS_cjs.__name(useChat, "useChat");
function findLastUserIndex(messages) {
  for (let i = messages.length - 1; i >= 0; i -= 1) {
    if (messages[i].role === "user") return i;
  }
  return -1;
}
chunkOLISEQHS_cjs.__name(findLastUserIndex, "findLastUserIndex");
function findPreviousUserIndex(messages, from) {
  for (let i = from - 1; i >= 0; i -= 1) {
    if (messages[i].role === "user") return i;
  }
  return -1;
}
chunkOLISEQHS_cjs.__name(findPreviousUserIndex, "findPreviousUserIndex");
var DEFAULT_MOBILE_QUERY = "(max-width: 640px)";
function useChatLayout(config = {}) {
  const {
    defaultMode = "closed",
    storageKey = STORAGE_KEYS.mode,
    sidebarStorageKey = STORAGE_KEYS.sidebarWidth,
    reserveCssVar = CSS_VARS.reserve,
    defaultSidebarWidth = DEFAULT_SIDEBAR.width,
    minSidebarWidth = DEFAULT_SIDEBAR.min,
    maxSidebarWidth = DEFAULT_SIDEBAR.max,
    mobileQuery = DEFAULT_MOBILE_QUERY
  } = config;
  const [mode, setMode] = hooks.useLocalStorage(storageKey, defaultMode);
  const [sidebarWidthRaw, setSidebarWidthRaw] = hooks.useLocalStorage(
    sidebarStorageKey,
    defaultSidebarWidth
  );
  const isMobile = hooks.useMediaQuery(mobileQuery);
  const setSidebarWidth = react.useCallback(
    (w) => {
      const clamped = Math.max(minSidebarWidth, Math.min(maxSidebarWidth, w));
      setSidebarWidthRaw(clamped);
    },
    [setSidebarWidthRaw, minSidebarWidth, maxSidebarWidth]
  );
  const [previousOpenMode, setPreviousOpenMode] = react.useState(
    mode === "closed" ? "embedded" : mode
  );
  react.useEffect(() => {
    if (mode !== "closed") setPreviousOpenMode(mode);
  }, [mode]);
  const open = react.useCallback(() => {
    setMode(previousOpenMode === "closed" ? "embedded" : previousOpenMode);
  }, [setMode, previousOpenMode]);
  const close = react.useCallback(() => setMode("closed"), [setMode]);
  const toggle = react.useCallback(() => {
    setMode(mode === "closed" ? previousOpenMode : "closed");
  }, [setMode, mode, previousOpenMode]);
  const effectiveMode = react.useMemo(() => {
    if (mode === "closed") return "closed";
    if (isMobile && (mode === "sidebar" || mode === "floating")) return "fullscreen";
    return mode;
  }, [mode, isMobile]);
  react.useEffect(() => {
    if (typeof document === "undefined") return;
    const root = document.documentElement;
    if (effectiveMode === "sidebar") {
      root.style.setProperty(reserveCssVar, `${sidebarWidthRaw}px`);
    } else {
      root.style.removeProperty(reserveCssVar);
    }
    return () => {
      root.style.removeProperty(reserveCssVar);
    };
  }, [effectiveMode, sidebarWidthRaw, reserveCssVar]);
  return {
    mode,
    setMode,
    open,
    close,
    toggle,
    sidebarWidth: sidebarWidthRaw,
    setSidebarWidth,
    isMobile,
    effectiveMode
  };
}
chunkOLISEQHS_cjs.__name(useChatLayout, "useChatLayout");

// src/tools/Chat/core/audio/audioBus.ts
var unlocked = false;
var unlockListeners = /* @__PURE__ */ new Set();
function setUnlocked(value) {
  if (unlocked === value) return;
  unlocked = value;
  for (const cb of unlockListeners) cb(value);
}
chunkOLISEQHS_cjs.__name(setUnlocked, "setUnlocked");
function createAudioBus(options) {
  if (typeof window === "undefined") {
    return noopBus();
  }
  let sounds = options.sounds;
  const cache2 = /* @__PURE__ */ new Map();
  const getOrCreate = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((url) => {
    const hit = cache2.get(url);
    if (hit) return hit;
    const el = new Audio(url);
    el.preload = "auto";
    el.crossOrigin = "anonymous";
    cache2.set(url, el);
    return el;
  }, "getOrCreate");
  const resolveUrl = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((event) => {
    const v = sounds[event];
    if (!v) return null;
    return v;
  }, "resolveUrl");
  const play = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((event) => {
    if (options.getMuted()) return;
    if (!options.isEnabled(event)) return;
    const url = resolveUrl(event);
    if (!url) return;
    getOrCreate(url);
    const fresh = new Audio(url);
    fresh.preload = "auto";
    fresh.volume = options.getVolume();
    const p = fresh.play();
    if (p && typeof p.catch === "function") {
      p.catch(() => {
      });
    }
  }, "play");
  const preload = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((event) => {
    const url = resolveUrl(event);
    if (!url) return;
    const el = getOrCreate(url);
    try {
      el.load();
    } catch {
    }
  }, "preload");
  const unlock = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
    if (unlocked) return;
    for (const el of cache2.values()) {
      const wasMuted = el.muted;
      el.muted = true;
      const p = el.play();
      if (p && typeof p.then === "function") {
        p.then(() => {
          el.pause();
          el.currentTime = 0;
          el.muted = wasMuted;
        }).catch(() => {
          el.muted = wasMuted;
        });
      } else {
        el.pause();
        el.muted = wasMuted;
      }
    }
    setUnlocked(true);
  }, "unlock");
  return {
    play,
    preload,
    unlock,
    isUnlocked: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => unlocked, "isUnlocked"),
    subscribeUnlock(cb) {
      unlockListeners.add(cb);
      return () => unlockListeners.delete(cb);
    },
    setSounds(next) {
      sounds = next;
    },
    dispose() {
      cache2.clear();
    }
  };
}
chunkOLISEQHS_cjs.__name(createAudioBus, "createAudioBus");
function noopBus() {
  return {
    play: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => void 0, "play"),
    preload: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => void 0, "preload"),
    unlock: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => void 0, "unlock"),
    isUnlocked: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => false, "isUnlocked"),
    subscribeUnlock: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => () => void 0, "subscribeUnlock"),
    setSounds: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => void 0, "setSounds"),
    dispose: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => void 0, "dispose")
  };
}
chunkOLISEQHS_cjs.__name(noopBus, "noopBus");
var STORAGE_KEY = "djangocfg-chat-audio:prefs";
var clamp01 = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((v) => {
  if (!Number.isFinite(v)) return 1;
  return v < 0 ? 0 : v > 1 ? 1 : v;
}, "clamp01");
var useChatAudioPrefs = zustand.create()(
  middleware.persist(
    (set) => ({
      volume: 1,
      muted: false,
      enabled: {},
      setVolume: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((v) => set({ volume: clamp01(v) }), "setVolume"),
      setMuted: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((m) => set({ muted: !!m }), "setMuted"),
      setEventEnabled: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((event, enabled) => set((s) => ({ enabled: { ...s.enabled, [event]: enabled } })), "setEventEnabled")
    }),
    {
      name: STORAGE_KEY,
      storage: middleware.createJSONStorage(() => {
        if (typeof window === "undefined") {
          return {
            getItem: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => null, "getItem"),
            setItem: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => void 0, "setItem"),
            removeItem: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => void 0, "removeItem")
          };
        }
        return window.localStorage;
      }),
      partialize: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((s) => ({ volume: s.volume, muted: s.muted, enabled: s.enabled }), "partialize"),
      version: 1
    }
  )
);

// src/tools/Chat/hooks/useChatAudio.ts
var ALL_EVENTS = [
  "messageSent",
  "messageReceived",
  "streamStart",
  "error",
  "mention",
  "notification"
];
function readReducedMotion() {
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
chunkOLISEQHS_cjs.__name(readReducedMotion, "readReducedMotion");
function readReducedData() {
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
  return window.matchMedia("(prefers-reduced-data: reduce)").matches;
}
chunkOLISEQHS_cjs.__name(readReducedData, "readReducedData");
function readVisibilityHidden() {
  if (typeof document === "undefined") return false;
  return document.visibilityState === "hidden";
}
chunkOLISEQHS_cjs.__name(readVisibilityHidden, "readVisibilityHidden");
function useChatAudio(config = {}) {
  const {
    sounds = {},
    volume: volumeOverride,
    muted: mutedOverride,
    shouldPlay,
    respectReducedMotion = true,
    respectReducedData = true,
    muteWhenHidden = true
  } = config;
  const volume = useChatAudioPrefs((s) => volumeOverride != null ? volumeOverride : s.volume);
  const mutedPersisted = useChatAudioPrefs((s) => s.muted);
  const muted = mutedOverride != null ? mutedOverride : mutedPersisted;
  const enabledMap = useChatAudioPrefs((s) => s.enabled);
  const setVolumePref = useChatAudioPrefs((s) => s.setVolume);
  const setMutedPref = useChatAudioPrefs((s) => s.setMuted);
  const setEventEnabledPref = useChatAudioPrefs((s) => s.setEventEnabled);
  const volumeRef = react.useRef(volume);
  volumeRef.current = volume;
  const mutedRef = react.useRef(muted);
  mutedRef.current = muted;
  const enabledRef = react.useRef(enabledMap);
  enabledRef.current = enabledMap;
  const reducedMotionRef = react.useRef(readReducedMotion());
  const reducedDataRef = react.useRef(readReducedData());
  const hiddenRef = react.useRef(readVisibilityHidden());
  const shouldPlayRef = react.useRef(shouldPlay);
  shouldPlayRef.current = shouldPlay;
  react.useEffect(() => {
    if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
    const mqMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
    const mqData = window.matchMedia("(prefers-reduced-data: reduce)");
    const onMotion = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
      reducedMotionRef.current = mqMotion.matches;
    }, "onMotion");
    const onData = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
      reducedDataRef.current = mqData.matches;
    }, "onData");
    mqMotion.addEventListener("change", onMotion);
    mqData.addEventListener("change", onData);
    return () => {
      mqMotion.removeEventListener("change", onMotion);
      mqData.removeEventListener("change", onData);
    };
  }, []);
  react.useEffect(() => {
    if (!muteWhenHidden || typeof document === "undefined") return;
    const onVis = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
      hiddenRef.current = document.visibilityState === "hidden";
    }, "onVis");
    document.addEventListener("visibilitychange", onVis);
    return () => document.removeEventListener("visibilitychange", onVis);
  }, [muteWhenHidden]);
  const busRef = react.useRef(null);
  if (busRef.current === null) {
    busRef.current = createAudioBus({
      sounds,
      getVolume: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => volumeRef.current, "getVolume"),
      getMuted: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => effectiveMuted(), "getMuted"),
      isEnabled: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((event) => isEnabledImpl(event), "isEnabled")
    });
  }
  function effectiveMuted() {
    if (mutedRef.current) return true;
    if (muteWhenHidden && hiddenRef.current) return true;
    if (respectReducedMotion && reducedMotionRef.current) return true;
    if (respectReducedData && reducedDataRef.current) return true;
    return false;
  }
  chunkOLISEQHS_cjs.__name(effectiveMuted, "effectiveMuted");
  function isEnabledImpl(event) {
    if (shouldPlayRef.current && shouldPlayRef.current(event) === false) return false;
    const flag = enabledRef.current[event];
    if (flag === false) return false;
    return true;
  }
  chunkOLISEQHS_cjs.__name(isEnabledImpl, "isEnabledImpl");
  react.useEffect(() => {
    busRef.current?.setSounds(sounds);
  }, [sounds]);
  react.useEffect(() => {
    const bus = busRef.current;
    if (!bus) return;
    for (const event of ALL_EVENTS) {
      bus.preload(event);
    }
  }, [sounds]);
  react.useEffect(() => {
    const bus = busRef.current;
    return () => {
      bus?.dispose();
    };
  }, []);
  const isUnlocked = react.useSyncExternalStore(
    react.useCallback((cb) => busRef.current?.subscribeUnlock(cb) ?? (() => void 0), []),
    () => busRef.current?.isUnlocked() ?? false,
    () => false
  );
  const play = react.useCallback((event) => {
    busRef.current?.play(event);
  }, []);
  const preload = react.useCallback((event) => {
    busRef.current?.preload(event);
  }, []);
  const unlock = react.useCallback(() => {
    busRef.current?.unlock();
  }, []);
  const isEventEnabled = react.useCallback(
    (event) => isEnabledImpl(event),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    []
  );
  const api = react.useMemo(
    () => ({
      play,
      preload,
      unlock,
      isUnlocked,
      muted,
      setMuted: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((m) => setMutedPref(m), "setMuted"),
      volume,
      setVolume: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((v) => setVolumePref(v), "setVolume"),
      isEventEnabled,
      setEventEnabled: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((event, enabled) => setEventEnabledPref(event, enabled), "setEventEnabled")
    }),
    [play, preload, unlock, isUnlocked, muted, volume, isEventEnabled, setMutedPref, setVolumePref, setEventEnabledPref]
  );
  return api;
}
chunkOLISEQHS_cjs.__name(useChatAudio, "useChatAudio");
var Ctx = react.createContext(null);
function ChatProvider({
  transport,
  config = {},
  initialSessionId,
  autoCreateSession,
  streaming,
  audio,
  debug,
  children
}) {
  const audioApi = useChatAudio(audio ?? {});
  const audioRef = react.useRef(audioApi);
  audioRef.current = audioApi;
  const onMessageSent = react.useCallback(() => audioRef.current.play("messageSent"), []);
  const onMessageEnd = react.useCallback(() => audioRef.current.play("messageReceived"), []);
  const onStreamStart = react.useCallback(() => audioRef.current.play("streamStart"), []);
  const onError = react.useCallback(() => audioRef.current.play("error"), []);
  const chat = useChat({
    transport,
    initialSessionId,
    autoCreateSession,
    streaming,
    debug,
    metadata: {
      locale: config.locale ?? config.prefs?.locale,
      slug: config.slug
    },
    userPersona: config.user,
    onMessageSent,
    onMessageEnd,
    onStreamStart,
    onError
  });
  const layout = useChatLayout({ defaultMode: "embedded" });
  const rootRef = react.useRef(null);
  react.useEffect(() => {
    if (audioApi.isUnlocked) return;
    const root = rootRef.current;
    if (!root) return;
    const handler = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
      audioApi.unlock();
    }, "handler");
    root.addEventListener("pointerdown", handler, { once: true, capture: true });
    root.addEventListener("keydown", handler, { once: true, capture: true });
    return () => {
      root.removeEventListener("pointerdown", handler, { capture: true });
      root.removeEventListener("keydown", handler, { capture: true });
    };
  }, [audioApi]);
  const labels = react.useMemo(
    () => ({ ...DEFAULT_LABELS, ...config.labels ?? {} }),
    [config.labels]
  );
  const hasAudio = react.useMemo(() => {
    const sounds = audio?.sounds;
    if (!sounds) return false;
    return Object.values(sounds).some(
      (v) => typeof v === "string" && v.length > 0
    );
  }, [audio]);
  const value = react.useMemo(
    () => ({ ...chat, layout, config, labels, audio: audioApi, hasAudio }),
    [chat, layout, config, labels, audioApi, hasAudio]
  );
  return /* @__PURE__ */ jsxRuntime.jsx(Ctx.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: rootRef, style: { display: "contents" }, children }) });
}
chunkOLISEQHS_cjs.__name(ChatProvider, "ChatProvider");
function useChatContext() {
  const v = react.useContext(Ctx);
  if (!v) throw new Error("useChatContext must be used inside <ChatProvider>");
  return v;
}
chunkOLISEQHS_cjs.__name(useChatContext, "useChatContext");
function useChatContextOptional() {
  return react.useContext(Ctx);
}
chunkOLISEQHS_cjs.__name(useChatContextOptional, "useChatContextOptional");
var MAX_TEXTAREA_HEIGHT = 240;
function useChatComposer(options) {
  const {
    onSubmit,
    initialValue = "",
    maxLength = LIMITS.messageMaxLength,
    maxAttachments = LIMITS.attachmentsMax,
    disabled = false,
    submitOn = "enter",
    history = { enabled: true, size: LIMITS.composerHistorySize },
    onPasteFiles
  } = options;
  const [value, setValueState] = react.useState(initialValue);
  const [attachments, setAttachments] = react.useState([]);
  const [isSubmitting, setIsSubmitting] = react.useState(false);
  const textareaRef = react.useRef(null);
  const historyRef = react.useRef({ items: [], index: -1 });
  const setValue = react.useCallback(
    (next) => {
      setValueState(next.length > maxLength ? next.slice(0, maxLength) : next);
    },
    [maxLength]
  );
  react.useEffect(() => {
    const el = textareaRef.current;
    if (!el) return;
    el.style.height = "auto";
    el.style.height = `${Math.min(el.scrollHeight, MAX_TEXTAREA_HEIGHT)}px`;
  }, [value]);
  const reset = react.useCallback(() => {
    setValueState("");
    setAttachments([]);
    historyRef.current.index = -1;
  }, []);
  const focus = react.useCallback(() => {
    requestAnimationFrame(() => textareaRef.current?.focus());
  }, []);
  const submit = react.useCallback(async () => {
    const trimmed = value.trim();
    if (!trimmed && attachments.length === 0 || isSubmitting || disabled) return;
    setIsSubmitting(true);
    try {
      if (history.enabled !== false && trimmed) {
        const buf = historyRef.current.items;
        if (buf[buf.length - 1] !== trimmed) {
          buf.push(trimmed);
          if (buf.length > (history.size ?? LIMITS.composerHistorySize)) buf.shift();
        }
        historyRef.current.index = -1;
      }
      const snapshot = [...attachments];
      const text = value;
      reset();
      await onSubmit(text, snapshot);
    } finally {
      setIsSubmitting(false);
    }
  }, [value, attachments, isSubmitting, disabled, history, onSubmit, reset]);
  const addAttachment = react.useCallback(
    (a) => {
      setAttachments((prev) => {
        if (prev.some((p) => p.id === a.id)) {
          return prev.map((p) => p.id === a.id ? a : p);
        }
        if (prev.length >= maxAttachments) return prev;
        return [...prev, a];
      });
    },
    [maxAttachments]
  );
  const removeAttachment = react.useCallback((id) => {
    setAttachments((prev) => prev.filter((a) => a.id !== id));
  }, []);
  const recallPrevious = react.useCallback(() => {
    const { items } = historyRef.current;
    if (!items.length) return;
    const next = historyRef.current.index < 0 ? items.length - 1 : Math.max(0, historyRef.current.index - 1);
    historyRef.current.index = next;
    setValueState(items[next]);
  }, []);
  const recallNext = react.useCallback(() => {
    const { items } = historyRef.current;
    if (!items.length || historyRef.current.index < 0) return;
    const next = historyRef.current.index + 1;
    if (next >= items.length) {
      historyRef.current.index = -1;
      setValueState("");
      return;
    }
    historyRef.current.index = next;
    setValueState(items[next]);
  }, []);
  const onChange = react.useCallback(
    (e) => {
      setValue(e.target.value);
    },
    [setValue]
  );
  const onKeyDown = react.useCallback(
    (e) => {
      if (e.key === "Enter") {
        const isCmd = e.metaKey || e.ctrlKey;
        const shouldSend = submitOn === "cmd+enter" ? isCmd : !e.shiftKey;
        if (shouldSend) {
          e.preventDefault();
          void submit();
        }
        return;
      }
      if (history.enabled !== false && value === "" && attachments.length === 0) {
        if (e.key === "ArrowUp") {
          e.preventDefault();
          recallPrevious();
        } else if (e.key === "ArrowDown" && historyRef.current.index >= 0) {
          e.preventDefault();
          recallNext();
        }
      }
    },
    [submitOn, submit, history, value, attachments.length, recallPrevious, recallNext]
  );
  const onPaste = react.useCallback(
    (e) => {
      const files = Array.from(e.clipboardData?.files ?? []);
      if (files.length && onPasteFiles) {
        e.preventDefault();
        onPasteFiles(files);
      }
    },
    [onPasteFiles]
  );
  const canSubmit = !disabled && !isSubmitting && (value.trim().length > 0 || attachments.length > 0);
  return {
    value,
    setValue,
    attachments,
    addAttachment,
    removeAttachment,
    isSubmitting,
    canSubmit,
    submit,
    reset,
    focus,
    textareaRef,
    textareaProps: {
      ref: textareaRef,
      value,
      disabled,
      onChange,
      onKeyDown,
      onPaste
    },
    recallPrevious,
    recallNext
  };
}
chunkOLISEQHS_cjs.__name(useChatComposer, "useChatComposer");
function useChatScroll(options) {
  const {
    containerRef,
    bottomRef,
    isStreaming = false,
    bottomThresholdPx = 80,
    messagesCount = 0
  } = options;
  const [isAtBottom, setIsAtBottom] = react.useState(true);
  const [unreadCount, setUnreadCount] = react.useState(0);
  const lastCountRef = react.useRef(messagesCount);
  const stickyRef = react.useRef(true);
  const wasStreamingRef = react.useRef(isStreaming);
  const scrollToBottom = react.useCallback(
    (smooth = false) => {
      const el = containerRef.current;
      if (!el) return;
      el.scrollTo({
        top: el.scrollHeight,
        behavior: smooth ? "smooth" : "auto"
      });
      stickyRef.current = true;
      setIsAtBottom(true);
      setUnreadCount(0);
    },
    [containerRef]
  );
  const resetUnread = react.useCallback(() => setUnreadCount(0), []);
  react.useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const onScroll = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
      const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
      const atBottom = distance <= bottomThresholdPx;
      stickyRef.current = atBottom;
      setIsAtBottom(atBottom);
      if (atBottom) setUnreadCount(0);
    }, "onScroll");
    onScroll();
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      el.removeEventListener("scroll", onScroll);
    };
  }, [containerRef, bottomThresholdPx]);
  react.useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    if (isStreaming) {
      wasStreamingRef.current = true;
      if (!stickyRef.current) return;
      let raf = 0;
      const tick = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
        if (!stickyRef.current) return;
        el.scrollTop = el.scrollHeight;
        raf = requestAnimationFrame(tick);
      }, "tick");
      raf = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(raf);
    }
    if (wasStreamingRef.current && stickyRef.current) {
      wasStreamingRef.current = false;
      let raf1 = 0;
      let raf2 = 0;
      raf1 = requestAnimationFrame(() => {
        el.scrollTop = el.scrollHeight;
        raf2 = requestAnimationFrame(() => {
          el.scrollTop = el.scrollHeight;
        });
      });
      return () => {
        cancelAnimationFrame(raf1);
        cancelAnimationFrame(raf2);
      };
    }
    wasStreamingRef.current = false;
    return;
  }, [containerRef, isStreaming]);
  react.useEffect(() => {
    if (messagesCount > lastCountRef.current) {
      if (stickyRef.current) {
        const el = containerRef.current;
        if (el) el.scrollTop = el.scrollHeight;
      } else {
        setUnreadCount((n) => n + (messagesCount - lastCountRef.current));
      }
    }
    lastCountRef.current = messagesCount;
  }, [containerRef, messagesCount]);
  react.useEffect(() => {
  }, [bottomRef]);
  return { isAtBottom, unreadCount, scrollToBottom, resetUnread };
}
chunkOLISEQHS_cjs.__name(useChatScroll, "useChatScroll");
function useChatHistory(options) {
  const { enabled = true, containerRef, topSentinelRef, hasMore, isLoadingMore, loadMore } = options;
  const heightBeforeRef = react.useRef(null);
  react.useEffect(() => {
    if (heightBeforeRef.current == null) return;
    const el = containerRef.current;
    if (!el) {
      heightBeforeRef.current = null;
      return;
    }
    if (!isLoadingMore) {
      const delta = el.scrollHeight - heightBeforeRef.current;
      if (delta > 0) {
        el.scrollTop += delta;
      }
      heightBeforeRef.current = null;
    }
  }, [containerRef, isLoadingMore]);
  react.useEffect(() => {
    if (!enabled || !hasMore) return;
    const sentinel = topSentinelRef.current;
    const root = containerRef.current;
    if (!sentinel || !root) return;
    const observer = new IntersectionObserver(
      (entries) => {
        const entry = entries[0];
        if (!entry?.isIntersecting) return;
        if (isLoadingMore) return;
        const el = containerRef.current;
        if (el) heightBeforeRef.current = el.scrollHeight;
        void loadMore();
      },
      { root, threshold: 0, rootMargin: "200px 0px 0px 0px" }
    );
    observer.observe(sentinel);
    return () => observer.disconnect();
  }, [enabled, hasMore, isLoadingMore, containerRef, topSentinelRef, loadMore]);
}
chunkOLISEQHS_cjs.__name(useChatHistory, "useChatHistory");
function AttachmentsGrid({
  attachments,
  maxVisible,
  onClick,
  onRemove,
  isInComposer = false,
  layout = "wrap",
  className
}) {
  if (!attachments?.length) return null;
  const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
  return /* @__PURE__ */ jsxRuntime.jsx(
    "div",
    {
      className: lib.cn(
        layout === "grid" ? "grid grid-cols-3 gap-2" : "flex flex-wrap gap-2",
        className
      ),
      children: visible.map((a) => /* @__PURE__ */ jsxRuntime.jsx(
        AttachmentTile,
        {
          attachment: a,
          isInComposer,
          onClick: onClick ? () => onClick(a) : void 0,
          onRemove: onRemove ? () => onRemove(a) : void 0
        },
        a.id
      ))
    }
  );
}
chunkOLISEQHS_cjs.__name(AttachmentsGrid, "AttachmentsGrid");
function AttachmentsList({
  attachments,
  maxVisible,
  onClick,
  onRemove,
  renderers,
  isInComposer = false,
  className
}) {
  if (!attachments?.length) return null;
  const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: lib.cn("flex w-full flex-col gap-2", className), children: visible.map((a) => {
    const renderer = renderers?.[a.type] ?? renderers?.default;
    const args = {
      attachment: a,
      isInComposer,
      onClick: onClick ? () => onClick(a) : void 0,
      onRemove: onRemove ? () => onRemove(a) : void 0
    };
    if (renderer) {
      return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full min-w-0", children: [
        renderer(args),
        args.onRemove ? /* @__PURE__ */ jsxRuntime.jsx(RemoveBtn, { onRemove: args.onRemove }) : null
      ] }, a.id);
    }
    return /* @__PURE__ */ jsxRuntime.jsx(AttachmentTile, { ...args }, a.id);
  }) });
}
chunkOLISEQHS_cjs.__name(AttachmentsList, "AttachmentsList");
function Attachments(props) {
  const { renderers, layout, ...rest } = props;
  if (renderers) {
    return /* @__PURE__ */ jsxRuntime.jsx(AttachmentsList, { ...rest, renderers });
  }
  return /* @__PURE__ */ jsxRuntime.jsx(AttachmentsGrid, { ...rest, layout: layout === "grid" ? "grid" : "wrap" });
}
chunkOLISEQHS_cjs.__name(Attachments, "Attachments");
function AttachmentTile({ attachment, onClick, onRemove }) {
  const isImage = attachment.type === "image";
  const isUploading = attachment.status === "uploading";
  const inner = isImage ? /* @__PURE__ */ jsxRuntime.jsx(
    "img",
    {
      src: attachment.thumbnailUrl ?? attachment.url,
      alt: attachment.name ?? "attachment",
      className: "h-16 w-16 rounded-md object-cover",
      loading: "lazy"
    }
  ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-w-44 items-center gap-2 rounded-md border border-border bg-background/60 px-2 py-1.5 text-xs", children: [
    /* @__PURE__ */ jsxRuntime.jsx(lucideReact.File, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
    /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: attachment.name ?? "file" })
  ] });
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
    onClick ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick, className: "block", children: inner }) : inner,
    isUploading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center rounded-md bg-background/70 text-[10px] font-medium", children: attachment.progress != null ? `${Math.round(attachment.progress * 100)}%` : "\u2026" }) : null,
    onRemove ? /* @__PURE__ */ jsxRuntime.jsx(RemoveBtn, { onRemove }) : null
  ] });
}
chunkOLISEQHS_cjs.__name(AttachmentTile, "AttachmentTile");
function RemoveBtn({ onRemove }) {
  return /* @__PURE__ */ jsxRuntime.jsx(
    "button",
    {
      type: "button",
      "aria-label": "Remove attachment",
      onClick: onRemove,
      className: "absolute -right-1.5 -top-1.5 grid h-4 w-4 place-items-center rounded-full border border-border bg-background text-muted-foreground hover:bg-destructive hover:text-destructive-foreground",
      children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": true, className: "size-2.5" })
    }
  );
}
chunkOLISEQHS_cjs.__name(RemoveBtn, "RemoveBtn");
var SIZE_CLASSES = {
  sm: {
    slot: "[&>:not(textarea)]:h-8",
    button: "h-8 w-8",
    iconButton: "size-3.5",
    textarea: "min-h-8 max-h-48 px-3 py-1.5",
    text: "text-sm",
    padding: "gap-1.5",
    containerPadding: "px-2 pt-1.5 pb-[max(0.375rem,env(safe-area-inset-bottom))]"
  },
  md: {
    slot: "[&>:not(textarea)]:h-9",
    button: "h-9 w-9",
    iconButton: "size-4",
    textarea: "min-h-9 max-h-60 px-3.5 py-2",
    text: "text-base sm:text-sm",
    padding: "gap-1.5",
    containerPadding: "px-2.5 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))]"
  },
  lg: {
    slot: "[&>:not(textarea)]:h-12",
    button: "h-12 w-12",
    iconButton: "size-5",
    textarea: "min-h-12 max-h-72 px-4 py-3",
    text: "text-base",
    padding: "gap-2",
    containerPadding: "px-3.5 pt-3 pb-[max(0.875rem,env(safe-area-inset-bottom))]"
  }
};
var Composer = react.forwardRef(/* @__PURE__ */ chunkOLISEQHS_cjs.__name(function Composer2({
  composer,
  placeholder = "Type a message...",
  disabled,
  showAttachmentButton = false,
  onPickFiles,
  toolbarStart,
  toolbarEnd,
  attachmentTray,
  className,
  textareaClassName,
  size = "md",
  isStreaming: isStreamingProp,
  onCancel: onCancelProp
}, ref) {
  const ctx = useChatContextOptional();
  const isStreaming = isStreamingProp ?? ctx?.isStreaming ?? false;
  const onCancel = onCancelProp ?? ctx?.cancelStream;
  const isDisabled = disabled ?? isStreaming;
  const sz = SIZE_CLASSES[size];
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "div",
    {
      ref,
      className: lib.cn(
        "border-t border-border bg-background/95",
        sz.containerPadding,
        className
      ),
      children: [
        composer.attachments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1.5", children: attachmentTray ?? /* @__PURE__ */ jsxRuntime.jsx(
          Attachments,
          {
            attachments: composer.attachments,
            onRemove: (a) => composer.removeAttachment(a.id)
          }
        ) }) : null,
        /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex items-end [&>:not(textarea)]:shrink-0", sz.padding, sz.slot), children: [
          showAttachmentButton ? /* @__PURE__ */ jsxRuntime.jsx(
            components.Button,
            {
              type: "button",
              variant: "ghost",
              size: "icon",
              onClick: onPickFiles,
              "aria-label": "Attach files",
              disabled: isDisabled,
              className: sz.button,
              children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { "aria-hidden": true, className: sz.iconButton })
            }
          ) : null,
          toolbarStart,
          /* @__PURE__ */ jsxRuntime.jsx(
            components.Textarea,
            {
              ...composer.textareaProps,
              rows: 1,
              placeholder,
              "aria-label": placeholder,
              "aria-multiline": "true",
              disabled: isDisabled,
              className: lib.cn(
                "flex-1 resize-none rounded-2xl",
                sz.textarea,
                sz.text,
                textareaClassName
              )
            }
          ),
          toolbarEnd,
          isStreaming ? /* @__PURE__ */ jsxRuntime.jsx(
            components.Button,
            {
              type: "button",
              variant: "secondary",
              size: "icon",
              onClick: onCancel,
              "aria-label": "Stop",
              "aria-keyshortcuts": "Escape",
              className: sz.button,
              children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { "aria-hidden": true, className: sz.iconButton })
            }
          ) : /* @__PURE__ */ jsxRuntime.jsx(
            components.Button,
            {
              type: "button",
              size: "icon",
              onClick: () => void composer.submit(),
              disabled: !composer.canSubmit,
              "aria-label": "Send",
              "aria-keyshortcuts": "Enter",
              className: sz.button,
              children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { "aria-hidden": true, className: sz.iconButton })
            }
          )
        ] })
      ]
    }
  );
}, "Composer"));
function EmptyState({
  greeting,
  description,
  suggestions,
  onPickSuggestion,
  className
}) {
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col items-center gap-3 px-4 py-12 text-center", className), children: [
    /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid size-10 place-items-center rounded-full bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { "aria-hidden": true, className: "size-5 text-muted-foreground" }) }),
    greeting ? /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-semibold", children: greeting }) : null,
    description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: description }) : null,
    suggestions?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 grid w-full max-w-md grid-cols-1 gap-2 sm:grid-cols-2", children: suggestions.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
      "button",
      {
        type: "button",
        onClick: () => onPickSuggestion?.(s.prompt),
        className: "rounded-lg border border-border bg-background/60 px-3 py-2 text-left text-xs hover:bg-accent",
        children: s.label
      },
      s.prompt
    )) }) : null
  ] });
}
chunkOLISEQHS_cjs.__name(EmptyState, "EmptyState");
function ErrorBanner({ error, onDismiss, onRetry, className }) {
  if (!error) return null;
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "div",
    {
      role: "alert",
      className: lib.cn(
        "mx-2.5 my-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
        className
      ),
      children: [
        /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { "aria-hidden": true, className: "mt-0.5 size-3.5 shrink-0" }),
        /* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 break-words", children: error }),
        onRetry ? /* @__PURE__ */ jsxRuntime.jsxs(
          "button",
          {
            type: "button",
            onClick: onRetry,
            className: "inline-flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-destructive/15",
            children: [
              /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { "aria-hidden": true, className: "size-3" }),
              " Retry"
            ]
          }
        ) : null,
        onDismiss ? /* @__PURE__ */ jsxRuntime.jsx(
          "button",
          {
            type: "button",
            "aria-label": "Dismiss",
            onClick: onDismiss,
            className: "rounded p-0.5 hover:bg-destructive/15",
            children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": true, className: "size-3" })
          }
        ) : null
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(ErrorBanner, "ErrorBanner");
function JumpToLatest({ visible, unreadCount = 0, onClick, className }) {
  if (!visible) return null;
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "button",
    {
      type: "button",
      onClick,
      "aria-live": "polite",
      className: lib.cn(
        "pointer-events-auto inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-md hover:bg-accent",
        className
      ),
      children: [
        /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDown, { "aria-hidden": true, className: "size-3.5" }),
        unreadCount > 0 ? `${unreadCount} new` : "Jump to latest"
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(JumpToLatest, "JumpToLatest");

// src/tools/Chat/core/persona.ts
var FALLBACK_USER = { name: "You", initials: "You" };
var FALLBACK_ASSISTANT = { name: "AI", initials: "AI" };
function resolvePersona(message, user, assistant) {
  if (message.sender) return message.sender;
  if (message.role === "user") return user ?? FALLBACK_USER;
  if (message.role === "assistant") return assistant ?? FALLBACK_ASSISTANT;
  return { name: message.role };
}
chunkOLISEQHS_cjs.__name(resolvePersona, "resolvePersona");
function deriveInitials(persona, role) {
  if (persona.initials) return persona.initials;
  if (persona.name) {
    const parts = persona.name.trim().split(/\s+/);
    if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
    return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
  }
  if (role === "user") return "You";
  if (role === "assistant") return "AI";
  return "?";
}
chunkOLISEQHS_cjs.__name(deriveInitials, "deriveInitials");
function StreamingIndicatorRaw({ variant = "dots", label, className }) {
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "span",
    {
      className: lib.cn("inline-flex items-center gap-1.5 text-xs text-muted-foreground", className),
      "aria-live": "off",
      children: [
        variant === "dots" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex gap-0.5", "aria-hidden": true, children: [
          /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1 animate-bounce rounded-full bg-current [animation-delay:-0.2s]" }),
          /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1 animate-bounce rounded-full bg-current [animation-delay:-0.1s]" }),
          /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1 animate-bounce rounded-full bg-current" })
        ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block size-1.5 animate-pulse rounded-full bg-current", "aria-hidden": true }),
        label ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: label }) : null
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(StreamingIndicatorRaw, "StreamingIndicatorRaw");
var StreamingIndicator = react.memo(StreamingIndicatorRaw);
function Sources({ sources, layout = "inline", maxVisible, onClick, className }) {
  if (!sources?.length) return null;
  const visible = maxVisible ? sources.slice(0, maxVisible) : sources;
  const remaining = maxVisible ? Math.max(0, sources.length - maxVisible) : 0;
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "div",
    {
      className: lib.cn(
        "mt-2 flex flex-wrap gap-1.5",
        layout === "grid" && "grid grid-cols-2",
        className
      ),
      children: [
        visible.map((s, i) => {
          const handle = onClick ? () => onClick(s) : void 0;
          const Tag = handle ? "button" : "a";
          const props = handle ? { type: "button", onClick: handle } : { href: s.url, target: "_blank", rel: "noopener noreferrer" };
          return /* @__PURE__ */ jsxRuntime.jsxs(
            Tag,
            {
              ...props,
              className: "inline-flex max-w-full items-center gap-1 rounded-md border border-border bg-background/60 px-2 py-1 text-xs text-foreground/80 hover:bg-accent hover:text-foreground",
              title: s.snippet ?? s.title,
              children: [
                /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: s.title || s.url }),
                /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { "aria-hidden": true, className: "size-3 shrink-0 opacity-60" })
              ]
            },
            `${s.url}-${i}`
          );
        }),
        remaining > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center rounded-md border border-dashed border-border px-2 py-1 text-xs text-muted-foreground", children: [
          "+",
          remaining
        ] }) : null
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(Sources, "Sources");
function ToolCalls({
  calls,
  defaultExpanded = false,
  expandWhileStreaming = true,
  renderInput,
  renderOutput,
  renderStreaming,
  renderPayload,
  className
}) {
  if (!calls?.length) return null;
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: lib.cn("mt-2 space-y-1.5", className), children: calls.map((call) => /* @__PURE__ */ jsxRuntime.jsx(
    ToolCallItem,
    {
      call,
      defaultExpanded,
      expandWhileStreaming,
      renderInput,
      renderOutput,
      renderStreaming,
      renderPayload
    },
    call.id
  )) });
}
chunkOLISEQHS_cjs.__name(ToolCalls, "ToolCalls");
function ToolCallItem({
  call,
  defaultExpanded,
  expandWhileStreaming,
  renderInput,
  renderOutput,
  renderStreaming,
  renderPayload
}) {
  const isRunning = call.status === "running";
  const initialOpen = defaultExpanded || expandWhileStreaming && isRunning;
  const [open, setOpen] = react.useState(initialOpen);
  const userToggledRef = react.useRef(false);
  const wasRunningRef = react.useRef(isRunning);
  react.useEffect(() => {
    if (wasRunningRef.current && !isRunning) {
      if (!userToggledRef.current && !defaultExpanded) {
        setOpen(false);
      }
    }
    wasRunningRef.current = isRunning;
  }, [isRunning, defaultExpanded]);
  const handleToggle = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
    userToggledRef.current = true;
    setOpen((v) => !v);
  }, "handleToggle");
  const Icon = open ? lucideReact.ChevronDown : lucideReact.ChevronRight;
  const statusColor = call.status === "success" ? "text-emerald-500" : call.status === "error" ? "text-destructive" : call.status === "cancelled" ? "text-muted-foreground" : "text-amber-500";
  const renderValue = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((value, kind) => {
    if (kind === "input" && renderInput) return renderInput(value, call);
    if (kind === "output" && renderOutput) return renderOutput(value, call);
    if (kind === "streaming" && renderStreaming)
      return renderStreaming(typeof value === "string" ? value : String(value), call);
    if (renderPayload) return renderPayload(value, kind, call);
    return /* @__PURE__ */ jsxRuntime.jsx(DefaultPayload, { value, kind });
  }, "renderValue");
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-muted/30", children: [
    /* @__PURE__ */ jsxRuntime.jsxs(
      "button",
      {
        type: "button",
        onClick: handleToggle,
        "aria-expanded": open,
        className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs hover:bg-muted/60",
        children: [
          /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }),
          isRunning ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { "aria-hidden": true, className: "size-3 shrink-0 animate-spin text-amber-500" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: lib.cn("size-2 shrink-0 rounded-full", statusColor.replace("text-", "bg-")) }),
          /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-foreground", children: call.name }),
          /* @__PURE__ */ jsxRuntime.jsx("span", { className: lib.cn("ml-auto", statusColor), children: call.status })
        ]
      }
    ),
    open ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 border-t border-border px-2 py-1.5 text-[11px]", children: [
      call.input != null ? renderValue(call.input, "input") : null,
      call.streamingText != null ? renderValue(call.streamingText, "streaming") : call.output !== void 0 ? renderValue(call.output, "output") : null
    ] }) : null
  ] });
}
chunkOLISEQHS_cjs.__name(ToolCallItem, "ToolCallItem");
function DefaultPayload({ value, kind }) {
  const isStreamingOrString = kind === "streaming" || typeof value === "string";
  const muted = kind === "input";
  return /* @__PURE__ */ jsxRuntime.jsx(
    "pre",
    {
      className: lib.cn(
        "overflow-auto rounded bg-background/60 p-1.5 font-mono",
        kind === "input" ? "max-h-32" : "max-h-48",
        muted ? "text-muted-foreground" : "text-foreground/90"
      ),
      children: isStreamingOrString ? String(value) : safeStringify(value)
    }
  );
}
chunkOLISEQHS_cjs.__name(DefaultPayload, "DefaultPayload");
function safeStringify(value) {
  try {
    return JSON.stringify(value, null, 2);
  } catch {
    return String(value);
  }
}
chunkOLISEQHS_cjs.__name(safeStringify, "safeStringify");
function MessageActionsRaw({
  role,
  onCopy,
  onRegenerate,
  onEdit,
  onDelete,
  hideOn,
  className
}) {
  if (hideOn?.includes(role)) return null;
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "div",
    {
      className: lib.cn(
        "mt-1 flex items-center gap-0.5 opacity-0 transition-opacity group-hover/msg:opacity-100 focus-within:opacity-100",
        className
      ),
      children: [
        onCopy ? /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { onClick: onCopy, label: "Copy", icon: lucideReact.Copy }) : null,
        onRegenerate && role === "assistant" ? /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { onClick: onRegenerate, label: "Regenerate", icon: lucideReact.RefreshCw }) : null,
        onEdit && role === "user" ? /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { onClick: onEdit, label: "Edit", icon: lucideReact.Pencil }) : null,
        onDelete ? /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { onClick: onDelete, label: "Delete", icon: lucideReact.Trash, destructive: true }) : null
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(MessageActionsRaw, "MessageActionsRaw");
var ActionButton = react.memo(/* @__PURE__ */ chunkOLISEQHS_cjs.__name(function ActionButton2({
  onClick,
  label,
  icon: Icon,
  destructive
}) {
  return /* @__PURE__ */ jsxRuntime.jsx(
    "button",
    {
      type: "button",
      onClick,
      "aria-label": label,
      className: lib.cn(
        "rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
        destructive && "hover:bg-destructive/15 hover:text-destructive"
      ),
      children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": true, className: "size-3" })
    }
  );
}, "ActionButton"));
var MessageActions = react.memo(MessageActionsRaw);
var MessageBubbleInner = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(({
  message,
  isUser: isUserProp,
  showAvatar = true,
  avatarSrc,
  avatarFallback,
  user,
  assistant,
  showTimestamp = false,
  showActions = true,
  isCompact = false,
  className,
  beforeContent,
  afterContent,
  toolCallsRenderer,
  toolCallsProps,
  sourcesRenderer,
  attachmentsRenderer,
  attachmentRenderers,
  onAttachmentOpen,
  onCopy,
  onRegenerate,
  onEdit,
  onDelete
}) => {
  const isUser = isUserProp ?? message.role === "user";
  const isStreaming = !!message.isStreaming;
  const isErr = !!message.isError;
  const ctx = useChatContextOptional();
  const persona = resolvePersona(
    message,
    user ?? ctx?.config.user,
    assistant ?? ctx?.config.assistant
  );
  const initials = deriveInitials(persona, message.role);
  const personaName = persona.name ?? (isUser ? "You" : "Assistant");
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "div",
    {
      role: "article",
      "aria-label": `${personaName} said: ${message.content.slice(0, 80)}`,
      "aria-busy": isStreaming || void 0,
      "data-role": message.role,
      className: lib.cn(
        "group/msg flex gap-2.5 px-2.5 py-2",
        isUser ? "flex-row-reverse" : "flex-row",
        className
      ),
      children: [
        showAvatar ? /* @__PURE__ */ jsxRuntime.jsxs(
          components.Avatar,
          {
            className: "size-7 shrink-0",
            title: persona.description ?? personaName,
            children: [
              avatarSrc || persona.avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx(components.AvatarImage, { src: avatarSrc ?? persona.avatarUrl, alt: personaName }) : null,
              /* @__PURE__ */ jsxRuntime.jsx(components.AvatarFallback, { className: "text-[10px]", children: avatarFallback ?? initials })
            ]
          }
        ) : null,
        /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("min-w-0 flex-1", isUser && "flex flex-col items-end"), children: [
          beforeContent,
          message.attachments?.length ? attachmentsRenderer ? attachmentsRenderer(message.attachments) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1.5 w-full", children: attachmentRenderers ? /* @__PURE__ */ jsxRuntime.jsx(
            AttachmentsList,
            {
              attachments: message.attachments,
              renderers: attachmentRenderers,
              onClick: onAttachmentOpen,
              className: isUser ? "items-end" : void 0
            }
          ) : /* @__PURE__ */ jsxRuntime.jsx(
            AttachmentsGrid,
            {
              attachments: message.attachments,
              onClick: onAttachmentOpen,
              className: isUser ? "justify-end" : void 0
            }
          ) }) : null,
          /* @__PURE__ */ jsxRuntime.jsxs(
            "div",
            {
              className: lib.cn(
                "inline-block max-w-full rounded-2xl px-3.5 py-2 text-sm",
                isUser ? "bg-primary text-primary-foreground rounded-tr-md" : isErr ? "bg-destructive/10 text-destructive rounded-tl-md border border-destructive/30" : "bg-muted text-foreground rounded-tl-md"
              ),
              children: [
                isStreaming && message.toolActivity ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(StreamingIndicator, { label: message.toolActivity }) }) : null,
                message.content || !isStreaming ? /* @__PURE__ */ jsxRuntime.jsx(
                  chunkXACCHZH2_cjs.MarkdownMessage,
                  {
                    content: message.content || (isErr ? "*Failed to generate a response.*" : ""),
                    isUser,
                    isCompact,
                    plainText: isStreaming
                  }
                ) : /* @__PURE__ */ jsxRuntime.jsx(StreamingIndicator, {})
              ]
            }
          ),
          message.toolCalls?.length ? toolCallsRenderer ? toolCallsRenderer(message.toolCalls) : /* @__PURE__ */ jsxRuntime.jsx(ToolCalls, { calls: message.toolCalls, ...toolCallsProps }) : null,
          message.sources?.length && !isStreaming ? sourcesRenderer ? sourcesRenderer(message.sources) : /* @__PURE__ */ jsxRuntime.jsx(Sources, { sources: message.sources }) : null,
          showActions && !isStreaming ? /* @__PURE__ */ jsxRuntime.jsx(
            MessageActions,
            {
              role: message.role,
              onCopy,
              onRegenerate,
              onEdit,
              onDelete
            }
          ) : null,
          showTimestamp ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 text-[10px] text-muted-foreground", children: new Date(message.createdAt).toLocaleTimeString() }) : null,
          afterContent
        ] })
      ]
    }
  );
}, "MessageBubbleInner");
var MessageBubble = react.memo(MessageBubbleInner, (prev, next) => {
  const a = prev.message;
  const b = next.message;
  return a.id === b.id && a.content === b.content && a.isStreaming === b.isStreaming && a.isError === b.isError && (a.version ?? 0) === (b.version ?? 0) && a.toolActivity === b.toolActivity && a.toolCalls === b.toolCalls && a.sources === b.sources && a.attachments === b.attachments;
});
MessageBubble.displayName = "MessageBubble";
var MessageList = react.forwardRef(/* @__PURE__ */ chunkOLISEQHS_cjs.__name(function MessageList2({
  messages: messagesProp,
  renderItem,
  renderEmpty,
  isLoadingMore: isLoadingMoreProp,
  topSentinelRef,
  bottomRef,
  className,
  itemClassName
}, ref) {
  const ctx = useChatContextOptional();
  const messages = messagesProp ?? ctx?.messages ?? [];
  const isLoadingMore = isLoadingMoreProp ?? ctx?.isLoadingMore ?? false;
  const defaultRenderItem = react.useCallback(
    (m) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: itemClassName, children: /* @__PURE__ */ jsxRuntime.jsx(
      MessageBubble,
      {
        message: m,
        onCopy: () => copy(m.content),
        onRegenerate: ctx ? () => void ctx.regenerate(m.id) : void 0,
        onDelete: ctx ? () => ctx.deleteMessage(m.id) : void 0
      }
    ) }, m.id),
    [itemClassName, ctx]
  );
  const itemRenderer = renderItem ?? defaultRenderItem;
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "div",
    {
      ref,
      role: "log",
      "aria-live": "polite",
      "aria-atomic": "false",
      className: lib.cn("flex-1 overflow-y-auto", className),
      children: [
        /* @__PURE__ */ jsxRuntime.jsx("div", { ref: topSentinelRef, "aria-hidden": true }),
        isLoadingMore ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsxRuntime.jsx(components.Spinner, { className: "size-4 text-muted-foreground" }) }) : null,
        messages.length === 0 ? renderEmpty?.() ?? null : messages.map((m, i) => itemRenderer(m, i)),
        /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef, "aria-hidden": true })
      ]
    }
  );
}, "MessageList"));
function copy(text) {
  if (typeof navigator !== "undefined" && navigator.clipboard) {
    void navigator.clipboard.writeText(text);
  }
}
chunkOLISEQHS_cjs.__name(copy, "copy");
function ChatRoot(props) {
  const { transport, config, initialSessionId, autoCreateSession, streaming, audio, debug, className, listClassName, ...slots } = props;
  return /* @__PURE__ */ jsxRuntime.jsx(
    ChatProvider,
    {
      transport,
      config,
      initialSessionId,
      autoCreateSession,
      streaming,
      audio,
      debug,
      children: /* @__PURE__ */ jsxRuntime.jsx(ChatRootShell, { className, listClassName, slots })
    }
  );
}
chunkOLISEQHS_cjs.__name(ChatRoot, "ChatRoot");
function ChatRootShell({ className, listClassName, slots }) {
  const chat = useChatContext();
  const composer = useChatComposer({
    onSubmit: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((content, attachments) => chat.sendMessage(content, attachments), "onSubmit"),
    disabled: chat.isStreaming
  });
  const containerRef = react.useRef(null);
  const bottomRef = react.useRef(null);
  const topRef = react.useRef(null);
  const scroll = useChatScroll({
    containerRef,
    bottomRef,
    isStreaming: chat.isStreaming,
    messagesCount: chat.messages.length
  });
  useChatHistory({
    containerRef,
    topSentinelRef: topRef,
    hasMore: chat.hasMore,
    isLoadingMore: chat.isLoadingMore,
    loadMore: chat.loadMore
  });
  const greeting = chat.config.greeting ?? "How can I help?";
  const description = chat.config.description;
  const suggestions = chat.config.suggestions;
  const headerNode = slots.renderHeader ? slots.renderHeader(chat) : slots.header;
  const emptyNode = slots.empty ?? (slots.renderEmpty ? slots.renderEmpty({ setValue: composer.setValue, focus: composer.focus }) : /* @__PURE__ */ jsxRuntime.jsx(
    EmptyState,
    {
      greeting,
      description,
      suggestions,
      onPickSuggestion: (prompt) => {
        composer.setValue(prompt);
        composer.focus();
      }
    }
  ));
  const renderItem = slots.renderMessage ?? ((m) => /* @__PURE__ */ jsxRuntime.jsx(
    MessageBubble,
    {
      message: m,
      toolCallsProps: slots.toolCallsProps,
      attachmentRenderers: slots.attachmentRenderers,
      onAttachmentOpen: slots.onAttachmentOpen,
      onCopy: () => copy2(m.content),
      onRegenerate: () => void chat.regenerate(m.id),
      onDelete: () => chat.deleteMessage(m.id)
    },
    m.id
  ));
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("relative flex h-full min-h-0 flex-col overflow-hidden", className), children: [
    slots.banner ?? null,
    headerNode ?? null,
    /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
      /* @__PURE__ */ jsxRuntime.jsx(
        ErrorBanner,
        {
          error: chat.error,
          onDismiss: chat.error ? () => chat.clearMessages() : void 0,
          onRetry: chat.error ? () => void chat.regenerate() : void 0
        }
      ),
      /* @__PURE__ */ jsxRuntime.jsx(
        MessageList,
        {
          ref: containerRef,
          topSentinelRef: topRef,
          bottomRef,
          renderItem,
          renderEmpty: () => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: emptyNode }),
          className: listClassName
        }
      ),
      /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-2 flex justify-center", children: slots.jumpToLatest ?? /* @__PURE__ */ jsxRuntime.jsx(
        JumpToLatest,
        {
          visible: !scroll.isAtBottom,
          unreadCount: scroll.unreadCount,
          onClick: () => scroll.scrollToBottom(true)
        }
      ) })
    ] }),
    !slots.hideComposer && /* @__PURE__ */ jsxRuntime.jsx(
      Composer,
      {
        composer,
        placeholder: chat.config.placeholder,
        showAttachmentButton: slots.showAttachmentButton,
        onPickFiles: slots.onPickFiles,
        toolbarStart: slots.composerToolbarStart,
        toolbarEnd: slots.composerToolbarEnd,
        attachmentTray: slots.composerAttachmentTray,
        size: slots.composerSize
      }
    ),
    slots.footer ?? null
  ] });
}
chunkOLISEQHS_cjs.__name(ChatRootShell, "ChatRootShell");
function copy2(text) {
  if (typeof navigator !== "undefined" && navigator.clipboard) {
    void navigator.clipboard.writeText(text);
  }
}
chunkOLISEQHS_cjs.__name(copy2, "copy");

exports.Attachments = Attachments;
exports.AttachmentsGrid = AttachmentsGrid;
exports.AttachmentsList = AttachmentsList;
exports.CHAT_EVENT_NAME = CHAT_EVENT_NAME;
exports.CSS_VARS = CSS_VARS;
exports.ChatProvider = ChatProvider;
exports.ChatRoot = ChatRoot;
exports.Composer = Composer;
exports.DEFAULT_LABELS = DEFAULT_LABELS;
exports.DEFAULT_SIDEBAR = DEFAULT_SIDEBAR;
exports.DEFAULT_Z_INDEX = DEFAULT_Z_INDEX;
exports.EmptyState = EmptyState;
exports.ErrorBanner = ErrorBanner;
exports.HOTKEYS = HOTKEYS;
exports.JumpToLatest = JumpToLatest;
exports.LIMITS = LIMITS;
exports.MessageActions = MessageActions;
exports.MessageBubble = MessageBubble;
exports.MessageList = MessageList;
exports.STORAGE_KEYS = STORAGE_KEYS;
exports.Sources = Sources;
exports.StreamingIndicator = StreamingIndicator;
exports.ToolCalls = ToolCalls;
exports.createId = createId;
exports.createTokenBuffer = createTokenBuffer;
exports.deriveInitials = deriveInitials;
exports.getChatLogger = getChatLogger;
exports.initialState = initialState;
exports.reducer = reducer;
exports.resolvePersona = resolvePersona;
exports.useChat = useChat;
exports.useChatAudio = useChatAudio;
exports.useChatAudioPrefs = useChatAudioPrefs;
exports.useChatComposer = useChatComposer;
exports.useChatContext = useChatContext;
exports.useChatContextOptional = useChatContextOptional;
exports.useChatHistory = useChatHistory;
exports.useChatLayout = useChatLayout;
exports.useChatScroll = useChatScroll;
//# sourceMappingURL=chunk-OPKFKTIN.cjs.map
//# sourceMappingURL=chunk-OPKFKTIN.cjs.map