> ## Documentation Index
> Fetch the complete documentation index at: https://docs.theswarm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Query Translator

> Transforms natural language requests into accurate OpenSearch DSL queries for The Swarm API using AI-powered interpretation.

export const SwarmQueryTranslator = () => {
  const [query, setQuery] = useState("");
  const [result, setResult] = useState("");
  const [loading, setLoading] = useState(false);
  const [queryType, setQueryType] = useState("profiles");
  const [copied, setCopied] = useState(false);
  const [currentPlaceholderIndex, setCurrentPlaceholderIndex] = useState(0);
  const [displayedPlaceholder, setDisplayedPlaceholder] = useState("");
  const [isTyping, setIsTyping] = useState(false);
  const [isFading, setIsFading] = useState(false);
  const placeholders = ["Find CTOs in fintech startups in London", "Show companies in healthcare with more than 100 employees", "Find data scientists at AI companies in New York"];
  useEffect(() => {
    const interval = setInterval(() => {
      const nextIndex = (currentPlaceholderIndex + 1) % placeholders.length;
      setCurrentPlaceholderIndex(nextIndex);
      setIsFading(true);
      setTimeout(() => {
        setIsFading(false);
        setIsTyping(true);
        const targetPlaceholder = placeholders[nextIndex];
        let currentText = "";
        let charIndex = 0;
        const typeInterval = setInterval(() => {
          if (charIndex < targetPlaceholder.length) {
            currentText += targetPlaceholder[charIndex];
            setDisplayedPlaceholder(currentText);
            charIndex++;
          } else {
            clearInterval(typeInterval);
            setIsTyping(false);
          }
        }, 30);
      }, 350);
    }, 3500);
    return () => clearInterval(interval);
  }, [currentPlaceholderIndex, placeholders.length]);
  useEffect(() => {
    setDisplayedPlaceholder(placeholders[0]);
  }, []);
  const handleSubmit = async () => {
    if (!query.trim()) return;
    setLoading(true);
    setResult("");
    try {
      const response = await fetch(`${API_BASE}/api/convert`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          query: query.trim(),
          type: queryType,
          provider: "openai"
        })
      });
      const data = await response.json().catch(() => null);
      if (response.ok && data?.success) {
        setResult(JSON.stringify(data.query, null, 2));
      } else {
        setResult(`Error: ${data?.error || `Request failed (status ${response.status})`}`);
      }
    } catch (error) {
      setResult("Failed to connect to server (network or CORS). In DevTools → Network, check the /api/convert request — Mintlify preview hosts must be allowlisted on the API.");
    } finally {
      setLoading(false);
    }
  };
  const handleKeyPress = e => {
    if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
      handleSubmit();
    }
  };
  const copyToClipboard = async () => {
    if (!result) return;
    try {
      await navigator.clipboard.writeText(result);
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    } catch (error) {
      console.error("Failed to copy to clipboard:", error);
    }
  };
  const renderJSON = text => {
    if (!text) return null;
    if (text.startsWith("Error:")) {
      return <div className="json-error">{text}</div>;
    }
    try {
      const jsonObj = JSON.parse(text);
      const renderValue = (value, indent = 0) => {
        const spaces = ("  ").repeat(indent);
        if (value === null) {
          return <span className="json-null">null</span>;
        }
        if (typeof value === "boolean") {
          return <span className="json-boolean">{String(value)}</span>;
        }
        if (typeof value === "number") {
          return <span className="json-number">{value}</span>;
        }
        if (typeof value === "string") {
          return <span className="json-string">"{value}"</span>;
        }
        if (Array.isArray(value)) {
          if (value.length === 0) {
            return <>
                <span className="json-punctuation">[</span>
                <span className="json-punctuation">]</span>
              </>;
          }
          return <>
              <span className="json-punctuation">[</span>
              <div style={{
            marginLeft: "20px"
          }}>
                {value.map((item, index) => <div key={index}>
                    {spaces}
                    {renderValue(item, indent + 1)}
                    {index < value.length - 1 && <span className="json-punctuation">,</span>}
                  </div>)}
              </div>
              {spaces}<span className="json-punctuation">]</span>
            </>;
        }
        if (typeof value === "object") {
          const keys = Object.keys(value);
          if (keys.length === 0) {
            return <>
                <span className="json-punctuation">{`{`}</span>
                <span className="json-punctuation">{`}`}</span>
              </>;
          }
          return <>
              <span className="json-punctuation">{`{`}</span>
              <div style={{
            marginLeft: "20px"
          }}>
                {keys.map((key, index) => <div key={key}>
                    {spaces}
                    <span className="json-key">"{key}"</span>
                    <span className="json-punctuation">: </span>
                    {renderValue(value[key], indent + 1)}
                    {index < keys.length - 1 && <span className="json-punctuation">,</span>}
                  </div>)}
              </div>
              {spaces}<span className="json-punctuation">{`}`}</span>
            </>;
        }
        return String(value);
      };
      return renderValue(jsonObj);
    } catch (e) {
      return <div>{text}</div>;
    }
  };
  return <div style={{
    padding: "12px 24px 24px",
    border: "1px solid rgba(255, 255, 255, 0.1)",
    borderRadius: "12px",
    margin: "20px 0",
    background: "rgba(255, 255, 255, 0.05)",
    color: "#f9fafb",
    fontFamily: "Roobert Pro, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif"
  }}>
      {}
      <div style={{
    marginBottom: "16px"
  }}>
        <div style={{
    display: "flex",
    background: "transparent",
    borderRadius: "0",
    padding: "0",
    gap: "0"
  }}>
          <button onClick={() => setQueryType("profiles")} style={{
    flex: "none",
    padding: "12px 20px",
    border: "none",
    borderRadius: "0",
    background: "transparent",
    color: queryType === "profiles" ? "rgb(255, 157, 87)" : "rgba(255,255,255,0.9)",
    cursor: "pointer",
    fontSize: "14px",
    fontWeight: "500",
    borderBottom: queryType === "profiles" ? "1px solid rgb(255, 157, 87)" : "none",
    transition: "all 0.2s ease"
  }}>
            Profiles
          </button>
          <button onClick={() => setQueryType("companies")} style={{
    flex: "none",
    padding: "12px 20px",
    border: "none",
    borderRadius: "0",
    background: "transparent",
    color: queryType === "companies" ? "rgb(255, 157, 87)" : "rgba(255,255,255,0.9)",
    cursor: "pointer",
    fontSize: "14px",
    fontWeight: "500",
    borderBottom: queryType === "companies" ? "1px solid rgb(255, 157, 87)" : "none",
    transition: "all 0.2s ease"
  }}>
            Companies
          </button>
        </div>
      </div>


      {}
      <div style={{
    marginBottom: "16px",
    position: "relative"
  }}>
        <textarea value={query} onChange={e => setQuery(e.target.value)} onKeyDown={handleKeyPress} placeholder="" style={{
    width: "100%",
    padding: "12px 16px",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "8px",
    background: "rgba(0,0,0,0.5)",
    color: "#f9fafb",
    fontSize: "14px",
    lineHeight: "1.5",
    resize: "vertical",
    minHeight: "80px",
    fontFamily: "inherit"
  }} />
        {}
        {!query && <div style={{
    position: "absolute",
    top: "12px",
    left: "16px",
    right: "16px",
    pointerEvents: "none",
    color: "rgba(255,255,255,0.5)",
    fontSize: "14px",
    lineHeight: "1.5",
    fontFamily: "inherit",
    whiteSpace: "pre-wrap",
    wordWrap: "break-word",
    opacity: isFading ? 0.3 : 1,
    transition: "opacity 0.3s ease-in-out"
  }}>
            {displayedPlaceholder}
          </div>}
      </div>

      {}
      <button onClick={handleSubmit} disabled={loading || !query.trim()} style={{
    width: "100%",
    padding: "12px 24px",
    background: loading || !query.trim() ? "rgb(55 55 55)" : "rgb(255, 157, 87)",
    color: !loading && query.trim() ? "black" : "white",
    border: "none",
    borderRadius: "8px",
    fontSize: "15px",
    fontWeight: "600",
    cursor: loading || !query.trim() ? "not-allowed" : "pointer",
    marginBottom: "16px",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "8px"
  }}>
        {loading ? <>
            <div style={{
    width: "16px",
    height: "16px",
    border: "2px solid transparent",
    borderTop: "2px solid white",
    borderRadius: "50%",
    animation: "spin 1s linear infinite"
  }} />
            Converting...
          </> : <>
          Convert
          </>}
      </button>

      {}
      <div style={{
    marginBottom: "20px"
  }}>
        <div style={{
    fontSize: "14px",
    fontWeight: "500",
    color: "rgba(255,255,255,0.8)",
    marginBottom: "8px"
  }}>
          Try these examples:
        </div>
        <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "8px"
  }}>
          {(queryType === "profiles" ? ["Find CEOs at AI companies", "CTOs in fintech startups", "Data scientists in San Francisco"] : ["Companies that raised Series A", "AI startups in California", "Fintech companies over $10M"]).map((example, index) => <button key={index} onClick={() => setQuery(example)} style={{
    padding: "4px 8px",
    background: "rgba(255,255,255,0.1)",
    borderRadius: "8px",
    color: "#d1d5db",
    fontSize: "12px",
    cursor: "pointer",
    transition: "all 0.2s"
  }} onMouseOver={e => {
    e.target.style.background = "rgba(255,255,255,0.2)";
    e.target.style.color = "rgba(255,157,87,1)";
  }} onMouseOut={e => {
    e.target.style.background = "rgba(255,255,255,0.1)";
    e.target.style.color = "#d1d5db";
  }}>
              {example}
            </button>)}
        </div>
      </div>

      {}
      {result && <div style={{
    marginTop: "20px"
  }}>
          <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "space-between",
    marginBottom: "12px"
  }}>
            <h4 style={{
    margin: 0,
    fontSize: "16px",
    fontWeight: "600"
  }}>
              {result.startsWith("Error:") ? "Error" : "Generated Query"}
            </h4>
            {!result.startsWith("Error:") && <button onClick={copyToClipboard} style={{
    padding: "6px 12px",
    background: copied ? "rgba(255, 140, 0, 0.15)" : "rgba(255,255,255, 0.2)",
    border: copied ? "1px solid orange" : "1px solid rgba(0, 0, 0, 0.9)",
    borderRadius: "6px",
    color: copied ? "#ff9800" : "#d1d5db",
    fontSize: "12px",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    gap: "6px",
    transition: "all 0.2s"
  }} disabled={copied}>
                {copied ? <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" style={{
    display: "inline-block",
    verticalAlign: "middle",
    color: "#ff9800"
  }} aria-hidden="true" focusable="false">
                    <path d="M4.5 9.5L8 13L14 7" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
                    <circle cx="9" cy="9" r="8" stroke="currentColor" strokeWidth="1.2" fill="none" />
                  </svg> : <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" style={{
    display: "inline-block",
    verticalAlign: "middle",
    color: "#a1a1aa"
  }} aria-hidden="true" focusable="false">
                    <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"></path>
                    <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"></path>
                  </svg>}
                {copied ? "Copied" : "Copy"}
              </button>}
          </div>
          <div style={{
    background: "rgba(0,0,0,1.0)",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "8px",
    padding: "16px",
    fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
    fontSize: "12px",
    lineHeight: "1.4",
    overflow: "auto",
    color: "#f9fafb",
    margin: 0,
    minHeight: "100px"
  }}>
            {renderJSON(result)}
          </div>
        </div>}

      {}
      <div style={{
    marginTop: "16px",
    padding: "12px 16px",
    background: "rgba(0,128,255,0.15)",
    border: "1px solid rgba(0,125,255,.5)",
    borderRadius: "8px",
    fontSize: "12px",
    color: "rgba(255,255,255,0.9)",
    display: "flex",
    alignItems: "flex-start",
    gap: "10px"
  }}>
        <svg viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-label="Info" style={{
    flex: "none",
    width: "20px",
    height: "20px",
    color: "#a1a1aa",
    marginTop: "2px"
  }}>
          <path d="M8 0C3.58125 0 0 3.58125 0 8C0 12.4187 3.58125 16 8 16C12.4187 16 16 12.4187 16 8C16 3.58125 12.4187 0 8 0ZM8 14.5C4.41563 14.5 1.5 11.5841 1.5 8C1.5 4.41594 4.41563 1.5 8 1.5C11.5844 1.5 14.5 4.41594 14.5 8C14.5 11.5841 11.5844 14.5 8 14.5ZM9.25 10.5H8.75V7.75C8.75 7.3375 8.41563 7 8 7H7C6.5875 7 6.25 7.3375 6.25 7.75C6.25 8.1625 6.5875 8.5 7 8.5H7.25V10.5H6.75C6.3375 10.5 6 10.8375 6 11.25C6 11.6625 6.3375 12 6.75 12H9.25C9.66406 12 10 11.6641 10 11.25C10 10.8359 9.66563 10.5 9.25 10.5ZM8 6C8.55219 6 9 5.55219 9 5C9 4.44781 8.55219 4 8 4C7.44781 4 7 4.44687 7 5C7 5.55313 7.44687 6 8 6Z"></path>
        </svg>
        <div>
          <strong>Tips:</strong> Use natural language to describe what you're looking for.<br />
          For profiles, mention job titles, companies, locations, or industries.<br />
          For companies, mention funding, size, industry, or location.<br />
        </div>
      </div>

      <style>{`
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }



        /* JSON Syntax Highlighting */
        .json-key {
          color: #7dd3fc; /* Light blue/cyan for keys */
        }

        .json-string {
          color: #fbbf24; /* Light orange/peach for strings */
        }

        .json-number {
          color: #86efac; /* Light green for numbers */
        }

        .json-boolean {
          color: #c084fc; /* Purple for booleans */
        }

        .json-null {
          color: #f87171; /* Red for null */
        }

        .json-punctuation {
          color: #ffffff; /* White for punctuation */
        }

        .json-error {
          color: #f87171; /* Red for error messages */
        }
      `}</style>
    </div>;
};

<SwarmQueryTranslator />

[Test in Query Tester](/docs/examples/api-query-tester)
