4 - Add Summary

Adding Summary with Raffle’s API.

In this step, we’ll implement Summary, which provide a concise, AI-generated summary to user queries. This feature is useful for delivering immediate insights to users without requiring them to sift through multiple search results.

To fetch a summary, use the Raffle API’s /summary endpoint. This requires a uid and a query parameter.

API Call

Here’s the function to fetch a summary from the Raffle API:

export const fetchSummary = async (query) => {
  const response = await fetch(
    `https://api.raffle.ai/v2/summary?uid=$YOUR_RAFFLE_UID&query=${query}`
  );
  const data = await response.json();
  return data;
};

This function sends a GET request to the summary endpoint using the user’s query and returns the summary response.

Key Considerations

  • HTML Content: The summary content from the API is returned as HTML, allowing for rich formatting. Use dangerouslySetInnerHTML to render it safely in React.
  • Loading State: Generating a summary might take some time, so it’s crucial to provide a clear loading state to improve user experience.

Integration with React

We’ll integrate the summary feature using React Query’s useMutation. This will handle the fetching of a summary dynamically when a search is triggered.

import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { fetchSummary } from "./api";

export const Search = () => {
  const [query, setQuery] = useState("");

  const {
    data: summary,
    isPending: isLoadingSummary,
    mutate: handleFetchSummary,
  } = useMutation({
    mutationKey: ["summary"],
    mutationFn: fetchSummary,
  });

  const handleSearch = () => {
    if (query.trim()) {
      handleFetchSummary(query.trim());
    }
  };

  return (
    <div>
      <h1>Search API - React Example</h1>
      <div>
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Enter your query..."
        />
        <button onClick={handleSearch}>Search</button>
      </div>

      <div>
        <h2>Summary</h2>
        {isLoadingSummary ? (
          <p>Loading...</p>
        ) : summary ? (
          <div>
            <div dangerouslySetInnerHTML={{ __html: summary.summary }} />
            {summary.references.length > 0 && (
              <div>
                <h3>References</h3>
                <ul>
                  {summary.references.map((ref) => (
                    <li key={ref.url}>
                      <a
                        href={ref.url}
                        target="_blank"
                        rel="noopener noreferrer"
                      >
                        {ref.title}
                      </a>
                    </li>
                  ))}
                </ul>
              </div>
            )}
          </div>
        ) : (
          <p>No summary available for this query.</p>
        )}
      </div>
    </div>
  );
};

Explanation

  1. fetchSummary: Fetches the AI-generated summary for a given query from the API.
  2. useMutation: Dynamically fetches the summary when the user initiates a search.
  3. Debounced Suggestions: Ensures suggestions are fetched efficiently without overloading the API.
  4. HTML Content Rendering: Uses dangerouslySetInnerHTML to display HTML content safely in the UI.
  5. Loading State: Displays a loading indicator while waiting for the summary to be generated.
  6. References: If references are included in the summary, they are displayed as clickable links for additional context.

This step integrates the summary seamlessly into the UI, providing users with instant insights while maintaining a smooth and efficient experience.

Total Code

At this stage, your full code should look like this:

import { useEffect, useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useDebounce } from "@uidotdev/usehooks";
import { fetchSuggestions, fetchSummary, fetchTopQuestions } from "./api";

export const Search = () => {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 500); // Debounce with 500ms delay

  const { data: topQuestions = [], isLoading } = useQuery({
    queryKey: ["topQuestions"],
    queryFn: fetchTopQuestions,
  });

  const { data: suggestions = [], mutate: handleFetchSuggestions } =
    useMutation({
      mutationKey: ["suggestions", debouncedQuery],
      mutationFn: fetchSuggestions,
    });

  const {
    data: summary,
    isPending: isLoadingSummary,
    mutate: handleFetchSummary,
  } = useMutation({
    mutationKey: ["summary"],
    mutationFn: fetchSummary,
  });

  // Trigger suggestions when debouncedQuery is at least 3 characters long
  useEffect(() => {
    if (debouncedQuery.length >= 3) {
      handleFetchSuggestions(debouncedQuery);
    }
  }, [debouncedQuery, handleFetchSuggestions]);

  const handleSearch = () => {
    if (query.trim()) {
      handleFetchSummary(query.trim());
    }
  };

  return (
    <div>
      <h1>Search API - React Example</h1>
      <div>
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Enter your query..."
        />
        <button onClick={handleSearch}>Search</button>
      </div>

      <div>
        <h2>Top Questions</h2>
        {isLoading ? (
          <p>Loading top questions...</p>
        ) : (
          <ul>
            {topQuestions.map(({ question }) => (
              <li key={question} onClick={() => setQuery(question)}>
                {question}
              </li>
            ))}
          </ul>
        )}
      </div>

      <div>
        <h2>Suggestions</h2>
        <ul>
          {suggestions.map(({ suggestion }, index) => (
            <li
              key={index}
              onClick={() => {
                setQuery(suggestion);
                handleSearch();
              }}
            >
              {suggestion}
            </li>
          ))}
        </ul>
      </div>

      <div>
        <h2>Summary</h2>
        {isLoadingSummary ? (
          <p>Loading...</p>
        ) : summary ? (
          <div>
            <div dangerouslySetInnerHTML={{ __html: summary.summary }} />
            {summary.references.length > 0 && (
              <div>
                <h3>References</h3>
                <ul>
                  {summary.references.map((ref) => (
                    <li key={ref.url}>
                      <a
                        href={ref.url}
                        target="_blank"
                        rel="noopener noreferrer"
                      >
                        {ref.title}
                      </a>
                    </li>
                  ))}
                </ul>
              </div>
            )}
          </div>
        ) : (
          <p>No summary available for this query.</p>
        )}
      </div>

      <div>
        <h2>Search Results</h2>
        <p>Search results will appear here...</p>
      </div>
    </div>
  );
};