var __legacyDecorateClassTS = function(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
    r = Reflect.decorate(decorators, target, key, desc);
  else
    for (var i = decorators.length - 1;i >= 0; i--)
      if (d = decorators[i])
        r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};

// src/react/ai/AIStreamProvider.tsx
import {
  createContext,
  useContext,
  useEffect,
  useRef
} from "react";

// types/typeGuards.ts
var isValidAIServerMessage = (data) => {
  if (!data || typeof data !== "object") {
    return false;
  }
  if (!("type" in data) || typeof data.type !== "string") {
    return false;
  }
  switch (data.type) {
    case "chunk":
      return "content" in data && typeof data.content === "string" && "messageId" in data && "conversationId" in data;
    case "thinking":
      return "content" in data && typeof data.content === "string" && "messageId" in data && "conversationId" in data;
    case "tool_status":
      return "name" in data && "status" in data && "messageId" in data && "conversationId" in data;
    case "image":
      return "data" in data && typeof data.data === "string" && "format" in data && typeof data.format === "string" && "isPartial" in data && typeof data.isPartial === "boolean" && "messageId" in data && "conversationId" in data;
    case "complete":
      return "messageId" in data && "conversationId" in data;
    case "rag_retrieved":
      return "conversationId" in data && "messageId" in data && "sources" in data && Array.isArray(data.sources);
    case "error":
      return "message" in data && typeof data.message === "string";
    default:
      return false;
  }
};

// src/ai/client/connection.ts
var WS_OPEN = 1;
var WS_NORMAL_CLOSURE = 1000;
var WS_CLOSED = 3;
var DEFAULT_PING_INTERVAL = 30000;
var RECONNECT_INITIAL_DELAY = 500;
var RECONNECT_POLL_INTERVAL = 300;
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 60;
var noop = () => {};
var noopUnsubscribe = () => noop;
var NOOP_CONNECTION = {
  close: noop,
  send: noop,
  subscribe: noopUnsubscribe,
  getReadyState: () => WS_CLOSED
};
var buildWsUrl = (path) => {
  const { hostname, port, protocol } = window.location;
  const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
  const portSuffix = port ? `:${port}` : "";
  return `${wsProtocol}//${hostname}${portSuffix}${path}`;
};
var parseServerMessage = (event) => {
  let data;
  try {
    data = JSON.parse(String(event.data));
  } catch {
    return null;
  }
  if (data && typeof data === "object" && "type" in data && data.type === "pong") {
    return null;
  }
  if (!isValidAIServerMessage(data)) {
    return null;
  }
  return data;
};
var createAIConnection = (path, options = {}) => {
  if (typeof window === "undefined") {
    return NOOP_CONNECTION;
  }
  const shouldReconnect = options.reconnect !== false;
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
  const listeners = new Set;
  const connState = {
    isConnected: false,
    pingInterval: null,
    reconnectAttempts: 0,
    reconnectTimeout: null,
    ws: null
  };
  const clearTimers = () => {
    if (connState.pingInterval) {
      clearInterval(connState.pingInterval);
      connState.pingInterval = null;
    }
    if (connState.reconnectTimeout) {
      clearTimeout(connState.reconnectTimeout);
      connState.reconnectTimeout = null;
    }
  };
  const scheduleReconnect = () => {
    connState.reconnectAttempts++;
    const delay = connState.reconnectAttempts === 1 ? RECONNECT_INITIAL_DELAY : RECONNECT_POLL_INTERVAL;
    connState.reconnectTimeout = setTimeout(() => {
      if (connState.reconnectAttempts > maxReconnectAttempts) {
        return;
      }
      connect();
    }, delay);
  };
  const connect = () => {
    const url = buildWsUrl(path);
    const wsInstance = new WebSocket(url, options.protocols);
    wsInstance.onopen = () => {
      connState.isConnected = true;
      connState.reconnectAttempts = 0;
      connState.pingInterval = setInterval(() => {
        if (wsInstance.readyState === WS_OPEN && connState.isConnected) {
          wsInstance.send(JSON.stringify({ type: "ping" }));
        }
      }, pingInterval);
    };
    wsInstance.onmessage = (event) => {
      const message = parseServerMessage(event);
      if (!message) {
        return;
      }
      listeners.forEach((listener) => listener(message));
    };
    wsInstance.onclose = (event) => {
      connState.isConnected = false;
      clearTimers();
      const shouldAttemptReconnect = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && connState.reconnectAttempts < maxReconnectAttempts;
      if (shouldAttemptReconnect) {
        scheduleReconnect();
      }
    };
    wsInstance.onerror = () => {};
    connState.ws = wsInstance;
  };
  const send = (msg) => {
    if (connState.ws?.readyState === WS_OPEN) {
      connState.ws.send(JSON.stringify(msg));
    }
  };
  const subscribe = (callback) => {
    listeners.add(callback);
    return () => {
      listeners.delete(callback);
    };
  };
  const close = () => {
    clearTimers();
    if (connState.ws) {
      connState.ws.close(WS_NORMAL_CLOSURE);
      connState.ws = null;
    }
    connState.isConnected = false;
    listeners.clear();
  };
  const getReadyState = () => connState.ws?.readyState ?? WS_CLOSED;
  connect();
  return { close, getReadyState, send, subscribe };
};

// src/ai/client/messageStore.ts
var EMPTY_STATE = {
  activeConversationId: null,
  conversations: new Map,
  error: null,
  isStreaming: false
};
var initialActiveConversationId = null;
var initialError = null;
var freshState = () => ({
  activeConversationId: initialActiveConversationId,
  conversations: new Map,
  error: initialError,
  isStreaming: false
});
var findAssistantMessage = (conversation, messageId) => conversation.messages.find((msg) => msg.id === messageId && msg.role === "assistant");
var getOrCreate = (state, conversationId) => {
  let conversation = state.conversations.get(conversationId);
  if (!conversation) {
    conversation = {
      createdAt: Date.now(),
      id: conversationId,
      messages: []
    };
    state.conversations.set(conversationId, conversation);
  }
  return conversation;
};
var handleSend = (state, action) => {
  const conversation = getOrCreate(state, action.conversationId);
  const message = {
    attachments: action.attachments,
    content: action.content,
    conversationId: action.conversationId,
    id: action.messageId,
    role: "user",
    timestamp: Date.now()
  };
  conversation.messages = [...conversation.messages, message];
  state.activeConversationId = action.conversationId;
  state.error = null;
  state.isStreaming = true;
};
var handleChunk = (state, action) => {
  const conversation = getOrCreate(state, action.conversationId);
  const existingIdx = conversation.messages.findIndex((msg) => msg.id === action.messageId && msg.role === "assistant");
  if (existingIdx >= 0) {
    const prevContent = conversation.messages[existingIdx]?.content ?? "";
    conversation.messages = conversation.messages.map((msg, idx) => idx === existingIdx ? { ...msg, content: prevContent + action.content } : msg);
    return;
  }
  const message = {
    content: action.content,
    conversationId: action.conversationId,
    id: action.messageId,
    isStreaming: true,
    role: "assistant",
    timestamp: Date.now()
  };
  conversation.messages = [...conversation.messages, message];
};
var handleThinking = (state, action) => {
  const conversation = getOrCreate(state, action.conversationId);
  const existingIdx = conversation.messages.findIndex((msg) => msg.id === action.messageId && msg.role === "assistant");
  if (existingIdx >= 0) {
    const prevThinking = conversation.messages[existingIdx]?.thinking ?? "";
    conversation.messages = conversation.messages.map((msg, idx) => idx === existingIdx ? { ...msg, thinking: prevThinking + action.content } : msg);
    return;
  }
  const message = {
    content: "",
    conversationId: action.conversationId,
    id: action.messageId,
    isStreaming: true,
    role: "assistant",
    thinking: action.content,
    timestamp: Date.now()
  };
  conversation.messages = [...conversation.messages, message];
};
var upsertToolCall = (message, toolCall) => {
  if (!message.toolCalls) {
    message.toolCalls = [toolCall];
    return;
  }
  const existingIdx = message.toolCalls.findIndex((existing) => existing.name === toolCall.name);
  if (existingIdx >= 0) {
    message.toolCalls[existingIdx] = toolCall;
  } else {
    message.toolCalls = [...message.toolCalls, toolCall];
  }
};
var getOrCreateAssistantMessage = (conversation, messageId, conversationId) => {
  const existing = findAssistantMessage(conversation, messageId);
  if (existing) {
    return existing;
  }
  const message = {
    content: "",
    conversationId,
    id: messageId,
    isStreaming: true,
    role: "assistant",
    timestamp: Date.now()
  };
  conversation.messages = [...conversation.messages, message];
  return message;
};
var handleRAGRetrieved = (state, action) => {
  const conversation = getOrCreate(state, action.conversationId);
  const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
  message.sources = action.sources;
  message.retrievedAt = Date.now();
  conversation.messages = [...conversation.messages];
};
var handleToolStatus = (state, action) => {
  const conversation = getOrCreate(state, action.conversationId);
  const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
  const toolCall = {
    id: action.messageId,
    input: action.input,
    name: action.name,
    result: action.status === "complete" ? action.result ?? "" : undefined
  };
  upsertToolCall(message, toolCall);
  conversation.messages = [...conversation.messages];
};
var markMessageComplete = (conversation, messageId, usage, durationMs, model, sources) => {
  conversation.messages = conversation.messages.map((msg) => msg.id === messageId && msg.role === "assistant" ? {
    ...msg,
    durationMs,
    isStreaming: false,
    model,
    sources: sources ?? msg.sources,
    usage
  } : msg);
};
var NOT_FOUND = -1;
var findExistingImageById = (images, imageId) => {
  if (!imageId)
    return NOT_FOUND;
  return images.findIndex((img) => img.imageId === imageId);
};
var findReplaceablePartialIndex = (images) => {
  const lastIdx = images.length - 1;
  if (lastIdx >= 0 && images[lastIdx]?.isPartial) {
    return lastIdx;
  }
  return NOT_FOUND;
};
var upsertImage = (message, imageData) => {
  if (!message.images) {
    message.images = [imageData];
    return;
  }
  const existingIdx = findExistingImageById(message.images, imageData.imageId);
  if (existingIdx >= 0) {
    message.images[existingIdx] = imageData;
    return;
  }
  const replaceableIdx = findReplaceablePartialIndex(message.images);
  if (replaceableIdx >= 0) {
    message.images[replaceableIdx] = imageData;
    return;
  }
  message.images = [...message.images, imageData];
};
var handleImage = (state, action) => {
  const conversation = getOrCreate(state, action.conversationId);
  const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
  upsertImage(message, {
    data: action.data,
    format: action.format,
    imageId: action.imageId,
    isPartial: action.isPartial,
    revisedPrompt: action.revisedPrompt
  });
  conversation.messages = [...conversation.messages];
};
var handleComplete = (state, action) => {
  const conversation = state.conversations.get(action.conversationId);
  if (conversation) {
    markMessageComplete(conversation, action.messageId, action.usage, action.durationMs, action.model, action.sources);
  }
  state.isStreaming = false;
};
var markConversationStreamsComplete = (conversation) => {
  const streamingMessages = conversation.messages.filter((msg) => msg.isStreaming);
  if (streamingMessages.length === 0) {
    return;
  }
  for (const msg of streamingMessages) {
    msg.isStreaming = false;
  }
  conversation.messages = [...conversation.messages];
};
var markAllStreamsComplete = (state) => {
  for (const [, conversation] of state.conversations) {
    markConversationStreamsComplete(conversation);
  }
};
var handleBranch = (state, action) => {
  const source = state.conversations.get(action.oldConversationId);
  if (!source) {
    return;
  }
  const cutoffIndex = source.messages.findIndex((msg) => msg.id === action.fromMessageId);
  if (cutoffIndex < 0) {
    return;
  }
  const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: action.newConversationId }));
  const newConversation = {
    createdAt: Date.now(),
    id: action.newConversationId,
    messages: branchedMessages
  };
  state.conversations.set(action.newConversationId, newConversation);
  state.activeConversationId = action.newConversationId;
};
var applyAction = (state, action) => {
  switch (action.type) {
    case "send":
      handleSend(state, action);
      break;
    case "chunk":
      handleChunk(state, action);
      break;
    case "thinking":
      handleThinking(state, action);
      break;
    case "tool_status":
      handleToolStatus(state, action);
      break;
    case "image":
      handleImage(state, action);
      break;
    case "complete":
      handleComplete(state, action);
      break;
    case "error":
      state.error = action.message;
      state.isStreaming = false;
      break;
    case "rag_retrieved":
      handleRAGRetrieved(state, action);
      break;
    case "cancel":
      state.isStreaming = false;
      markAllStreamsComplete(state);
      break;
    case "branch":
      handleBranch(state, action);
      break;
    case "set_conversation":
      state.activeConversationId = action.conversationId;
      break;
  }
};
var createAIMessageStore = () => {
  let state = freshState();
  const subscribers = new Set;
  return {
    dispatch: (action) => {
      applyAction(state, action);
      state = { ...state, conversations: new Map(state.conversations) };
      subscribers.forEach((callback) => callback());
    },
    getServerSnapshot: () => EMPTY_STATE,
    getSnapshot: () => state,
    subscribe: (callback) => {
      subscribers.add(callback);
      return () => {
        subscribers.delete(callback);
      };
    }
  };
};

// src/ai/client/actions.ts
var serverMessageToAction = (msg) => {
  switch (msg.type) {
    case "chunk":
      return {
        content: msg.content,
        conversationId: msg.conversationId,
        messageId: msg.messageId,
        type: "chunk"
      };
    case "thinking":
      return {
        content: msg.content,
        conversationId: msg.conversationId,
        messageId: msg.messageId,
        type: "thinking"
      };
    case "tool_status":
      return {
        conversationId: msg.conversationId,
        input: msg.input,
        messageId: msg.messageId,
        name: msg.name,
        result: msg.result,
        status: msg.status,
        type: "tool_status"
      };
    case "image":
      return {
        conversationId: msg.conversationId,
        data: msg.data,
        format: msg.format,
        imageId: msg.imageId,
        isPartial: msg.isPartial,
        messageId: msg.messageId,
        revisedPrompt: msg.revisedPrompt,
        type: "image"
      };
    case "complete":
      return {
        conversationId: msg.conversationId,
        durationMs: msg.durationMs,
        messageId: msg.messageId,
        model: msg.model,
        sources: msg.sources,
        type: "complete",
        usage: msg.usage
      };
    case "rag_retrieved":
      return {
        conversationId: msg.conversationId,
        messageId: msg.messageId,
        sources: msg.sources,
        type: "rag_retrieved"
      };
    case "error":
      return { message: msg.message, type: "error" };
    default:
      return null;
  }
};

// src/react/ai/AIStreamProvider.tsx
import { jsxDEV } from "react/jsx-dev-runtime";
var AIStreamContext = createContext(null);
var AIStreamProvider = ({
  children,
  path
}) => {
  const ref = useRef(null);
  if (!ref.current) {
    const connection = createAIConnection(path);
    const store = createAIMessageStore();
    ref.current = { connection, store };
  }
  useEffect(() => {
    const { current } = ref;
    if (!current) {
      return;
    }
    const { connection, store } = current;
    const unsubscribe = connection.subscribe((msg) => {
      const action = serverMessageToAction(msg);
      if (action) {
        store.dispatch(action);
      }
    });
    return () => {
      unsubscribe();
      connection.close();
    };
  }, []);
  return /* @__PURE__ */ jsxDEV(AIStreamContext.Provider, {
    value: ref.current,
    children
  }, undefined, false, undefined, this);
};
var useAIStreamContext = () => useContext(AIStreamContext);
// src/react/ai/useAIStream.ts
import { useCallback, useEffect as useEffect2, useRef as useRef2, useSyncExternalStore } from "react";

// src/ai/protocol.ts
var generateId = () => crypto.randomUUID();

// src/react/ai/useAIStream.ts
var useAIStream = (path, conversationId) => {
  const context = useAIStreamContext();
  const standaloneRef = useRef2(null);
  const isStandalone = !context;
  if (isStandalone && !standaloneRef.current && path) {
    const connection2 = createAIConnection(path);
    const store2 = createAIMessageStore();
    standaloneRef.current = { connection: connection2, store: store2 };
  }
  const resolved = context ?? standaloneRef.current;
  if (!resolved) {
    throw new Error("useAIStream requires either an AIStreamProvider or a path argument");
  }
  const { connection, store } = resolved;
  useEffect2(() => {
    if (!isStandalone) {
      return;
    }
    const unsubscribe = connection.subscribe((msg) => {
      const action = serverMessageToAction(msg);
      if (action) {
        store.dispatch(action);
      }
    });
    return () => {
      unsubscribe();
      connection.close();
    };
  }, [connection, isStandalone, store]);
  const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
  const activeConvId = conversationId ?? state.activeConversationId;
  const conversation = activeConvId ? state.conversations.get(activeConvId) : undefined;
  const messages = conversation?.messages ?? [];
  const send = useCallback((content, attachments) => {
    const convId = activeConvId ?? generateId();
    const msgId = generateId();
    store.dispatch({
      attachments,
      content,
      conversationId: convId,
      messageId: msgId,
      type: "send"
    });
    connection.send({
      attachments,
      content,
      conversationId: convId,
      type: "message"
    });
  }, [activeConvId, connection, store]);
  const cancel = useCallback(() => {
    if (activeConvId) {
      store.dispatch({ type: "cancel" });
      connection.send({ conversationId: activeConvId, type: "cancel" });
    }
  }, [activeConvId, connection, store]);
  const branch = useCallback((messageId, content) => {
    if (activeConvId) {
      connection.send({
        content,
        conversationId: activeConvId,
        messageId,
        type: "branch"
      });
    }
  }, [activeConvId, connection]);
  return {
    branch,
    cancel,
    error: state.error,
    isStreaming: state.isStreaming,
    messages,
    send
  };
};
// src/react/ai/useRAGChunkPreview.ts
import { useCallback as useCallback2, useMemo, useState } from "react";
// src/constants.ts
var HOURS_IN_DAY = 24;
var MILLISECONDS_IN_A_SECOND = 1000;
var MINUTES_IN_AN_HOUR = 60;
var SECONDS_IN_A_MINUTE = 60;
var MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
var MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR * HOURS_IN_DAY;
var TWO_THIRDS = 2 / 3;
var UNFOUND_INDEX = -1;

// src/ai/client/ragClient.ts
var jsonHeaders = {
  "Content-Type": "application/json"
};
var normalizeBasePath = (path) => path.endsWith("/") ? path.slice(0, UNFOUND_INDEX) : path;
var parseJson = async (response) => {
  const payload = await response.json();
  return payload;
};
var toErrorMessage = async (response) => {
  try {
    const payload = await response.json();
    if (typeof payload.error === "string" && payload.error) {
      return payload.error;
    }
  } catch {}
  return `Request failed with status ${response.status}`;
};
var createRAGClient = (options) => {
  const basePath = normalizeBasePath(options.path);
  const fetchImpl = options.fetch ?? fetch;
  return {
    async backends() {
      const response = await fetchImpl(`${basePath}/backends`);
      if (!response.ok) {
        throw new Error(await toErrorMessage(response));
      }
      return parseJson(response);
    },
    async clearIndex() {
      const response = await fetchImpl(`${basePath}/index`, {
        method: "DELETE"
      });
      if (!response.ok) {
        throw new Error(await toErrorMessage(response));
      }
      return parseJson(response);
    },
    async createDocument(input) {
      const response = await fetchImpl(`${basePath}/documents`, {
        body: JSON.stringify(input),
        headers: jsonHeaders,
        method: "POST"
      });
      if (!response.ok) {
        return {
          error: await toErrorMessage(response),
          ok: false
        };
      }
      return parseJson(response);
    },
    async deleteDocument(id) {
      const response = await fetchImpl(`${basePath}/documents/${encodeURIComponent(id)}`, {
        method: "DELETE"
      });
      if (!response.ok) {
        return {
          error: await toErrorMessage(response),
          ok: false
        };
      }
      return parseJson(response);
    },
    async documentChunks(id) {
      const response = await fetchImpl(`${basePath}/documents/${encodeURIComponent(id)}/chunks`);
      if (!response.ok) {
        const error = await toErrorMessage(response);
        return { error, ok: false };
      }
      return parseJson(response);
    },
    async documents(kind) {
      const query = kind ? `?kind=${encodeURIComponent(kind)}` : "";
      const response = await fetchImpl(`${basePath}/documents${query}`);
      if (!response.ok) {
        throw new Error(await toErrorMessage(response));
      }
      return parseJson(response);
    },
    async ingest(chunks) {
      const response = await fetchImpl(`${basePath}/ingest`, {
        body: JSON.stringify({ chunks }),
        headers: jsonHeaders,
        method: "POST"
      });
      if (!response.ok) {
        return {
          error: await toErrorMessage(response),
          ok: false
        };
      }
      return parseJson(response);
    },
    async ingestDocuments(input) {
      const response = await fetchImpl(`${basePath}/ingest`, {
        body: JSON.stringify(input),
        headers: jsonHeaders,
        method: "POST"
      });
      if (!response.ok) {
        return {
          error: await toErrorMessage(response),
          ok: false
        };
      }
      return parseJson(response);
    },
    async reseed() {
      const response = await fetchImpl(`${basePath}/reseed`, {
        method: "POST"
      });
      if (!response.ok) {
        return {
          error: await toErrorMessage(response),
          ok: false
        };
      }
      return parseJson(response);
    },
    async reset() {
      const response = await fetchImpl(`${basePath}/reset`, {
        method: "POST"
      });
      if (!response.ok) {
        return {
          error: await toErrorMessage(response),
          ok: false
        };
      }
      return parseJson(response);
    },
    async search(input) {
      const response = await fetchImpl(`${basePath}/search`, {
        body: JSON.stringify(input),
        headers: jsonHeaders,
        method: "POST"
      });
      if (!response.ok) {
        throw new Error(await toErrorMessage(response));
      }
      const payload = await parseJson(response);
      if (!payload.ok) {
        throw new Error(payload.error ?? "RAG search failed");
      }
      return payload.results ?? [];
    },
    async status() {
      const response = await fetchImpl(`${basePath}/status`);
      if (!response.ok) {
        throw new Error(await toErrorMessage(response));
      }
      return parseJson(response);
    }
  };
};
// src/react/ai/useRAGChunkPreview.ts
var useRAGChunkPreview = (path) => {
  const client = useMemo(() => createRAGClient({ path }), [path]);
  const [preview, setPreview] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const inspect = useCallback2(async (id) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await client.documentChunks(id);
      if (!response.ok) {
        throw new Error(response.error);
      }
      setPreview(response);
      return response;
    } catch (err) {
      const message = err instanceof Error ? err.message : "Failed to load RAG chunk preview";
      setError(message);
      throw err;
    } finally {
      setIsLoading(false);
    }
  }, [client]);
  const clear = useCallback2(() => {
    setPreview(null);
    setError(null);
    setIsLoading(false);
  }, []);
  return {
    clear,
    error,
    inspect,
    isLoading,
    preview
  };
};
// src/react/ai/useRAG.ts
import { useMemo as useMemo10 } from "react";

// src/react/ai/useRAGCitations.ts
import { useMemo as useMemo2 } from "react";

// src/ai/rag/presentation.ts
var buildSourceGroupKey = (source) => source.source ?? source.title ?? source.chunkId;
var buildSourceLabel = (source) => source.source ?? source.title ?? source.chunkId;
var buildRAGCitations = (sources) => {
  const unique = new Map;
  for (const source of sources) {
    const key = source.chunkId;
    const existing = unique.get(key);
    const hasBetterExisting = existing !== undefined && existing.score >= source.score;
    if (hasBetterExisting)
      continue;
    unique.set(key, {
      chunkId: source.chunkId,
      key,
      label: buildSourceLabel(source),
      metadata: source.metadata,
      score: source.score,
      source: source.source,
      text: source.text,
      title: source.title
    });
  }
  return [...unique.values()].sort((left, right) => {
    if (right.score !== left.score) {
      return right.score - left.score;
    }
    return left.label.localeCompare(right.label);
  });
};
var buildRAGRetrievedState = (messages) => {
  const message = getLatestRetrievedMessage(messages);
  if (!message) {
    return null;
  }
  const sources = message.sources ?? [];
  return {
    citations: buildRAGCitations(sources),
    conversationId: message.conversationId,
    messageId: message.id,
    retrievedAt: message.retrievedAt,
    sourceGroups: buildRAGSourceGroups(sources),
    sources
  };
};
var buildRAGSourceGroups = (sources) => {
  const groups = new Map;
  for (const source of sources) {
    updateSourceGroup(groups, source);
  }
  return [...groups.values()].sort((left, right) => {
    if (right.bestScore !== left.bestScore) {
      return right.bestScore - left.bestScore;
    }
    return left.label.localeCompare(right.label);
  });
};
var buildSourceGroup = (source, key) => ({
  bestScore: source.score,
  chunks: [source],
  count: 1,
  key,
  label: buildSourceLabel(source),
  source: source.source,
  title: source.title
});
var updateSourceGroup = (groups, source) => {
  const key = buildSourceGroupKey(source);
  const existing = groups.get(key);
  if (!existing) {
    groups.set(key, buildSourceGroup(source, key));
    return;
  }
  existing.bestScore = Math.max(existing.bestScore, source.score);
  existing.count += 1;
  existing.chunks.push(source);
};
var getLatestAssistantMessage = (messages) => {
  for (let index = messages.length - 1;index >= 0; index -= 1) {
    const message = messages[index];
    if (message?.role === "assistant") {
      return message;
    }
  }
  return;
};
var getLatestRAGSources = (messages) => getLatestAssistantMessage(messages)?.sources ?? [];
var getLatestRetrievedMessage = (messages) => {
  for (let index = messages.length - 1;index >= 0; index -= 1) {
    const message = messages[index];
    if (message?.role === "assistant" && (message.sources?.length ?? 0) > 0) {
      return message;
    }
  }
  return;
};
var resolveRAGStreamStage = ({
  error,
  isStreaming,
  messages
}) => {
  if (error) {
    return "error";
  }
  const assistantMessage = getLatestAssistantMessage(messages);
  if (!assistantMessage) {
    return isStreaming ? "submitting" : "idle";
  }
  if (!isStreaming) {
    return "complete";
  }
  const hasSources = (assistantMessage.sources?.length ?? 0) > 0;
  const hasContent = assistantMessage.content.trim().length > 0 || assistantMessage.thinking?.trim().length || (assistantMessage.toolCalls?.length ?? 0) > 0 || (assistantMessage.images?.length ?? 0) > 0;
  if (hasSources && !hasContent) {
    return "retrieved";
  }
  return "streaming";
};

// src/react/ai/useRAGCitations.ts
var useRAGCitations = (sources) => {
  const citations = useMemo2(() => buildRAGCitations(sources), [sources]);
  const sourceGroups = useMemo2(() => buildRAGSourceGroups(sources), [sources]);
  return {
    citations,
    hasCitations: citations.length > 0,
    sourceGroups
  };
};

// src/react/ai/useRAGDocuments.ts
import { useCallback as useCallback3, useMemo as useMemo3, useState as useState2 } from "react";
var useRAGDocuments = (path) => {
  const client = useMemo3(() => createRAGClient({ path }), [path]);
  const [documents, setDocuments] = useState2([]);
  const [isLoading, setIsLoading] = useState2(false);
  const [error, setError] = useState2(null);
  const [lastResponse, setLastResponse] = useState2(null);
  const load = useCallback3(async (kind) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await client.documents(kind);
      setDocuments(response.documents);
      setLastResponse(response);
      return response;
    } catch (err) {
      const message = err instanceof Error ? err.message : "Failed to load RAG documents";
      setError(message);
      throw err;
    } finally {
      setIsLoading(false);
    }
  }, [client]);
  const reset = useCallback3(() => {
    setDocuments([]);
    setError(null);
    setLastResponse(null);
    setIsLoading(false);
  }, []);
  return {
    documents,
    error,
    isLoading,
    lastResponse,
    load,
    reset
  };
};

// src/react/ai/useRAGIngest.ts
import { useCallback as useCallback4, useMemo as useMemo4, useState as useState3 } from "react";
var useRAGIngest = (path) => {
  const client = useMemo4(() => createRAGClient({ path }), [path]);
  const [error, setError] = useState3(null);
  const [isIngesting, setIsIngesting] = useState3(false);
  const [lastIngestCount, setLastIngestCount] = useState3(null);
  const [lastDocumentCount, setLastDocumentCount] = useState3(null);
  const [lastResponse, setLastResponse] = useState3(null);
  const ingestChunks = useCallback4(async (chunks) => {
    setIsIngesting(true);
    setError(null);
    try {
      const response = await client.ingest(chunks);
      if (!response.ok) {
        throw new Error(response.error ?? "RAG ingest failed");
      }
      setLastIngestCount(response.count ?? chunks.length);
      setLastDocumentCount(null);
      setLastResponse(response);
      return response;
    } catch (caught) {
      const message = caught instanceof Error ? caught.message : String(caught);
      setError(message);
      throw caught;
    } finally {
      setIsIngesting(false);
    }
  }, [client]);
  const ingestDocuments = useCallback4(async (input) => {
    setIsIngesting(true);
    setError(null);
    try {
      const response = await client.ingestDocuments(input);
      if (!response.ok) {
        throw new Error(response.error ?? "RAG ingest failed");
      }
      setLastIngestCount(response.count ?? null);
      setLastDocumentCount(response.documentCount ?? input.documents.length);
      setLastResponse(response);
      return response;
    } catch (caught) {
      const message = caught instanceof Error ? caught.message : String(caught);
      setError(message);
      throw caught;
    } finally {
      setIsIngesting(false);
    }
  }, [client]);
  const clearIndex = useCallback4(async () => {
    setIsIngesting(true);
    setError(null);
    try {
      await client.clearIndex();
      setLastIngestCount(0);
      setLastDocumentCount(0);
      setLastResponse(null);
    } catch (caught) {
      const message = caught instanceof Error ? caught.message : String(caught);
      setError(message);
      throw caught;
    } finally {
      setIsIngesting(false);
    }
  }, [client]);
  const reset = useCallback4(() => {
    setError(null);
    setLastDocumentCount(null);
    setLastIngestCount(null);
    setLastResponse(null);
  }, []);
  return {
    clearIndex,
    error,
    ingest: ingestChunks,
    ingestChunks,
    ingestDocuments,
    isIngesting,
    lastDocumentCount,
    lastIngestCount,
    lastResponse,
    reset
  };
};

// src/react/ai/useRAGIndexAdmin.ts
import { useCallback as useCallback5, useMemo as useMemo5, useState as useState4 } from "react";
var useRAGIndexAdmin = (path) => {
  const client = useMemo5(() => createRAGClient({ path }), [path]);
  const [isLoading, setIsLoading] = useState4(false);
  const [error, setError] = useState4(null);
  const [lastMutation, setLastMutation] = useState4(null);
  const [backends, setBackends] = useState4(null);
  const run = useCallback5(async (operation) => {
    setIsLoading(true);
    setError(null);
    try {
      return await operation();
    } catch (err) {
      const message = err instanceof Error ? err.message : "RAG index administration failed";
      setError(message);
      throw err;
    } finally {
      setIsLoading(false);
    }
  }, []);
  const deleteDocument = useCallback5(async (id) => run(async () => {
    const response = await client.deleteDocument(id);
    setLastMutation(response);
    if (!response.ok) {
      throw new Error(response.error ?? "Failed to delete document");
    }
    return response;
  }), [client, run]);
  const createDocument = useCallback5(async (input) => run(async () => {
    const response = await client.createDocument(input);
    setLastMutation(response);
    if (!response.ok) {
      throw new Error(response.error ?? "Failed to create document");
    }
    return response;
  }), [client, run]);
  const reseed = useCallback5(async () => run(async () => {
    const response = await client.reseed();
    setLastMutation(response);
    if (!response.ok) {
      throw new Error(response.error ?? "Failed to reseed index");
    }
    return response;
  }), [client, run]);
  const reset = useCallback5(async () => run(async () => {
    const response = await client.reset();
    setLastMutation(response);
    if (!response.ok) {
      throw new Error(response.error ?? "Failed to reset index");
    }
    return response;
  }), [client, run]);
  const loadBackends = useCallback5(async () => run(async () => {
    const response = await client.backends();
    setBackends(response);
    return response;
  }), [client, run]);
  const clearIndex = useCallback5(async () => run(async () => {
    const response = await client.clearIndex();
    const mutation = {
      ok: response.ok
    };
    setLastMutation(mutation);
    return mutation;
  }), [client, run]);
  const resetState = useCallback5(() => {
    setIsLoading(false);
    setError(null);
    setLastMutation(null);
    setBackends(null);
  }, []);
  return {
    backends,
    clearIndex,
    createDocument,
    deleteDocument,
    error,
    isLoading,
    lastMutation,
    loadBackends,
    reseed,
    reset,
    resetState
  };
};

// src/react/ai/useRAGSearch.ts
import { useCallback as useCallback6, useMemo as useMemo6, useState as useState5 } from "react";
var useRAGSearch = (path) => {
  const client = useMemo6(() => createRAGClient({ path }), [path]);
  const [results, setResults] = useState5([]);
  const [error, setError] = useState5(null);
  const [isSearching, setIsSearching] = useState5(false);
  const [hasSearched, setHasSearched] = useState5(false);
  const [lastRequest, setLastRequest] = useState5(null);
  const search = useCallback6(async (input) => {
    setIsSearching(true);
    setError(null);
    setLastRequest(input);
    try {
      const nextResults = await client.search(input);
      setResults(nextResults);
      setHasSearched(true);
      return nextResults;
    } catch (caught) {
      const message = caught instanceof Error ? caught.message : String(caught);
      setError(message);
      throw caught;
    } finally {
      setIsSearching(false);
    }
  }, [client]);
  const reset = useCallback6(() => {
    setError(null);
    setHasSearched(false);
    setLastRequest(null);
    setResults([]);
  }, []);
  return {
    error,
    hasSearched,
    isSearching,
    lastRequest,
    reset,
    results,
    search,
    setResults
  };
};

// src/react/ai/useRAGSources.ts
import { useMemo as useMemo7 } from "react";
var useRAGSources = (messages) => {
  const latestAssistantMessage = useMemo7(() => getLatestAssistantMessage(messages), [messages]);
  const sources = useMemo7(() => getLatestRAGSources(messages), [messages]);
  const sourceGroups = useMemo7(() => buildRAGSourceGroups(sources), [sources]);
  return {
    hasSources: sources.length > 0,
    latestAssistantMessage,
    sourceGroups,
    sources
  };
};

// src/react/ai/useRAGStatus.ts
import { useCallback as useCallback7, useEffect as useEffect3, useMemo as useMemo8, useState as useState6 } from "react";
var useRAGStatus = (path, autoLoad = true) => {
  const client = useMemo8(() => createRAGClient({ path }), [path]);
  const [status, setStatus] = useState6();
  const [capabilities, setCapabilities] = useState6();
  const [error, setError] = useState6(null);
  const [isLoading, setIsLoading] = useState6(autoLoad);
  const refresh = useCallback7(async () => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await client.status();
      setStatus(response.status);
      setCapabilities(response.capabilities);
      return response;
    } catch (caught) {
      const message = caught instanceof Error ? caught.message : String(caught);
      setError(message);
      throw caught;
    } finally {
      setIsLoading(false);
    }
  }, [client]);
  const reset = useCallback7(() => {
    setCapabilities(undefined);
    setError(null);
    setIsLoading(false);
    setStatus(undefined);
  }, []);
  useEffect3(() => {
    if (!autoLoad) {
      setIsLoading(false);
      return;
    }
    refresh();
  }, [autoLoad, refresh]);
  return {
    capabilities,
    error,
    isLoading,
    refresh,
    reset,
    status
  };
};

// src/react/ai/useRAGStream.ts
import { useCallback as useCallback8, useMemo as useMemo9 } from "react";
var useRAGStream = (path, conversationId) => {
  const stream = useAIStream(path, conversationId);
  const latestAssistantMessage = useMemo9(() => getLatestAssistantMessage(stream.messages), [stream.messages]);
  const sources = useMemo9(() => getLatestRAGSources(stream.messages), [stream.messages]);
  const sourceGroups = useMemo9(() => buildRAGSourceGroups(sources), [sources]);
  const citations = useMemo9(() => buildRAGCitations(sources), [sources]);
  const retrieval = useMemo9(() => buildRAGRetrievedState(stream.messages), [stream.messages]);
  const stage = useMemo9(() => resolveRAGStreamStage({
    error: stream.error,
    isStreaming: stream.isStreaming,
    messages: stream.messages
  }), [stream.error, stream.isStreaming, stream.messages]);
  const query = useCallback8((content, attachments) => {
    stream.send(content, attachments);
  }, [stream]);
  const isRetrieving = stage === "submitting" || stage === "retrieved";
  const isAnswerStreaming = stage === "streaming";
  const hasRetrieved = (retrieval?.sources.length ?? 0) > 0;
  const isComplete = stage === "complete";
  return {
    ...stream,
    citations,
    hasRetrieved,
    hasSources: sources.length > 0,
    isAnswerStreaming,
    isComplete,
    isRetrieving,
    latestAssistantMessage,
    query,
    retrieval,
    sourceGroups,
    sources,
    stage
  };
};

// src/react/ai/useRAG.ts
var useRAG = (path, options = {}) => {
  const search = useRAGSearch(path);
  const ingest = useRAGIngest(path);
  const status = useRAGStatus(path, options.autoLoadStatus ?? true);
  const documents = useRAGDocuments(path);
  const chunkPreview = useRAGChunkPreview(path);
  const index = useRAGIndexAdmin(path);
  const stream = useRAGStream(options.streamPath ?? path, options.conversationId);
  const sources = useRAGSources(stream.messages);
  const citations = useRAGCitations(sources.sources);
  return useMemo10(() => ({
    chunkPreview,
    citations,
    documents,
    index,
    ingest,
    search,
    sources,
    status,
    stream
  }), [
    citations,
    chunkPreview,
    documents,
    ingest,
    index,
    search,
    sources,
    status,
    stream
  ]);
};
export {
  useRAGStream,
  useRAGStatus,
  useRAGSources,
  useRAGSearch,
  useRAGIngest,
  useRAGIndexAdmin,
  useRAGDocuments,
  useRAGCitations,
  useRAGChunkPreview,
  useRAG,
  useAIStream,
  AIStreamProvider
};
