Skip to content

Commit

Permalink
Add SearchApi search provider
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastjanPrachovskij committed Sep 17, 2024
1 parent 85d2764 commit 7c734a7
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 2 deletions.
24 changes: 23 additions & 1 deletion core/src/blocks/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ pub struct Error {
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SearchProviderID {
SearchApi,
SerpAPI,
Serper,
}

impl ToString for SearchProviderID {
fn to_string(&self) -> String {
match self {
SearchProviderID::SearchApi => String::from("searchapi"),
SearchProviderID::SerpAPI => String::from("serpapi"),
SearchProviderID::Serper => String::from("serper"),
}
Expand All @@ -39,10 +41,11 @@ impl FromStr for SearchProviderID {

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"searchapi" => Ok(SearchProviderID::SearchApi),
"serpapi" => Ok(SearchProviderID::SerpAPI),
"serper" => Ok(SearchProviderID::Serper),
_ => Err(ParseError::with_message(
"Unknown search provider ID (possible values: serpapi, serper)",
"Unknown search provider ID (possible values: searchapi, serpapi, serper)",
)),
}
}
Expand Down Expand Up @@ -200,6 +203,7 @@ impl Block for Search {
let query = replace_variables_in_string(&self.query, "query", env)?;

let credential_key = match provider_id {
SearchProviderID::SearchApi => "SEARCH_API_KEY",
SearchProviderID::SerpAPI => "SERP_API_KEY",
SearchProviderID::Serper => "SERPER_API_KEY",
};
Expand All @@ -221,6 +225,24 @@ impl Block for Search {
};

let request = match provider_id {
SearchProviderID::SearchApi => {
let url = format!(
"https://www.searchapi.io/api/v1/search?q={}&engine={}&num={}",
encode(&query),
self.engine,
num.unwrap_or(10)
);

let headers = json!({
"Authorization": format!("Bearer {}", provider_api_key),
"Content-Type": "application/json",
"X-SearchApi-Source": "dust"
});

let request = HttpRequest::new("GET", url.as_str(), headers, Value::Null)?;

request
}
SearchProviderID::SerpAPI => {
let url = match num {
None => format!(
Expand Down
2 changes: 1 addition & 1 deletion front/components/app/blocks/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function Search({
});
const serviceProviders = filterServiceProviders(providers);
const searchProviders = serviceProviders?.filter?.(
(p) => p.providerId === "serpapi" || p.providerId === "serper"
(p) => p.providerId === "searchapi" || p.providerId === "serpapi" || p.providerId === "serper"
);

const currentProvider = searchProviders?.find?.(
Expand Down
206 changes: 206 additions & 0 deletions front/components/providers/SearchApiSetup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { Button } from "@dust-tt/sparkle";
import type { WorkspaceType } from "@dust-tt/types";
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useEffect, useState } from "react";
import { useSWRConfig } from "swr";

import { checkProvider } from "@app/lib/providers";

export default function SearchApiSetup({
owner,
open,
setOpen,
config,
enabled,
}: {
owner: WorkspaceType;
open: boolean;
setOpen: (open: boolean) => void;
config: { [key: string]: string };
enabled: boolean;
}) {
const { mutate } = useSWRConfig();

const [apiKey, setApiKey] = useState(config ? config.api_key : "");
const [testSuccessful, setTestSuccessful] = useState(false);
const [testRunning, setTestRunning] = useState(false);
const [testError, setTestError] = useState("");
const [enableRunning, setEnableRunning] = useState(false);

useEffect(() => {
if (config && config.api_key.length > 0 && apiKey.length == 0) {
setApiKey(config.api_key);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config]);

const runTest = async () => {
setTestRunning(true);
setTestError("");
const check = await checkProvider(owner, "searchapi", { api_key: apiKey });

if (!check.ok) {
setTestError(check.error);
setTestSuccessful(false);
setTestRunning(false);
} else {
setTestError("");
setTestSuccessful(true);
setTestRunning(false);
}
};

const handleEnable = async () => {
setEnableRunning(true);
const res = await fetch(`/api/w/${owner.sId}/providers/searchapi`, {
headers: {
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
config: JSON.stringify({
api_key: apiKey,
}),
}),
});
await res.json();
setEnableRunning(false);
setOpen(false);
await mutate(`/api/w/${owner.sId}/providers`);
};

const handleDisable = async () => {
const res = await fetch(`/api/w/${owner.sId}/providers/searchapi`, {
method: "DELETE",
});
await res.json();
setOpen(false);
await mutate(`/api/w/${owner.sId}/providers`);
};

return (
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="relative z-30" onClose={() => setOpen(false)}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-800 bg-opacity-75 transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-30 overflow-y-auto">
<div className="flex min-h-full items-end items-center justify-center p-4">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
leave="ease-in duration-200"
leaveTo="opacity-0"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6 lg:max-w-lg">
<div>
<div className="mt-3">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Setup SearchApi Search
</Dialog.Title>
<div className="mt-4">
<p className="text-sm text-gray-500">
SearchApi lets you search Google, Bing, Baidu,and other search
engines. To use SearchApi you must provide your API key.
It can be found{" "}
<a
className="font-bold text-action-600 hover:text-action-500"
href="https://www.searchapi.io/"
target="_blank"
>
here
</a>
</p>
<p className="mt-2 text-sm text-gray-500">
We'll never use your API key for anything other than to
run your apps.
</p>
</div>
<div className="mt-6">
<input
type="text"
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-action-500 focus:ring-action-500 sm:text-sm"
placeholder="SearchApi API Key"
value={apiKey}
onChange={(e) => {
setApiKey(e.target.value);
setTestSuccessful(false);
}}
/>
</div>
</div>
</div>
<div className="mt-1 px-2 text-sm">
{testError.length > 0 ? (
<span className="text-red-500">Error: {testError}</span>
) : testSuccessful ? (
<span className="text-green-600">
Test succeeded! You can enable SearchApi Search.
</span>
) : (
<span>&nbsp;</span>
)}
</div>
<div className="mt-5 flex flex-row items-center space-x-2 sm:mt-6">
{enabled ? (
<div
className="flex-initial cursor-pointer text-sm font-bold text-red-500"
onClick={() => handleDisable()}
>
Disable
</div>
) : (
<></>
)}
<div className="flex-1"></div>
<div className="flex flex-initial">
<Button
onClick={() => setOpen(false)}
label="Cancel"
variant="secondary"
/>
</div>
<div className="flex flex-initial">
{testSuccessful ? (
<Button
onClick={() => handleEnable()}
disabled={enableRunning}
label={
enabled
? enableRunning
? "Updating..."
: "Update"
: enableRunning
? "Enabling..."
: "Enable"
}
/>
) : (
<Button
disabled={apiKey.length == 0 || testRunning}
onClick={() => runTest()}
label={testRunning ? "Testing..." : "Test"}
/>
)}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}
6 changes: 6 additions & 0 deletions front/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ type ServiceProvider = {
};

export const serviceProviders: ServiceProvider[] = [
{
providerId: "searchapi",
name: "SearchApi",
built: true,
enabled: false,
},
{
providerId: "serpapi",
name: "SerpApi (Google Search)",
Expand Down
20 changes: 20 additions & 0 deletions front/pages/api/w/[wId]/providers/[pId]/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,26 @@ async function handler(
}
return;

case "searchapi":
const testSearch = await fetch(
`https://www.searchapi.io/api/v1/search?engine=google&q=dust.tt`,
{
method: "GET",
headers: {
"Authorization": `Bearer ${config.api_key}`,
"Content-Type": "application/json",
"X-SearchApi-Source": "dust"
},
}
);
if (!testSearch.ok) {
const err = await testSearch.json();
res.status(400).json({ ok: false, error: err.error });
} else {
await testSearch.json();
res.status(200).json({ ok: true });
}
return;
case "serpapi":
const testSearch = await fetch(
`https://serpapi.com/search?engine=google&q=Coffee&api_key=${config.api_key}`,
Expand Down
12 changes: 12 additions & 0 deletions front/pages/w/[wId]/vaults/[vaultId]/apps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import BrowserlessAPISetup from "@app/components/providers/BrowserlessAPISetup";
import GoogleAiStudioSetup from "@app/components/providers/GoogleAiStudioSetup";
import MistralAISetup from "@app/components/providers/MistralAISetup";
import OpenAISetup from "@app/components/providers/OpenAISetup";
import SearchApiSetup from "@app/components/providers/SearchApiSetup";
import SerpAPISetup from "@app/components/providers/SerpAPISetup";
import SerperSetup from "@app/components/providers/SerperSetup";
import AppLayout from "@app/components/sparkle/AppLayout";
Expand Down Expand Up @@ -437,6 +438,7 @@ export function Providers({ owner }: { owner: WorkspaceType }) {
const [anthropicOpen, setAnthropicOpen] = useState(false);
const [mistalAIOpen, setMistralAiOpen] = useState(false);
const [googleAiStudioOpen, setGoogleAiStudioOpen] = useState(false);
const [searchapiOpen, setSearchapiOpen] = useState(false);
const [serpapiOpen, setSerpapiOpen] = useState(false);
const [serperOpen, setSerperOpen] = useState(false);
const [browserlessapiOpen, setBrowserlessapiOpen] = useState(false);
Expand Down Expand Up @@ -514,6 +516,13 @@ export function Providers({ owner }: { owner: WorkspaceType }) {
enabled={!!configs["google_ai_studio"]}
config={configs["google_ai_studio"] ?? null}
/>
<SearchApiSetup
owner={owner}
open={searchapiOpen}
setOpen={setSearchapiOpen}
enabled={!!configs["searchapi"]}
config={configs["searchapi"] ?? null}
/>
<SerpAPISetup
owner={owner}
open={serpapiOpen}
Expand Down Expand Up @@ -657,6 +666,9 @@ export function Providers({ owner }: { owner: WorkspaceType }) {
}
onClick={() => {
switch (provider.providerId) {
case "searchapi":
setSearchapiOpen(true);
break;
case "serpapi":
setSerpapiOpen(true);
break;
Expand Down
3 changes: 3 additions & 0 deletions types/src/front/lib/api/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export const credentialsFromProviders = (
case "textsynth":
credentials["TEXTSYNTH_API_KEY"] = config.api_key;
break;
case "searchapi":
credentials["SEARCHAPI_API_KEY"] = config.api_key;
break;
case "serpapi":
credentials["SERP_API_KEY"] = config.api_key;
break;
Expand Down

0 comments on commit 7c734a7

Please sign in to comment.