SearchView.tsx•5.88 kB
"use client";
import classNames from "classnames";
import { useCallback, useEffect, useRef, useState } from "react";
import { LoadingIndicator } from "@/ui/App";
import { CTAButton, Select, TextArea, Input } from "@/ui/elements";
import useChat from "@/modules/chat/hooks/useChat";
import styles from "./SearchView.module.css";
interface SelectOption {
  value: string;
  label: string;
}
interface SearchFormPayload extends HTMLFormElement {
  chatInput: HTMLInputElement;
}
const MAIN_DATASET = {
  id: "",
  data: [],
  status: "",
  name: "main_dataset",
};
export default function SearchView() {
  const searchOptions: SelectOption[] = [{
    value: "GRAPH_COMPLETION",
    label: "GraphRAG Completion",
  }, {
    value: "RAG_COMPLETION",
    label: "RAG Completion",
  }];
  const scrollToBottom = useCallback(() => {
    setTimeout(() => {
      const messagesContainerElement = document.getElementById("messages");
      if (messagesContainerElement) {
        const messagesElements = messagesContainerElement.children[0];
        if (messagesElements) {
          messagesContainerElement.scrollTo({
            top: messagesElements.scrollHeight,
            behavior: "smooth",
          });
        }
      }
    }, 300);
  }, []);
  // Hardcoded to `main_dataset` for now, change when multiple datasets are supported.
  const { messages, refreshChat, sendMessage, isSearchRunning } = useChat(MAIN_DATASET);
  useEffect(() => {
    refreshChat()
      .then(() => scrollToBottom());
  }, [refreshChat, scrollToBottom]);
  const [searchInputValue, setSearchInputValue] = useState("");
  // Add state for top_k
  const [topK, setTopK] = useState(10);
  const handleSearchInputChange = useCallback((value: string) => {
    setSearchInputValue(value);
  }, []);
  // Add handler for top_k input
  const handleTopKChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    let value = parseInt(e.target.value, 10);
    if (isNaN(value)) value = 10;
    if (value < 1) value = 1;
    if (value > 100) value = 100;
    setTopK(value);
  }, []);
  const handleChatMessageSubmit = useCallback((event: React.FormEvent<SearchFormPayload>) => {
    event.preventDefault();
    const formElements = event.currentTarget;
    const searchType = formElements.searchType.value;
    const chatInput = searchInputValue.trim();
    if (chatInput === "") {
      return;
    }
    scrollToBottom();
    setSearchInputValue("");
    
    // Pass topK to sendMessage
    sendMessage(chatInput, searchType, topK)
      .then(scrollToBottom)
  }, [scrollToBottom, sendMessage, searchInputValue, topK]);
  const chatFormRef = useRef<HTMLFormElement>(null);
  const handleSubmitOnEnter = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (event.key === "Enter" && !event.shiftKey) {
      chatFormRef.current?.requestSubmit();
    }
  };
  return (
    <div className="flex flex-col h-full bg-gray-500 p-6 pt-16 rounded-3xl border-indigo-600 border-2 shadow-xl">
      <form onSubmit={handleChatMessageSubmit} ref={chatFormRef} className="flex flex-col gap-4 h-full">
        <div className="h-full overflow-y-auto" id="messages">
          <div className="flex flex-col gap-2 items-end justify-end min-h-full px-6 pb-4">
            {messages.map((message) => (
              <p
                key={message.id}
                className={classNames({
                  [classNames("ml-12 px-6 py-4 bg-gray-300 rounded-xl", styles.userMessage)]: message.user === "user",
                  [classNames("text-gray-200", styles.systemMessage)]: message.user !== "user",
                })}
              >
                {message?.text && (
                  typeof(message.text) == "string" ? message.text : JSON.stringify(message.text)
                )}
              </p>
            ))}
          </div>
        </div>
        <div className="p-4 bg-gray-300 rounded-xl flex flex-col gap-2">
          <TextArea
            value={searchInputValue}
            onChange={handleSearchInputChange}
            onKeyUp={handleSubmitOnEnter}
            isAutoExpanding
            name="chatInput"
            placeholder="Ask anything"
            contentEditable={true}
            className="resize-none min-h-14 max-h-96 overflow-y-auto"
          />
          <div className="flex flex-row items-center justify-between gap-4">
            <div className="flex flex-row items-center gap-4">
              <div className="flex flex-row items-center gap-2">
                <label className="text-gray-600 whitespace-nowrap">Search type:</label>
                <Select name="searchType" defaultValue={searchOptions[0].value} className="max-w-2xs">
                  {searchOptions.map((option) => (
                    <option key={option.value} value={option.value}>{option.label}</option>
                  ))}
                </Select>
              </div>
              <div className="flex flex-row items-center gap-2">
                <label className="text-gray-600 whitespace-nowrap" title="Controls how many results to return. Smaller = focused, larger = broader graph exploration.">
                  Max results:
                </label>
                <Input
                  type="number"
                  name="topK"
                  min={1}
                  max={100}
                  value={topK}
                  onChange={handleTopKChange}
                  className="w-20"
                  title="Controls how many results to return. Smaller = focused, larger = broader graph exploration."
                />
              </div>
            </div>
            <CTAButton disabled={isSearchRunning} type="submit">
              {isSearchRunning? "Searching..." : "Search"}
              {isSearchRunning && <LoadingIndicator />}
            </CTAButton>
          </div>
        </div>
      </form>
    </div>
  );
}