Skip to content
Closed
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.

256 changes: 256 additions & 0 deletions src/api-client-v2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import type { AxiosInstance, AxiosResponse } from 'axios';

export type ScraperModeV2 = 'llm-extraction';

export interface ScrapeRequestV2 {
url: string;
pageOptions?: {
screenshot?: boolean;
onlyMainContent?: boolean;
includeHtml?: boolean;
waitFor?: number;
headers?: Record<string, string>;
};
extractorOptions?: {
mode?: ScraperModeV2;
extractionPrompt?: string;
extractionSchema?: Record<string, unknown>;
};
scraperOptions?: {
useUnstructured?: boolean;
};
}

export interface ScrapeResponseV2 {
success?: boolean;
data?: {
content?: string;
markdown?: string;
html?: string;
metadata?: Record<string, unknown>;
screenshotUrl?: string;
};
metadata?: {
sourceURL?: string;
};
}

export interface CrawlRequestV2 {
url: string;
crawlerOptions?: {
includes?: string[];
excludes?: string[];
maxDepth?: number;
maxPages?: number;
returnOnlyUrls?: boolean;
};
pageOptions?: {
onlyMainContent?: boolean;
includeHtml?: boolean;
screenshot?: boolean;
};
webhook?: {
url?: string;
secret?: string;
};
}

export interface CrawlJobResponseV2 {
jobId: string;
}

export interface CrawlStatusResponseV2 {
status?: 'crawling' | 'completed' | 'failed';
total?: number;
progress?: number;
data?: ScrapeResponseV2[];
}

export interface CancelCrawlResponseV2 {
status?: string;
}

export interface SearchRequestV2 {
query: string;
searchOptions?: {
provider?: 'google' | 'serper' | 'searchapi' | 'searxng';
region?: string;
maxResults?: number;
};
}

export interface SearchResultV2 {
title?: string;
url?: string;
description?: string;
content?: string;
}

export interface MapRequestV2 {
url: string;
}

export interface ExtractRequestV2 {
html?: string;
markdown?: string;
extractorOptions: {
extractionSchema: Record<string, unknown>;
extractionPrompt?: string;
mode?: ScraperModeV2;
};
}

export interface ExtractResponseV2 {
success?: boolean;
data?: Record<string, unknown>;
}

/**
* Axios-based client implementing Firecrawl v2 scraping operations.
*/
export class ScrapingApiV2 {
private readonly http: AxiosInstance;

/**
* Create a new scraping client.
*
* @param http - Preconfigured Axios instance.
*/
constructor(http: AxiosInstance) {
this.http = http;
}

/**
* Scrape a single URL using the v2 endpoint.
*
* @param payload - Scrape request payload.
* @returns Axios response containing the scrape result.
*/
public scrape(payload: ScrapeRequestV2): Promise<AxiosResponse<ScrapeResponseV2>> {
return this.http.post<ScrapeResponseV2>('/scrape', payload);
}
}

/**
* Axios-based client implementing Firecrawl v2 crawling operations.
*/
export class CrawlingApiV2 {
private readonly http: AxiosInstance;

/**
* Create a new crawling client.
*
* @param http - Preconfigured Axios instance.
*/
constructor(http: AxiosInstance) {
this.http = http;
}

/**
* Start a crawl job using the v2 API.
*
* @param payload - Crawl configuration payload.
* @returns Axios response containing the created job identifier.
*/
public startCrawl(payload: CrawlRequestV2): Promise<AxiosResponse<CrawlJobResponseV2>> {
return this.http.post<CrawlJobResponseV2>('/crawl', payload);
}

/**
* Retrieve the status of a crawl job.
*
* @param jobId - Crawl job identifier.
* @returns Axios response describing the crawl progress.
*/
public getCrawlStatus(jobId: string): Promise<AxiosResponse<CrawlStatusResponseV2>> {
return this.http.get<CrawlStatusResponseV2>(`/crawl/status/${jobId}`);
}

/**
* Cancel an existing crawl job.
*
* @param jobId - Crawl job identifier.
* @returns Axios response confirming the cancellation.
*/
public cancelCrawl(jobId: string): Promise<AxiosResponse<CancelCrawlResponseV2>> {
return this.http.post<CancelCrawlResponseV2>('/crawl/cancel', { jobId });
}
}

/**
* Axios-based client implementing Firecrawl v2 search operations.
*/
export class SearchApiV2 {
private readonly http: AxiosInstance;

/**
* Create a new search client.
*
* @param http - Preconfigured Axios instance.
*/
constructor(http: AxiosInstance) {
this.http = http;
}

/**
* Execute a search query via the v2 API.
*
* @param payload - Search configuration payload.
* @returns Axios response containing an array of results.
*/
public search(payload: SearchRequestV2): Promise<AxiosResponse<SearchResultV2[]>> {
return this.http.post<SearchResultV2[]>('/search', payload);
}
}

/**
* Axios-based client implementing Firecrawl v2 sitemap retrieval.
*/
export class MappingApiV2 {
private readonly http: AxiosInstance;

/**
* Create a new mapping client.
*
* @param http - Preconfigured Axios instance.
*/
constructor(http: AxiosInstance) {
this.http = http;
}

/**
* Retrieve sitemap data for a given URL.
*
* @param payload - Map request payload.
* @returns Axios response containing the sitemap URLs.
*/
public getSitemap(payload: MapRequestV2): Promise<AxiosResponse<string[]>> {
return this.http.post<string[]>('/map', payload);
}
}

/**
* Axios-based client implementing Firecrawl v2 extraction operations.
*/
export class ExtractionApiV2 {
private readonly http: AxiosInstance;

/**
* Create a new extraction client.
*
* @param http - Preconfigured Axios instance.
*/
constructor(http: AxiosInstance) {
this.http = http;
}

/**
* Extract structured data from raw HTML or Markdown content.
*
* @param payload - Extraction payload matching the v2 schema.
* @returns Axios response containing the extraction result.
*/
public extract(payload: ExtractRequestV2): Promise<AxiosResponse<ExtractResponseV2>> {
return this.http.post<ExtractResponseV2>('/extract', payload);
}
}
48 changes: 30 additions & 18 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">Version 1</option>
<option value="v2">Version 2</option>
</select>
<small>Select the API version matching your backend deployment.</small>
</div>
<div class="form-group">
<label for="apiKey">Firecrawl API Key:</label>
<input
Expand All @@ -18,43 +26,42 @@
</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="placeholderUrl" />
<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 } from 'vue';
import { refreshApiClients } from '@/plugins/api';
import { getDefaultBaseUrl, getApiVersion, type ApiVersion } from '@/config/api';
import { useApiVersion } from '@/stores/apiVersion';

/**
* Component allowing users to configure and store the Firecrawl API key and base URL.
* Component allowing users to configure and store the Firecrawl API key, version and base URL.
* This component handles the persistence of these settings in local storage.
*
* @returns Component options for the API key input view.
*/

export default defineComponent({
name: 'ApiKeyInput',
setup() {
const apiVersionState = useApiVersion();
const selectedVersion = ref<ApiVersion>(apiVersionState.value ?? getApiVersion());
const apiKey = ref(localStorage.getItem('firecrawl_api_key') || '');
const baseUrl = ref(localStorage.getItem('firecrawl_base_url') || '');
const error = ref('');
const success = ref(false);

// Automatically display if no key is configured
const placeholderUrl = computed(() => getDefaultBaseUrl(selectedVersion.value));

onMounted(() => {
if (!apiKey.value) {
error.value = 'Please configure your API key to continue';
Expand All @@ -66,17 +73,19 @@ export default defineComponent({
if (!apiKey.value) {
throw new Error('Please enter a valid API key');
}
// Save the API key

localStorage.setItem('firecrawl_api_key', apiKey.value);
// Save the base URL (even if empty to override any previous value if needed)
localStorage.setItem('firecrawl_base_url', baseUrl.value);
if (baseUrl.value.trim()) {
localStorage.setItem('firecrawl_base_url', baseUrl.value);
} else {
localStorage.removeItem('firecrawl_base_url');
}

success.value = true;
error.value = '';
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 @@ -89,6 +98,8 @@ export default defineComponent({
error,
success,
saveApiConfig,
selectedVersion,
placeholderUrl,
};
},
});
Expand All @@ -113,7 +124,8 @@ label {
font-weight: bold;
}

input {
input,
select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
Expand Down
Loading