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=${encodeURIComponent(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 in React.
  • No Summary: The response has a status. It is success when a summary is available, and no-relevant-results when the query can’t be answered; the summary is then empty. Only render the summary when the status is success.
  • References Without a URL: Each reference has an index, matching the [1]-style markers in the summary, and a title. The url is only present if the source has one, so render a plain title when it’s missing.
  • 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?.status === "success" ? (
          <div>
            <div dangerouslySetInnerHTML={{ __html: summary.summary }} />
            {summary.references.length > 0 && (
              <div>
                <h3>References</h3>
                <ul>
                  {summary.references.map((ref) => (
                    <li key={ref.index}>
                      {ref.url ? (
                        <a
                          href={ref.url}
                          target="_blank"
                          rel="noopener noreferrer"
                        >
                          {ref.title}
                        </a>
                      ) : (
                        ref.title
                      )}
                    </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, or as plain text when a reference has no URL.
  7. No Relevant Results: When status is no-relevant-results, the fallback message is shown instead of an empty summary.

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: (q) => fetchSuggestions(q, 5),
    });

  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?.status === "success" ? (
          <div>
            <div dangerouslySetInnerHTML={{ __html: summary.summary }} />
            {summary.references.length > 0 && (
              <div>
                <h3>References</h3>
                <ul>
                  {summary.references.map((ref) => (
                    <li key={ref.index}>
                      {ref.url ? (
                        <a
                          href={ref.url}
                          target="_blank"
                          rel="noopener noreferrer"
                        >
                          {ref.title}
                        </a>
                      ) : (
                        ref.title
                      )}
                    </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>
  );
};