Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 154 additions & 155 deletions openapi_v2.yaml

Large diffs are not rendered by default.

188 changes: 188 additions & 0 deletions src/api-client-v2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
import type { Configuration } from '@/api-client/configuration.js';
import type {
CancelCrawlResponseV2,
CrawlRequestV2,
CrawlResponseV2,
CrawlStatusResponseV2,
ExtractRequestV2,
ExtractResponseV2,
MapRequestV2,
MapResponseV2,
ScrapeRequestV2,
ScrapeResponseV2,
SearchRequestV2,
SearchResultV2,
} from '@/types/api.js';

/**
* Build a dedicated Axios instance configured with the provided API configuration.
*
* @param configuration - The shared API configuration.
* @returns An Axios instance ready to communicate with the v2 API.
*/
function createAxiosInstance(configuration: Configuration): AxiosInstance {
const baseUrl = (configuration.basePath || configuration.baseUrl || '').replace(/\/$/, '');
const baseHeaders = configuration.baseOptions?.headers ?? {};
return axios.create({
baseURL: baseUrl,
headers: {
'Content-Type': 'application/json',
...baseHeaders,
},
});
}

/**
* Client for scraping endpoints exposed by the Firecrawl API v2.
*/
export class ScrapingApiV2 {
private readonly http: AxiosInstance;

constructor(configuration: Configuration) {
this.http = createAxiosInstance(configuration);
}

/**
* Trigger a scrape request following the v2 contract.
*
* @param payload - Parameters describing the scraping job.
* @returns Axios promise resolving with the scrape response payload.
*/
public scrapeAndExtractFromUrl(
payload: ScrapeRequestV2,
): Promise<AxiosResponse<ScrapeResponseV2>> {
return this.http.post('/scrape', payload);
}
}

/**
* Client handling crawl operations for the Firecrawl API v2.
*/
export class CrawlingApiV2 {
private readonly http: AxiosInstance;

constructor(configuration: Configuration) {
this.http = createAxiosInstance(configuration);
}

/**
* Start a crawl job and retrieve the job identifier.
*
* @param payload - Crawl parameters matching the v2 specification.
* @returns Axios promise containing the crawl job response.
*/
public crawlUrls(payload: CrawlRequestV2): Promise<AxiosResponse<CrawlResponseV2>> {
return this.http.post('/crawl', payload);
}

/**
* Retrieve the status of a previously started crawl job.
*
* @param jobId - Identifier of the crawl job.
* @returns Axios promise resolving with the crawl status payload.
*/
public getCrawlStatus(jobId: string): Promise<AxiosResponse<CrawlStatusResponseV2>> {
return this.http.get(`/crawl/status/${jobId}`);
}

/**
* Cancel a running crawl job.
*
* @param jobId - Identifier of the crawl job to cancel.
* @returns Axios promise containing the cancellation response.
*/
public cancelCrawl(jobId: string): Promise<AxiosResponse<CancelCrawlResponseV2>> {
return this.http.post('/crawl/cancel', { jobId });
}
}

/**
* Client for data extraction utilities using the Firecrawl API v2.
*/
export class ExtractionApiV2 {
private readonly http: AxiosInstance;

constructor(configuration: Configuration) {
this.http = createAxiosInstance(configuration);
}

/**
* Extract structured data from raw HTML or Markdown content.
*
* @param payload - Extraction parameters compliant with the v2 contract.
* @returns Axios promise resolving with the extraction response payload.
*/
public extractData(payload: ExtractRequestV2): Promise<AxiosResponse<ExtractResponseV2>> {
return this.http.post('/extract', payload);
}
}

/**
* Client for sitemap and mapping utilities in the Firecrawl API v2.
*/
export class MappingApiV2 {
private readonly http: AxiosInstance;

constructor(configuration: Configuration) {
this.http = createAxiosInstance(configuration);
}

/**
* Retrieve sitemap URLs for a given website.
*
* @param payload - Mapping request body containing the URL to inspect.
* @returns Axios promise resolving with a list of discovered URLs.
*/
public mapUrls(payload: MapRequestV2): Promise<AxiosResponse<MapResponseV2>> {
return this.http.post('/map', payload);
}
}

/**
* Client performing search operations with the Firecrawl API v2.
*/
export class SearchApiV2 {
private readonly http: AxiosInstance;

constructor(configuration: Configuration) {
this.http = createAxiosInstance(configuration);
}

/**
* Execute a search query using the selected provider.
*
* @param payload - Search parameters following the v2 schema.
* @returns Axios promise resolving with the search results array.
*/
public search(payload: SearchRequestV2): Promise<AxiosResponse<SearchResultV2[]>> {
return this.http.post('/search', payload);
}
}

/**
* Collection of v2 API clients used throughout the UI.
*/
export interface FirecrawlApiClientsV2 {
crawling: CrawlingApiV2;
extraction: ExtractionApiV2;
mapping: MappingApiV2;
scraping: ScrapingApiV2;
search: SearchApiV2;
}

/**
* Factory creating a bundle of v2 clients using a shared configuration instance.
*
* @param configuration - Shared API configuration containing authentication and base URL.
* @returns Instantiated client bundle ready for dependency injection.
*/
export function createApiClientsV2(configuration: Configuration): FirecrawlApiClientsV2 {
return {
crawling: new CrawlingApiV2(configuration),
extraction: new ExtractionApiV2(configuration),
mapping: new MappingApiV2(configuration),
scraping: new ScrapingApiV2(configuration),
search: new SearchApiV2(configuration),
};
}
62 changes: 51 additions & 11 deletions src/components/ApiKeyInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
<div class="api-key-input">
<h2>API Configuration</h2>
<form @submit.prevent="saveApiConfig">
<div class="form-group">
<label for="apiVersion">Firecrawl API version:</label>
<select id="apiVersion" v-model="selectedVersion">
<option value="v1">v1</option>
<option value="v2">v2</option>
</select>
<small>Select the API version exposed by your Firecrawl deployment.</small>
</div>
<div class="form-group">
<label for="apiKey">Firecrawl API Key:</label>
<input
Expand All @@ -18,26 +26,24 @@
</div>
<div class="form-group">
<label for="baseUrl">Firecrawl API base URL (optional):</label>
<input
id="baseUrl"
v-model="baseUrl"
type="text"
placeholder="https://api.firecrawl.dev/v1"
/>
<small> Leave blank to use the default URL. </small>
<input id="baseUrl" v-model="baseUrl" type="text" :placeholder="baseUrlPlaceholder" />
<small> Leave blank to use the default URL for the selected version. </small>
</div>
<button type="submit" class="primary-button">Save</button>
</form>
<div v-if="error" class="error-message">
{{ error }}
</div>
<div v-if="success" class="success-message">API Key saved successfully!</div>
<div v-if="success" class="success-message">API configuration saved successfully!</div>
</div>
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
import { defineComponent, ref, onMounted, computed, watch } from 'vue';
import { refreshApiClients } from '@/plugins/api';
import { getCurrentBaseUrl, getDefaultBaseUrl } from '@/config/api.js';
import type { ApiVersion } from '@/types/api.js';
import { useApiVersion } from '@/stores/apiVersion.js';

/**
* Component allowing users to configure and store the Firecrawl API key and base URL.
Expand All @@ -49,13 +55,45 @@ import { refreshApiClients } from '@/plugins/api';
export default defineComponent({
name: 'ApiKeyInput',
setup() {
const { apiVersion, setApiVersion } = useApiVersion();
const selectedVersion = ref<ApiVersion>(apiVersion.value);
const apiKey = ref(localStorage.getItem('firecrawl_api_key') || '');
const baseUrl = ref(localStorage.getItem('firecrawl_base_url') || '');
const storedBaseUrl = localStorage.getItem('firecrawl_base_url');
const baseUrl = ref(
storedBaseUrl && storedBaseUrl.trim().length > 0 ? storedBaseUrl : getCurrentBaseUrl(),
);
const error = ref('');
const success = ref(false);

const baseUrlPlaceholder = computed(() => getDefaultBaseUrl(selectedVersion.value));

watch(
apiVersion,
(newVersion) => {
if (selectedVersion.value !== newVersion) {
selectedVersion.value = newVersion;
}
},
{ immediate: false },
);

watch(
selectedVersion,
(newVersion, oldVersion) => {
setApiVersion(newVersion);
const previousDefault = oldVersion ? getDefaultBaseUrl(oldVersion) : null;
if (!baseUrl.value || (previousDefault && baseUrl.value === previousDefault)) {
baseUrl.value = getDefaultBaseUrl(newVersion);
}
},
{ immediate: false },
);

// Automatically display if no key is configured
onMounted(() => {
if (!baseUrl.value) {
baseUrl.value = getDefaultBaseUrl(selectedVersion.value);
}
if (!apiKey.value) {
error.value = 'Please configure your API key to continue';
}
Expand All @@ -76,7 +114,7 @@ export default defineComponent({
setTimeout(() => (success.value = false), 3000);

// Update API clients dynamically without reloading the page
refreshApiClients(baseUrl.value, apiKey.value);
refreshApiClients(baseUrl.value, apiKey.value, selectedVersion.value);
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
success.value = false;
Expand All @@ -86,8 +124,10 @@ export default defineComponent({
return {
apiKey,
baseUrl,
baseUrlPlaceholder,
error,
success,
selectedVersion,
saveApiConfig,
};
},
Expand Down
Loading