mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
Merge remote-tracking branch 'upstream/ci_mvp' into feat/ci-ui-changes
This commit is contained in:
commit
fd8d1273cd
188 changed files with 2163 additions and 3644 deletions
|
|
@ -13,6 +13,27 @@ import type { Announcement } from "@/contracts/types/announcement.types";
|
|||
* This file can be replaced with an API call in the future.
|
||||
*/
|
||||
export const announcements: Announcement[] = [
|
||||
{
|
||||
id: "2026-07-05-competitive-intelligence-direction",
|
||||
title: "SurfSense's Next Chapter: Competitive Intelligence for AI Agents",
|
||||
description:
|
||||
"Happy Independence Day to everyone celebrating in the United States! We picked this week to share some big news: SurfSense is now the open-source competitive intelligence agent platform. Your agents monitor competitors, track rankings, and listen to your market with live data from Reddit, YouTube, Google Maps, Google Search, and the open web, through one REST API or MCP server. Everything you rely on today keeps working, and self-hosting stays free.",
|
||||
category: "update",
|
||||
date: "2026-07-05T00:00:00Z",
|
||||
startTime: "2026-07-05T00:00:00Z",
|
||||
endTime: "2026-08-31T00:00:00Z",
|
||||
audience: "all",
|
||||
isImportant: true,
|
||||
spotlight: true,
|
||||
image: {
|
||||
src: "/announcements/competitive-intelligence.png",
|
||||
alt: "Platform data tiles for social, video, maps, search, and the web flowing into a central AI core that outputs market charts and alerts.",
|
||||
},
|
||||
link: {
|
||||
label: "Read the full announcement",
|
||||
url: "/changelog",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2026-05-31-ai-automations",
|
||||
title: "Introducing AI Automations",
|
||||
|
|
@ -23,8 +44,7 @@ export const announcements: Announcement[] = [
|
|||
startTime: "2026-05-31T00:00:00Z",
|
||||
endTime: "2026-07-15T00:00:00Z",
|
||||
audience: "users",
|
||||
isImportant: true,
|
||||
spotlight: true,
|
||||
isImportant: false,
|
||||
image: {
|
||||
src: "/announcements/automations.png",
|
||||
alt: "Connector tiles flowing into a central AI core that triggers scheduled and event-driven automations.",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const AgentActionReadSchema = z.object({
|
|||
id: z.number(),
|
||||
thread_id: z.number(),
|
||||
user_id: z.string().nullable(),
|
||||
search_space_id: z.number(),
|
||||
workspace_id: z.number(),
|
||||
tool_name: z.string(),
|
||||
args: z.record(z.string(), z.unknown()).nullable(),
|
||||
result_id: z.string().nullable(),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export type AgentPermissionAction = z.infer<typeof ActionEnum>;
|
|||
|
||||
const AgentPermissionRuleSchema = z.object({
|
||||
id: z.number(),
|
||||
search_space_id: z.number(),
|
||||
workspace_id: z.number(),
|
||||
user_id: z.string().nullable(),
|
||||
thread_id: z.number().nullable(),
|
||||
permission: z.string(),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class AutomationsApiService {
|
|||
|
||||
listAutomations = async (params: AutomationListParams) => {
|
||||
const qs = new URLSearchParams({
|
||||
workspace_id: String(params.search_space_id),
|
||||
workspace_id: String(params.workspace_id),
|
||||
limit: String(params.limit),
|
||||
offset: String(params.offset),
|
||||
});
|
||||
|
|
@ -50,9 +50,9 @@ class AutomationsApiService {
|
|||
|
||||
createAutomation = async (request: AutomationCreateRequest) => {
|
||||
const data = rejectIfInvalid(automationCreateRequest.safeParse(request));
|
||||
const { search_space_id, ...body } = data;
|
||||
const { workspace_id, ...body } = data;
|
||||
return baseApiService.post(BASE, automation, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ class AutomationsApiService {
|
|||
return baseApiService.delete(`${BASE}/${automationId}`);
|
||||
};
|
||||
|
||||
// Whether the search space's models are billable for automations (premium
|
||||
// Whether the workspace's models are billable for automations (premium
|
||||
// global or BYOK). Used to gate creation surfaces before submit.
|
||||
getModelEligibility = async (searchSpaceId: number) => {
|
||||
const qs = new URLSearchParams({ workspace_id: String(searchSpaceId) });
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ class ChatCommentsApiService {
|
|||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (parsed.data.search_space_id !== undefined) {
|
||||
params.set("workspace_id", String(parsed.data.search_space_id));
|
||||
if (parsed.data.workspace_id !== undefined) {
|
||||
params.set("workspace_id", String(parsed.data.workspace_id));
|
||||
}
|
||||
|
||||
const queryString = params.toString();
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class ChatThreadsApiService {
|
|||
};
|
||||
|
||||
/**
|
||||
* List all public chat snapshots for a search space.
|
||||
* List all public chat snapshots for a workspace.
|
||||
*/
|
||||
listPublicChatSnapshotsForSearchSpace = async (
|
||||
request: PublicChatSnapshotsBySpaceRequest
|
||||
|
|
@ -86,7 +86,7 @@ class ChatThreadsApiService {
|
|||
}
|
||||
|
||||
return baseApiService.get(
|
||||
`/api/v1/workspaces/${parsed.data.search_space_id}/snapshots`,
|
||||
`/api/v1/workspaces/${parsed.data.workspace_id}/snapshots`,
|
||||
publicChatSnapshotsBySpaceResponse
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { baseApiService } from "./base-api.service";
|
|||
|
||||
class ConnectorsApiService {
|
||||
/**
|
||||
* Get all connectors for a search space
|
||||
* Get all connectors for a workspace
|
||||
*/
|
||||
getConnectors = async (request: GetConnectorsRequest) => {
|
||||
const parsedRequest = getConnectorsRequest.safeParse(request);
|
||||
|
|
@ -59,7 +59,7 @@ class ConnectorsApiService {
|
|||
Object.entries(parsedRequest.data.queryParams)
|
||||
.filter(([_, v]) => v !== undefined && v !== null)
|
||||
.map(([k, v]) => {
|
||||
return [k === "search_space_id" ? "workspace_id" : k, String(v)];
|
||||
return [k, String(v)];
|
||||
})
|
||||
)
|
||||
: undefined;
|
||||
|
|
@ -113,7 +113,7 @@ class ConnectorsApiService {
|
|||
Object.entries(queryParams)
|
||||
.filter(([_, v]) => v !== undefined && v !== null)
|
||||
.map(([k, v]) => {
|
||||
return [k === "search_space_id" ? "workspace_id" : k, String(v)];
|
||||
return [k, String(v)];
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ class ConnectorsApiService {
|
|||
Object.entries(queryParams)
|
||||
.filter(([_, v]) => v !== undefined && v !== null)
|
||||
.map(([k, v]) => {
|
||||
return [k === "search_space_id" ? "workspace_id" : k, String(v)];
|
||||
return [k, String(v)];
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -308,13 +308,13 @@ class ConnectorsApiService {
|
|||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Get all MCP connectors for a search space
|
||||
* Get all MCP connectors for a workspace
|
||||
*/
|
||||
getMCPConnectors = async (request: GetMCPConnectorsRequest) => {
|
||||
const { search_space_id } = request.queryParams;
|
||||
const { workspace_id } = request.queryParams;
|
||||
|
||||
const queryString = new URLSearchParams({
|
||||
workspace_id: String(search_space_id),
|
||||
workspace_id: String(workspace_id),
|
||||
}).toString();
|
||||
|
||||
return baseApiService.get<MCPConnectorRead[]>(`/api/v1/connectors/mcp?${queryString}`);
|
||||
|
|
@ -334,7 +334,7 @@ class ConnectorsApiService {
|
|||
const { data, queryParams } = request;
|
||||
|
||||
const queryString = new URLSearchParams({
|
||||
workspace_id: String(queryParams.search_space_id),
|
||||
workspace_id: String(queryParams.workspace_id),
|
||||
}).toString();
|
||||
|
||||
return baseApiService.post<MCPConnectorRead>(
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class DocumentsApiService {
|
|||
const transformedQueryParams = parsedRequest.data.queryParams
|
||||
? Object.fromEntries(
|
||||
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => {
|
||||
const key = k === "search_space_id" ? "workspace_id" : k;
|
||||
const key = k;
|
||||
// Handle array values (document_type)
|
||||
if (Array.isArray(v)) {
|
||||
return [key, v.join(",")];
|
||||
|
|
@ -107,9 +107,9 @@ class DocumentsApiService {
|
|||
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const { search_space_id, ...body } = parsedRequest.data;
|
||||
const { workspace_id, ...body } = parsedRequest.data;
|
||||
return baseApiService.post(`/api/v1/documents`, createDocumentResponse, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ class DocumentsApiService {
|
|||
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const { files, search_space_id, use_vision_llm, processing_mode } = parsedRequest.data;
|
||||
const { files, workspace_id, use_vision_llm, processing_mode } = parsedRequest.data;
|
||||
const UPLOAD_BATCH_SIZE = 5;
|
||||
|
||||
const batches: File[][] = [];
|
||||
|
|
@ -145,7 +145,7 @@ class DocumentsApiService {
|
|||
for (const batch of batches) {
|
||||
const formData = new FormData();
|
||||
for (const file of batch) formData.append("files", file);
|
||||
formData.append("workspace_id", String(search_space_id));
|
||||
formData.append("workspace_id", String(workspace_id));
|
||||
formData.append("use_vision_llm", String(use_vision_llm));
|
||||
formData.append("processing_mode", processing_mode);
|
||||
|
||||
|
|
@ -191,9 +191,9 @@ class DocumentsApiService {
|
|||
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const { search_space_id, document_ids } = parsedRequest.data.queryParams;
|
||||
const { workspace_id, document_ids } = parsedRequest.data.queryParams;
|
||||
const params = new URLSearchParams({
|
||||
workspace_id: String(search_space_id),
|
||||
workspace_id: String(workspace_id),
|
||||
document_ids: document_ids.join(","),
|
||||
});
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ class DocumentsApiService {
|
|||
const transformedQueryParams = parsedRequest.data.queryParams
|
||||
? Object.fromEntries(
|
||||
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => {
|
||||
const key = k === "search_space_id" ? "workspace_id" : k;
|
||||
const key = k;
|
||||
// Handle array values (document_type)
|
||||
if (Array.isArray(v)) {
|
||||
return [key, v.join(",")];
|
||||
|
|
@ -258,7 +258,7 @@ class DocumentsApiService {
|
|||
Object.entries(parsedRequest.data.queryParams)
|
||||
.filter(([, v]) => v !== undefined)
|
||||
.map(([k, v]) => [
|
||||
k === "search_space_id" || k === "workspace_id" ? "workspace_id" : k,
|
||||
k,
|
||||
String(v),
|
||||
])
|
||||
);
|
||||
|
|
@ -272,9 +272,9 @@ class DocumentsApiService {
|
|||
);
|
||||
};
|
||||
|
||||
getDocumentByVirtualPath = async (request: { search_space_id: number; virtual_path: string }) => {
|
||||
getDocumentByVirtualPath = async (request: { workspace_id: number; virtual_path: string }) => {
|
||||
const params = new URLSearchParams({
|
||||
workspace_id: String(request.search_space_id),
|
||||
workspace_id: String(request.workspace_id),
|
||||
virtual_path: request.virtual_path,
|
||||
});
|
||||
return baseApiService.get(
|
||||
|
|
@ -302,7 +302,7 @@ class DocumentsApiService {
|
|||
const transformedQueryParams = parsedRequest.data.queryParams
|
||||
? Object.fromEntries(
|
||||
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [
|
||||
k === "search_space_id" ? "workspace_id" : k,
|
||||
k,
|
||||
String(v),
|
||||
])
|
||||
)
|
||||
|
|
@ -384,10 +384,10 @@ class DocumentsApiService {
|
|||
}
|
||||
|
||||
const { id, data } = parsedRequest.data;
|
||||
const { search_space_id, ...body } = data;
|
||||
const { workspace_id, ...body } = data;
|
||||
|
||||
return baseApiService.put(`/api/v1/documents/${id}`, updateDocumentResponse, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -413,12 +413,12 @@ class DocumentsApiService {
|
|||
|
||||
folderMtimeCheck = async (body: {
|
||||
folder_name: string;
|
||||
search_space_id: number;
|
||||
workspace_id: number;
|
||||
files: { relative_path: string; mtime: number }[];
|
||||
}): Promise<{ files_to_upload: string[] }> => {
|
||||
const { search_space_id, ...rest } = body;
|
||||
const { workspace_id, ...rest } = body;
|
||||
return baseApiService.post(`/api/v1/documents/folder-mtime-check`, undefined, {
|
||||
body: { ...rest, workspace_id: search_space_id },
|
||||
body: { ...rest, workspace_id },
|
||||
}) as unknown as { files_to_upload: string[] };
|
||||
};
|
||||
|
||||
|
|
@ -426,7 +426,7 @@ class DocumentsApiService {
|
|||
files: File[],
|
||||
metadata: {
|
||||
folder_name: string;
|
||||
search_space_id: number;
|
||||
workspace_id: number;
|
||||
relative_paths: string[];
|
||||
root_folder_id?: number | null;
|
||||
use_vision_llm?: boolean;
|
||||
|
|
@ -439,7 +439,7 @@ class DocumentsApiService {
|
|||
formData.append("files", file);
|
||||
}
|
||||
formData.append("folder_name", metadata.folder_name);
|
||||
formData.append("workspace_id", String(metadata.search_space_id));
|
||||
formData.append("workspace_id", String(metadata.workspace_id));
|
||||
formData.append("relative_paths", JSON.stringify(metadata.relative_paths));
|
||||
if (metadata.root_folder_id != null) {
|
||||
formData.append("root_folder_id", String(metadata.root_folder_id));
|
||||
|
|
@ -469,25 +469,25 @@ class DocumentsApiService {
|
|||
|
||||
folderNotifyUnlinked = async (body: {
|
||||
folder_name: string;
|
||||
search_space_id: number;
|
||||
workspace_id: number;
|
||||
root_folder_id: number | null;
|
||||
relative_paths: string[];
|
||||
}): Promise<{ deleted_count: number }> => {
|
||||
const { search_space_id, ...rest } = body;
|
||||
const { workspace_id, ...rest } = body;
|
||||
return baseApiService.post(`/api/v1/documents/folder-unlink`, undefined, {
|
||||
body: { ...rest, workspace_id: search_space_id },
|
||||
body: { ...rest, workspace_id },
|
||||
}) as unknown as { deleted_count: number };
|
||||
};
|
||||
|
||||
folderSyncFinalize = async (body: {
|
||||
folder_name: string;
|
||||
search_space_id: number;
|
||||
workspace_id: number;
|
||||
root_folder_id: number | null;
|
||||
all_relative_paths: string[];
|
||||
}): Promise<{ deleted_count: number }> => {
|
||||
const { search_space_id, ...rest } = body;
|
||||
const { workspace_id, ...rest } = body;
|
||||
return baseApiService.post(`/api/v1/documents/folder-sync-finalize`, undefined, {
|
||||
body: { ...rest, workspace_id: search_space_id },
|
||||
body: { ...rest, workspace_id },
|
||||
}) as unknown as { deleted_count: number };
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ class FoldersApiService {
|
|||
`Invalid request: ${parsed.error.issues.map((i) => i.message).join(", ")}`
|
||||
);
|
||||
}
|
||||
const { search_space_id, ...body } = parsed.data;
|
||||
const { workspace_id, ...body } = parsed.data;
|
||||
return baseApiService.post("/api/v1/folders", folder, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class InvitesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.post(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/invites`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/invites`,
|
||||
createInviteResponse,
|
||||
{
|
||||
body: parsedRequest.data.data,
|
||||
|
|
@ -45,7 +45,7 @@ class InvitesApiService {
|
|||
};
|
||||
|
||||
/**
|
||||
* Get all invites for a search space
|
||||
* Get all invites for a workspace
|
||||
*/
|
||||
getInvites = async (request: GetInvitesRequest) => {
|
||||
const parsedRequest = getInvitesRequest.safeParse(request);
|
||||
|
|
@ -58,7 +58,7 @@ class InvitesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.get(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/invites`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/invites`,
|
||||
getInvitesResponse
|
||||
);
|
||||
};
|
||||
|
|
@ -77,7 +77,7 @@ class InvitesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.put(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/invites/${parsedRequest.data.invite_id}`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/invites/${parsedRequest.data.invite_id}`,
|
||||
updateInviteResponse,
|
||||
{
|
||||
body: parsedRequest.data.data,
|
||||
|
|
@ -99,7 +99,7 @@ class InvitesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.delete(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/invites/${parsedRequest.data.invite_id}`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/invites/${parsedRequest.data.invite_id}`,
|
||||
deleteInviteResponse
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class LogsApiService {
|
|||
const transformedQueryParams = parsedRequest.data.queryParams
|
||||
? Object.fromEntries(
|
||||
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => {
|
||||
const key = k === "search_space_id" ? "workspace_id" : k;
|
||||
const key = k;
|
||||
// Handle array values (document_type)
|
||||
if (Array.isArray(v)) {
|
||||
return [key, v.join(",")];
|
||||
|
|
@ -75,9 +75,9 @@ class LogsApiService {
|
|||
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
||||
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
||||
}
|
||||
const { search_space_id, ...body } = parsedRequest.data;
|
||||
const { workspace_id, ...body } = parsedRequest.data;
|
||||
return baseApiService.post(`/api/v1/logs`, createLogResponse, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -91,9 +91,9 @@ class LogsApiService {
|
|||
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
||||
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
||||
}
|
||||
const { search_space_id, ...body } = parsedRequest.data;
|
||||
const { workspace_id, ...body } = parsedRequest.data;
|
||||
return baseApiService.put(`/api/v1/logs/${logId}`, updateLogResponse, {
|
||||
body: search_space_id === undefined ? body : { ...body, workspace_id: search_space_id },
|
||||
body: workspace_id === undefined ? body : { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ class LogsApiService {
|
|||
};
|
||||
|
||||
/**
|
||||
* Get summary for logs by search space
|
||||
* Get summary for logs by workspace
|
||||
*/
|
||||
getLogSummary = async (request: GetLogSummaryRequest) => {
|
||||
const parsedRequest = getLogSummaryRequest.safeParse(request);
|
||||
|
|
@ -120,8 +120,8 @@ class LogsApiService {
|
|||
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
||||
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
||||
}
|
||||
const { search_space_id, hours } = parsedRequest.data;
|
||||
const url = `/api/v1/logs/workspaces/${search_space_id}/summary${hours ? `?hours=${hours}` : ""}`;
|
||||
const { workspace_id, hours } = parsedRequest.data;
|
||||
const url = `/api/v1/logs/workspaces/${workspace_id}/summary${hours ? `?hours=${hours}` : ""}`;
|
||||
return baseApiService.get(url, getLogSummaryResponse);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { baseApiService } from "./base-api.service";
|
|||
|
||||
class MembersApiService {
|
||||
/**
|
||||
* Get members of a search space
|
||||
* Get members of a workspace
|
||||
*/
|
||||
getMembers = async (request: GetMembersRequest) => {
|
||||
const parsedRequest = getMembersRequest.safeParse(request);
|
||||
|
|
@ -33,7 +33,7 @@ class MembersApiService {
|
|||
}
|
||||
|
||||
return baseApiService.get(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/members`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/members`,
|
||||
getMembersResponse
|
||||
);
|
||||
};
|
||||
|
|
@ -52,7 +52,7 @@ class MembersApiService {
|
|||
}
|
||||
|
||||
return baseApiService.put(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/members/${parsedRequest.data.membership_id}`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/members/${parsedRequest.data.membership_id}`,
|
||||
updateMembershipResponse,
|
||||
{
|
||||
body: parsedRequest.data.data,
|
||||
|
|
@ -61,7 +61,7 @@ class MembersApiService {
|
|||
};
|
||||
|
||||
/**
|
||||
* Delete a member from search space
|
||||
* Delete a member from workspace
|
||||
*/
|
||||
deleteMember = async (request: DeleteMembershipRequest) => {
|
||||
const parsedRequest = deleteMembershipRequest.safeParse(request);
|
||||
|
|
@ -74,13 +74,13 @@ class MembersApiService {
|
|||
}
|
||||
|
||||
return baseApiService.delete(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/members/${parsedRequest.data.membership_id}`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/members/${parsedRequest.data.membership_id}`,
|
||||
deleteMembershipResponse
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leave a search space (remove self)
|
||||
* Leave a workspace (remove self)
|
||||
*/
|
||||
leaveSearchSpace = async (request: LeaveSearchSpaceRequest) => {
|
||||
const parsedRequest = leaveSearchSpaceRequest.safeParse(request);
|
||||
|
|
@ -93,13 +93,13 @@ class MembersApiService {
|
|||
}
|
||||
|
||||
return baseApiService.delete(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/members/me`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/members/me`,
|
||||
leaveSearchSpaceResponse
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current user's access information for a search space
|
||||
* Get current user's access information for a workspace
|
||||
*/
|
||||
getMyAccess = async (request: GetMyAccessRequest) => {
|
||||
const parsedRequest = getMyAccessRequest.safeParse(request);
|
||||
|
|
@ -112,7 +112,7 @@ class MembersApiService {
|
|||
}
|
||||
|
||||
return baseApiService.get(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/my-access`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/my-access`,
|
||||
getMyAccessResponse
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,15 +56,15 @@ class ModelConnectionsApiService {
|
|||
if (!parsed.success) {
|
||||
throw new ValidationError(parsed.error.issues.map((issue) => issue.message).join(", "));
|
||||
}
|
||||
const { search_space_id, ...body } = parsed.data;
|
||||
const { workspace_id, ...body } = parsed.data;
|
||||
if (
|
||||
body.scope === "SEARCH_SPACE" &&
|
||||
(!Number.isFinite(search_space_id) || (search_space_id ?? 0) <= 0)
|
||||
(!Number.isFinite(workspace_id) || (workspace_id ?? 0) <= 0)
|
||||
) {
|
||||
throw new ValidationError("workspace_id is required");
|
||||
}
|
||||
return baseApiService.post(`/api/v1/model-connections`, connectionRead, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -98,10 +98,10 @@ class ModelConnectionsApiService {
|
|||
if (!parsed.success) {
|
||||
throw new ValidationError(parsed.error.issues.map((issue) => issue.message).join(", "));
|
||||
}
|
||||
const { search_space_id, ...body } = parsed.data;
|
||||
const { workspace_id, ...body } = parsed.data;
|
||||
if (
|
||||
body.scope === "SEARCH_SPACE" &&
|
||||
(!Number.isFinite(search_space_id) || (search_space_id ?? 0) <= 0)
|
||||
(!Number.isFinite(workspace_id) || (workspace_id ?? 0) <= 0)
|
||||
) {
|
||||
throw new ValidationError("workspace_id is required");
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ class ModelConnectionsApiService {
|
|||
`/api/v1/model-connections/discover-preview`,
|
||||
modelPreviewListResponse,
|
||||
{
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
@ -121,15 +121,15 @@ class ModelConnectionsApiService {
|
|||
if (!parsed.success) {
|
||||
throw new ValidationError(parsed.error.issues.map((issue) => issue.message).join(", "));
|
||||
}
|
||||
const { search_space_id, ...body } = parsed.data;
|
||||
const { workspace_id, ...body } = parsed.data;
|
||||
if (
|
||||
body.scope === "SEARCH_SPACE" &&
|
||||
(!Number.isFinite(search_space_id) || (search_space_id ?? 0) <= 0)
|
||||
(!Number.isFinite(workspace_id) || (workspace_id ?? 0) <= 0)
|
||||
) {
|
||||
throw new ValidationError("workspace_id is required");
|
||||
}
|
||||
return baseApiService.post(`/api/v1/model-connections/test-preview`, verifyConnectionResponse, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ class NotificationsApiService {
|
|||
// Build query string from params
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (queryParams.search_space_id !== undefined) {
|
||||
params.append("workspace_id", String(queryParams.search_space_id));
|
||||
if (queryParams.workspace_id !== undefined) {
|
||||
params.append("workspace_id", String(queryParams.workspace_id));
|
||||
}
|
||||
if (queryParams.type) {
|
||||
params.append("type", queryParams.type);
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ class PromptsApiService {
|
|||
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const { search_space_id, ...body } = parsed.data;
|
||||
const { workspace_id, ...body } = parsed.data;
|
||||
return baseApiService.post("/api/v1/prompts", promptRead, {
|
||||
body: { ...body, workspace_id: search_space_id },
|
||||
body: { ...body, workspace_id },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class RolesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.post(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/roles`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/roles`,
|
||||
createRoleResponse,
|
||||
{
|
||||
body: parsedRequest.data.data,
|
||||
|
|
@ -49,7 +49,7 @@ class RolesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.get(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/roles`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/roles`,
|
||||
getRolesResponse
|
||||
);
|
||||
};
|
||||
|
|
@ -65,7 +65,7 @@ class RolesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.get(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/roles/${parsedRequest.data.role_id}`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/roles/${parsedRequest.data.role_id}`,
|
||||
getRoleByIdResponse
|
||||
);
|
||||
};
|
||||
|
|
@ -81,7 +81,7 @@ class RolesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.put(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/roles/${parsedRequest.data.role_id}`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/roles/${parsedRequest.data.role_id}`,
|
||||
updateRoleResponse,
|
||||
{
|
||||
body: parsedRequest.data.data,
|
||||
|
|
@ -100,7 +100,7 @@ class RolesApiService {
|
|||
}
|
||||
|
||||
return baseApiService.delete(
|
||||
`/api/v1/workspaces/${parsedRequest.data.search_space_id}/roles/${parsedRequest.data.role_id}`,
|
||||
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/roles/${parsedRequest.data.role_id}`,
|
||||
deleteRoleResponse
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ class StripeApiService {
|
|||
createCreditCheckoutSession = async (
|
||||
request: CreateCreditCheckoutSessionRequest
|
||||
): Promise<CreateCreditCheckoutSessionResponse> => {
|
||||
const { search_space_id, ...body } = request;
|
||||
const { workspace_id, ...body } = request;
|
||||
return baseApiService.post(
|
||||
"/api/v1/stripe/create-credit-checkout-session",
|
||||
createCreditCheckoutSessionResponse,
|
||||
{ body: { ...body, workspace_id: search_space_id } }
|
||||
{ body: { ...body, workspace_id } }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -75,11 +75,11 @@ class StripeApiService {
|
|||
createAutoReloadSetupSession = async (
|
||||
request: CreateAutoReloadSetupSessionRequest
|
||||
): Promise<CreateAutoReloadSetupSessionResponse> => {
|
||||
const { search_space_id } = request;
|
||||
const { workspace_id } = request;
|
||||
return baseApiService.post(
|
||||
"/api/v1/stripe/auto-reload/setup",
|
||||
createAutoReloadSetupSessionResponse,
|
||||
{ body: { workspace_id: search_space_id } }
|
||||
{ body: { workspace_id } }
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
updateSearchSpaceApiAccessResponse,
|
||||
updateSearchSpaceRequest,
|
||||
updateSearchSpaceResponse,
|
||||
} from "@/contracts/types/search-space.types";
|
||||
} from "@/contracts/types/workspace.types";
|
||||
import { ValidationError } from "../error";
|
||||
import { baseApiService } from "./base-api.service";
|
||||
|
||||
|
|
@ -139,6 +139,17 @@ class SearchSpacesApiService {
|
|||
return baseApiService.delete(`/api/v1/workspaces/${request.id}`, deleteSearchSpaceResponse);
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger AI file sorting for all documents in a search space
|
||||
*/
|
||||
triggerAiSort = async (searchSpaceId: number) => {
|
||||
return baseApiService.post(
|
||||
`/api/v1/workspaces/${searchSpaceId}/ai-sort`,
|
||||
z.object({ message: z.string() }),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leave a search space (remove own membership)
|
||||
* This is used by non-owners to leave a shared search space
|
||||
|
|
@ -271,7 +271,7 @@ export function buildCreatePayload(
|
|||
): AutomationCreateRequest {
|
||||
const trigger = buildScheduleTrigger(form);
|
||||
return {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
name: form.name.trim(),
|
||||
description: form.description?.trim() ? form.description.trim() : null,
|
||||
definition: buildDefinition(form),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export interface ThreadRecord {
|
|||
archived: boolean;
|
||||
visibility: ChatVisibility;
|
||||
created_by_id: string | null;
|
||||
search_space_id: number;
|
||||
workspace_id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
has_comments?: boolean;
|
||||
|
|
|
|||
|
|
@ -148,6 +148,135 @@ export const googleMaps: ConnectorPageContent = {
|
|||
},
|
||||
},
|
||||
|
||||
schema: {
|
||||
requestNote:
|
||||
"Provide at least one source: search_queries, urls, or place_ids. Up to 20 sources per call.",
|
||||
request: [
|
||||
{
|
||||
name: "search_queries",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"Google Maps search terms, e.g. 'coffee shops', 'dentist'. Each returns up to max_places. Pair with location to scope the search. Max 20.",
|
||||
},
|
||||
{
|
||||
name: "urls",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"Google Maps URLs: a place page (/maps/place/...) or a search results URL. Max 20.",
|
||||
},
|
||||
{
|
||||
name: "place_ids",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description: "Known Google place IDs (ChIJ...) to fetch directly. Max 20.",
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
type: "string",
|
||||
description: "Location to scope search_queries to, e.g. 'New York, USA'.",
|
||||
},
|
||||
{
|
||||
name: "max_places",
|
||||
type: "integer",
|
||||
defaultValue: "10",
|
||||
description: "Max places to return per search query. 1 to 1,000.",
|
||||
},
|
||||
{
|
||||
name: "language",
|
||||
type: "string",
|
||||
defaultValue: '"en"',
|
||||
description: "Result language code, e.g. 'en', 'fr'.",
|
||||
},
|
||||
{
|
||||
name: "include_details",
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
description:
|
||||
"Also fetch each place's detail page: opening hours, popular times, and extra contact info. Slower.",
|
||||
},
|
||||
{
|
||||
name: "max_reviews",
|
||||
type: "integer",
|
||||
defaultValue: "0",
|
||||
description: "Reviews to attach per place, up to 100,000. 0 disables reviews.",
|
||||
},
|
||||
{
|
||||
name: "max_images",
|
||||
type: "integer",
|
||||
defaultValue: "0",
|
||||
description: "Images to attach per place. 0 disables images.",
|
||||
},
|
||||
],
|
||||
responseNote:
|
||||
"The response is { items: [...] } with one item per place. One returned place is one billable unit; attached reviews are metered separately.",
|
||||
response: [
|
||||
{
|
||||
name: "title / categoryName / categories",
|
||||
type: "string / string[]",
|
||||
description: "Business name and its Google Maps categories.",
|
||||
},
|
||||
{
|
||||
name: "placeId / cid / url",
|
||||
type: "string",
|
||||
description: "Stable Google identifiers and the place's Maps URL.",
|
||||
},
|
||||
{
|
||||
name: "address / street / city / state / postalCode / countryCode",
|
||||
type: "string",
|
||||
description: "Full and structured address components.",
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
type: "object",
|
||||
description: "Coordinates: { lat, lng }.",
|
||||
},
|
||||
{
|
||||
name: "website / phone",
|
||||
type: "string",
|
||||
description:
|
||||
"Contact details as listed on the profile. Null website is a classic lead-gen signal.",
|
||||
},
|
||||
{
|
||||
name: "totalScore / reviewsCount / reviewsDistribution",
|
||||
type: "number / integer / object",
|
||||
description: "Average rating, review count, and the one-to-five-star breakdown.",
|
||||
},
|
||||
{
|
||||
name: "permanentlyClosed / temporarilyClosed",
|
||||
type: "boolean",
|
||||
description: "Business status flags.",
|
||||
},
|
||||
{
|
||||
name: "openingHours",
|
||||
type: "object[]",
|
||||
description: "Day-by-day hours. Populated when include_details is true.",
|
||||
},
|
||||
{
|
||||
name: "reviews",
|
||||
type: "object[]",
|
||||
description:
|
||||
"Attached reviews when max_reviews > 0: text, stars, publishedAtDate, likesCount, reviewer info, and the owner's response.",
|
||||
},
|
||||
{
|
||||
name: "images / imageUrl / imagesCount",
|
||||
type: "object[] / string / integer",
|
||||
description: "Photos attached when max_images > 0, plus the cover image and count.",
|
||||
},
|
||||
{
|
||||
name: "searchString / rank",
|
||||
type: "string / integer",
|
||||
description: "Provenance: which query found this place and its position in the results.",
|
||||
},
|
||||
{
|
||||
name: "scrapedAt",
|
||||
type: "string",
|
||||
description: "ISO timestamp for when the place was scraped.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
faq: [
|
||||
{
|
||||
question: "Is scraping Google Maps legal?",
|
||||
|
|
|
|||
|
|
@ -156,6 +156,83 @@ export const googleSearch: ConnectorPageContent = {
|
|||
},
|
||||
},
|
||||
|
||||
schema: {
|
||||
requestNote: "Only queries is required. Up to 20 queries per call.",
|
||||
request: [
|
||||
{
|
||||
name: "queries",
|
||||
type: "string[]",
|
||||
required: true,
|
||||
description:
|
||||
"Search terms (e.g. 'wedding photographers denver') or full Google Search URLs. Each term is searched; each URL is scraped as-is. 1 to 20.",
|
||||
},
|
||||
{
|
||||
name: "max_pages_per_query",
|
||||
type: "integer",
|
||||
defaultValue: "1",
|
||||
description: "Result pages to fetch per query, 1 to 10. 1 fetches the first page only.",
|
||||
},
|
||||
{
|
||||
name: "country_code",
|
||||
type: "string",
|
||||
description: "Two-letter country to search from, e.g. 'us', 'fr'.",
|
||||
},
|
||||
{
|
||||
name: "language_code",
|
||||
type: "string",
|
||||
defaultValue: '""',
|
||||
description: "Result language code, e.g. 'en', 'fr'. Blank uses Google's default.",
|
||||
},
|
||||
{
|
||||
name: "site",
|
||||
type: "string",
|
||||
description: "Restrict results to a single domain, e.g. 'example.com'.",
|
||||
},
|
||||
],
|
||||
responseNote:
|
||||
"The response is { items: [...] } with one item per fetched SERP page. One fetched page is one billable unit.",
|
||||
response: [
|
||||
{
|
||||
name: "searchQuery",
|
||||
type: "object",
|
||||
description:
|
||||
"Provenance for this page: the term or URL searched, page number, device, country, and language.",
|
||||
},
|
||||
{
|
||||
name: "resultsTotal",
|
||||
type: "integer",
|
||||
description: "Google's estimated total result count for the query.",
|
||||
},
|
||||
{
|
||||
name: "organicResults",
|
||||
type: "object[]",
|
||||
description:
|
||||
"The organic listings: title, url, displayedUrl, description, date, emphasizedKeywords, siteLinks, and position.",
|
||||
},
|
||||
{
|
||||
name: "paidResults / paidProducts",
|
||||
type: "object[]",
|
||||
description: "Ads and shopping placements on the page, with titles, URLs, and prices.",
|
||||
},
|
||||
{
|
||||
name: "relatedQueries",
|
||||
type: "object[]",
|
||||
description: "The 'related searches' block: title and search URL for each suggestion.",
|
||||
},
|
||||
{
|
||||
name: "peopleAlsoAsk",
|
||||
type: "object[]",
|
||||
description: "People Also Ask entries with the question, answer text, and source page URL.",
|
||||
},
|
||||
{
|
||||
name: "aiOverview",
|
||||
type: "object",
|
||||
description:
|
||||
"The AI Overview block when Google shows one: full answer content plus the sources it cites.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
faq: [
|
||||
{
|
||||
question: "What is a SERP API?",
|
||||
|
|
|
|||
|
|
@ -153,6 +153,138 @@ export const reddit: ConnectorPageContent = {
|
|||
},
|
||||
},
|
||||
|
||||
schema: {
|
||||
requestNote:
|
||||
"Provide at least one source: urls, search_queries, or community. Up to 20 sources per call.",
|
||||
request: [
|
||||
{
|
||||
name: "urls",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"Reddit URLs to scrape: a post, a subreddit (/r/name), a user (/user/name), or a search URL. Max 20.",
|
||||
},
|
||||
{
|
||||
name: "search_queries",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"Search terms to run on Reddit. Each returns up to max_items results. Scope to one subreddit with community. Max 20.",
|
||||
},
|
||||
{
|
||||
name: "community",
|
||||
type: "string",
|
||||
description:
|
||||
"Subreddit name without the r/ prefix, e.g. 'python'. Scopes search_queries to that subreddit; with no search_queries, its listing is scraped.",
|
||||
},
|
||||
{
|
||||
name: "sort",
|
||||
type: "string",
|
||||
defaultValue: '"new"',
|
||||
description: "Result ordering: relevance, hot, top, new, rising, or comments.",
|
||||
},
|
||||
{
|
||||
name: "time_filter",
|
||||
type: "string",
|
||||
description: "Time window for top sorts: hour, day, week, month, year, or all.",
|
||||
},
|
||||
{
|
||||
name: "include_nsfw",
|
||||
type: "boolean",
|
||||
defaultValue: "true",
|
||||
description: "Include posts flagged over-18 in the results.",
|
||||
},
|
||||
{
|
||||
name: "skip_comments",
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
description: "Skip fetching comment trees. Faster when you only need posts or listings.",
|
||||
},
|
||||
{
|
||||
name: "max_items",
|
||||
type: "integer",
|
||||
defaultValue: "10",
|
||||
description: "Max total items to return across all sources. 1 to 100.",
|
||||
},
|
||||
{
|
||||
name: "max_posts",
|
||||
type: "integer",
|
||||
defaultValue: "10",
|
||||
description: "Max posts to pull per subreddit, user, or search target.",
|
||||
},
|
||||
{
|
||||
name: "max_comments",
|
||||
type: "integer",
|
||||
defaultValue: "10",
|
||||
description: "Max comments to pull per post. 0 disables comments.",
|
||||
},
|
||||
{
|
||||
name: "post_date_limit",
|
||||
type: "string",
|
||||
description: "ISO date. Only return posts newer than this, for incremental scrapes.",
|
||||
},
|
||||
{
|
||||
name: "comment_date_limit",
|
||||
type: "string",
|
||||
description: "ISO date. Only return comments newer than this, for incremental scrapes.",
|
||||
},
|
||||
],
|
||||
responseNote:
|
||||
"The response is { items: [...] } with one flat item per result, keyed by dataType. Fields that do not apply to a given dataType are null. One returned item is one billable unit.",
|
||||
response: [
|
||||
{
|
||||
name: "dataType",
|
||||
type: "string",
|
||||
description: "What this item is: post, comment, community, or user.",
|
||||
},
|
||||
{
|
||||
name: "id / url / username",
|
||||
type: "string",
|
||||
description: "Identity and provenance: the Reddit ID, permalink, and author username.",
|
||||
},
|
||||
{
|
||||
name: "title / body",
|
||||
type: "string",
|
||||
description: "Post title and full text body (or comment text for comments).",
|
||||
},
|
||||
{
|
||||
name: "communityName",
|
||||
type: "string",
|
||||
description: "The subreddit the item belongs to, plus numberOfMembers for communities.",
|
||||
},
|
||||
{
|
||||
name: "upVotes / upVoteRatio",
|
||||
type: "integer / number",
|
||||
description: "Score and upvote ratio, the engagement signal for ranking what matters.",
|
||||
},
|
||||
{
|
||||
name: "numberOfComments",
|
||||
type: "integer",
|
||||
description: "Comment count on a post; numberOfReplies for comments.",
|
||||
},
|
||||
{
|
||||
name: "flair / over18 / isVideo",
|
||||
type: "string / boolean",
|
||||
description: "Post flair and content flags.",
|
||||
},
|
||||
{
|
||||
name: "thumbnailUrl / imageUrls / videoUrls",
|
||||
type: "string / string[]",
|
||||
description: "Media attached to the post.",
|
||||
},
|
||||
{
|
||||
name: "postId / parentId",
|
||||
type: "string",
|
||||
description: "Threading for comments: the parent post and parent comment IDs.",
|
||||
},
|
||||
{
|
||||
name: "createdAt / scrapedAt",
|
||||
type: "string",
|
||||
description: "ISO timestamps for when the item was posted and when it was scraped.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
faq: [
|
||||
{
|
||||
question: "Is scraping Reddit legal?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,32 @@ export interface ApiSample {
|
|||
requestBody: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** One request parameter or response field in the schema reference tables. */
|
||||
export interface SchemaField {
|
||||
name: string;
|
||||
/** Short type label, e.g. "string[]", "integer", "boolean", "object[]". */
|
||||
type: string;
|
||||
/** Rendered as a "required" pill next to the type. */
|
||||
required?: boolean;
|
||||
/** Default value shown for optional request parameters, e.g. '"new"', "10". */
|
||||
defaultValue?: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The request/response contract rendered as the "API schema" section. Fields
|
||||
* map 1:1 to the backend capability `ScrapeInput`/`ScrapeOutput` models so the
|
||||
* reference is truthful.
|
||||
*/
|
||||
export interface ApiSchema {
|
||||
/** One-liner above the request table (e.g. which sources are required). */
|
||||
requestNote: string;
|
||||
request: SchemaField[];
|
||||
/** One-liner above the response table (envelope shape + billable unit). */
|
||||
responseNote: string;
|
||||
response: SchemaField[];
|
||||
}
|
||||
|
||||
/** Everything needed to render one connector marketing page. */
|
||||
export interface ConnectorPageContent {
|
||||
slug: string;
|
||||
|
|
@ -99,6 +125,7 @@ export interface ConnectorPageContent {
|
|||
rows: ComparisonRow[];
|
||||
};
|
||||
api: ApiSample;
|
||||
schema: ApiSchema;
|
||||
faq: FaqItem[];
|
||||
related: RelatedLink[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,103 @@ export const webCrawl: ConnectorPageContent = {
|
|||
},
|
||||
},
|
||||
|
||||
schema: {
|
||||
requestNote:
|
||||
"Only startUrls is required. With maxCrawlDepth 0 the call scrapes exactly those URLs; with a higher depth it spiders the site from them.",
|
||||
request: [
|
||||
{
|
||||
name: "startUrls",
|
||||
type: "string[]",
|
||||
required: true,
|
||||
description:
|
||||
"Seed URLs to crawl, 1 to 20. With maxCrawlDepth 0 only these are fetched; otherwise they are the spider's entry points.",
|
||||
},
|
||||
{
|
||||
name: "maxCrawlDepth",
|
||||
type: "integer",
|
||||
defaultValue: "0",
|
||||
description:
|
||||
"Link-hops to follow from each start URL, 0 to 5. 0 scrapes only the start URLs; 1 also fetches their linked pages. The spider stays on the start URL's site.",
|
||||
},
|
||||
{
|
||||
name: "maxCrawlPages",
|
||||
type: "integer",
|
||||
defaultValue: "10",
|
||||
description:
|
||||
"Max pages to fetch in total, start URLs included, 1 to 200. The crawl stops at this ceiling.",
|
||||
},
|
||||
{
|
||||
name: "maxLength",
|
||||
type: "integer",
|
||||
defaultValue: "50000",
|
||||
description: "Max characters of cleaned markdown kept per page. Longer pages truncate.",
|
||||
},
|
||||
{
|
||||
name: "includeUrlPatterns",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"Regex patterns a discovered link must match to be followed. Empty follows every same-site link. Start URLs are always fetched. Max 25.",
|
||||
},
|
||||
{
|
||||
name: "excludeUrlPatterns",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"Regex patterns that exclude a discovered link from being followed. Wins over includeUrlPatterns. Max 25.",
|
||||
},
|
||||
],
|
||||
responseNote:
|
||||
"The response is { items: [...], contacts: {...} }: one item per fetched page in crawl order, plus site-wide deduplicated contacts. Only successful pages are billed.",
|
||||
response: [
|
||||
{
|
||||
name: "items[].url / status",
|
||||
type: "string",
|
||||
description:
|
||||
"The requested URL and its outcome: success, empty (fetched but no content), or failed.",
|
||||
},
|
||||
{
|
||||
name: "items[].markdown",
|
||||
type: "string",
|
||||
description: "The page's content as cleaned markdown, ready for an LLM or a diff.",
|
||||
},
|
||||
{
|
||||
name: "items[].metadata",
|
||||
type: "object",
|
||||
description: "Page metadata such as title and description.",
|
||||
},
|
||||
{
|
||||
name: "items[].crawl",
|
||||
type: "object",
|
||||
description:
|
||||
"Crawl provenance: the URL actually loaded, its link depth, and the referrer page it was discovered on.",
|
||||
},
|
||||
{
|
||||
name: "items[].links",
|
||||
type: "object[]",
|
||||
description:
|
||||
"Every link on the page with its anchor text and kind: internal, external, social, email, or tel.",
|
||||
},
|
||||
{
|
||||
name: "items[].contacts",
|
||||
type: "object",
|
||||
description:
|
||||
"Emails, phones, and social profile URLs harvested from the page's raw HTML, including footer boilerplate the markdown omits.",
|
||||
},
|
||||
{
|
||||
name: "items[].error",
|
||||
type: "string",
|
||||
description: "Failure reason when status is not success.",
|
||||
},
|
||||
{
|
||||
name: "contacts",
|
||||
type: "object",
|
||||
description:
|
||||
"Site-wide contact rollup: every email, phone, and social URL deduplicated across pages, each with the pages it appeared on and a siteWide flag separating company boilerplate from page-local finds like team members.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
faq: [
|
||||
{
|
||||
question: "What formats does the Web Crawl API return?",
|
||||
|
|
|
|||
|
|
@ -150,6 +150,101 @@ export const youtube: ConnectorPageContent = {
|
|||
},
|
||||
},
|
||||
|
||||
schema: {
|
||||
requestNote: "Provide at least one source: urls or search_queries. Up to 20 of each per call.",
|
||||
request: [
|
||||
{
|
||||
name: "urls",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"YouTube URLs to scrape: a video, channel (/@handle or /channel/UC...), playlist (?list=...), shorts, or hashtag page. Max 20.",
|
||||
},
|
||||
{
|
||||
name: "search_queries",
|
||||
type: "string[]",
|
||||
defaultValue: "[]",
|
||||
description:
|
||||
"Search terms to run on YouTube. Each returns up to max_results videos. Max 20.",
|
||||
},
|
||||
{
|
||||
name: "max_results",
|
||||
type: "integer",
|
||||
defaultValue: "10",
|
||||
description:
|
||||
"Max items per source and per content type, 1 to 1,000. For a channel, videos, shorts, and streams are capped independently.",
|
||||
},
|
||||
{
|
||||
name: "download_subtitles",
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
description: "Also fetch each video's subtitle track. Slower.",
|
||||
},
|
||||
{
|
||||
name: "subtitles_language",
|
||||
type: "string",
|
||||
defaultValue: '"en"',
|
||||
description:
|
||||
"Subtitle language code, e.g. 'en', 'fr'. Used when download_subtitles is true.",
|
||||
},
|
||||
],
|
||||
responseNote:
|
||||
"The response is { items: [...] } with one item per video, short, or stream. One returned item is one billable unit.",
|
||||
response: [
|
||||
{
|
||||
name: "title / id / url",
|
||||
type: "string",
|
||||
description: "Video title, YouTube ID, and watch URL.",
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
type: "string",
|
||||
description: "What this item is: video, short, or stream.",
|
||||
},
|
||||
{
|
||||
name: "viewCount / likes / commentsCount",
|
||||
type: "integer",
|
||||
description: "Engagement metrics for ranking what resonates.",
|
||||
},
|
||||
{
|
||||
name: "date / duration",
|
||||
type: "string",
|
||||
description: "Publish date and video length.",
|
||||
},
|
||||
{
|
||||
name: "text / descriptionLinks / hashtags",
|
||||
type: "string / object[] / string[]",
|
||||
description: "Full description, the links it contains, and its hashtags.",
|
||||
},
|
||||
{
|
||||
name: "subtitles",
|
||||
type: "object[]",
|
||||
description:
|
||||
"Full transcript tracks with SRT content when download_subtitles is true. The raw material for content analysis.",
|
||||
},
|
||||
{
|
||||
name: "thumbnailUrl",
|
||||
type: "string",
|
||||
description: "The video's thumbnail image.",
|
||||
},
|
||||
{
|
||||
name: "channelName / channelUrl / channelId",
|
||||
type: "string",
|
||||
description: "The publishing channel's name, URL, and ID.",
|
||||
},
|
||||
{
|
||||
name: "numberOfSubscribers / isChannelVerified",
|
||||
type: "integer / boolean",
|
||||
description: "Channel reach and verification status.",
|
||||
},
|
||||
{
|
||||
name: "input / fromYTUrl / order",
|
||||
type: "string / integer",
|
||||
description: "Provenance: which source produced this item and its position.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
faq: [
|
||||
{
|
||||
question: "Is scraping YouTube legal?",
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ async function uploadBatchesWithConcurrency(
|
|||
files,
|
||||
{
|
||||
folder_name: params.folderName,
|
||||
search_space_id: params.searchSpaceId,
|
||||
workspace_id: params.searchSpaceId,
|
||||
relative_paths: batch.map((e) => e.relativePath),
|
||||
root_folder_id: resolvedRootFolderId,
|
||||
processing_mode: params.processingMode,
|
||||
|
|
@ -169,7 +169,7 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise<number
|
|||
|
||||
const mtimeCheckResult = await documentsApiService.folderMtimeCheck({
|
||||
folder_name: folderName,
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
files: allFiles.map((f) => ({ relative_path: f.relativePath, mtime: f.mtimeMs / 1000 })),
|
||||
});
|
||||
|
||||
|
|
@ -214,7 +214,7 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise<number
|
|||
|
||||
await documentsApiService.folderSyncFinalize({
|
||||
folder_name: folderName,
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
root_folder_id: rootFolderId ?? null,
|
||||
all_relative_paths: allFiles.map((f) => f.relativePath),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -80,20 +80,20 @@ export function trackLogout() {
|
|||
|
||||
export function trackSearchSpaceCreated(searchSpaceId: number, name: string) {
|
||||
safeCapture("search_space_created", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackSearchSpaceDeleted(searchSpaceId: number) {
|
||||
safeCapture("search_space_deleted", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackSearchSpaceViewed(searchSpaceId: number) {
|
||||
safeCapture("search_space_viewed", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ export function trackSearchSpaceViewed(searchSpaceId: number) {
|
|||
|
||||
export function trackChatCreated(searchSpaceId: number, chatId: number) {
|
||||
safeCapture("chat_created", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
chat_id: chatId,
|
||||
});
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ export function trackChatMessageSent(
|
|||
}
|
||||
) {
|
||||
safeCapture("chat_message_sent", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
chat_id: chatId,
|
||||
has_attachments: options?.hasAttachments ?? false,
|
||||
has_mentioned_documents: options?.hasMentionedDocuments ?? false,
|
||||
|
|
@ -128,14 +128,14 @@ export function trackChatMessageSent(
|
|||
|
||||
export function trackChatResponseReceived(searchSpaceId: number, chatId: number) {
|
||||
safeCapture("chat_response_received", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
chat_id: chatId,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackChatError(searchSpaceId: number, chatId: number, error?: string) {
|
||||
safeCapture("chat_error", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
chat_id: chatId,
|
||||
error,
|
||||
});
|
||||
|
|
@ -158,7 +158,7 @@ export function trackChatBlocked(
|
|||
safeCapture(
|
||||
"chat_blocked",
|
||||
compact({
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
chat_id: chatId ?? undefined,
|
||||
flow: payload.flow,
|
||||
kind: payload.kind,
|
||||
|
|
@ -178,7 +178,7 @@ export function trackChatErrorDetailed(
|
|||
safeCapture(
|
||||
"chat_error",
|
||||
compact({
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
chat_id: chatId ?? undefined,
|
||||
flow: payload.flow,
|
||||
kind: payload.kind,
|
||||
|
|
@ -220,7 +220,7 @@ export function trackDocumentUploadStarted(
|
|||
totalSizeBytes: number
|
||||
) {
|
||||
safeCapture("document_upload_started", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
file_count: fileCount,
|
||||
total_size_bytes: totalSizeBytes,
|
||||
});
|
||||
|
|
@ -228,35 +228,35 @@ export function trackDocumentUploadStarted(
|
|||
|
||||
export function trackDocumentUploadSuccess(searchSpaceId: number, fileCount: number) {
|
||||
safeCapture("document_upload_success", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
file_count: fileCount,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackDocumentUploadFailure(searchSpaceId: number, error?: string) {
|
||||
safeCapture("document_upload_failure", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackDocumentDeleted(searchSpaceId: number, documentId: number) {
|
||||
safeCapture("document_deleted", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
document_id: documentId,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackDocumentBulkDeleted(searchSpaceId: number, count: number) {
|
||||
safeCapture("document_bulk_deleted", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
count,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackYouTubeImport(searchSpaceId: number, url: string) {
|
||||
safeCapture("youtube_import_started", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
|
@ -303,7 +303,7 @@ export function trackConnectorEvent(
|
|||
const meta = getConnectorTelemetryMeta(connectorType);
|
||||
safeCapture(`connector_${stage}`, {
|
||||
...compact({
|
||||
search_space_id: options.searchSpaceId ?? undefined,
|
||||
workspace_id: options.searchSpaceId ?? undefined,
|
||||
connector_id: options.connectorId ?? undefined,
|
||||
source: options.source,
|
||||
error: options.error,
|
||||
|
|
@ -369,14 +369,14 @@ export function trackConnectorSynced(
|
|||
|
||||
export function trackSettingsViewed(searchSpaceId: number, section: string) {
|
||||
safeCapture("settings_viewed", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
section,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackSettingsUpdated(searchSpaceId: number, section: string, setting: string) {
|
||||
safeCapture("settings_updated", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
section,
|
||||
setting,
|
||||
});
|
||||
|
|
@ -388,14 +388,14 @@ export function trackSettingsUpdated(searchSpaceId: number, section: string, set
|
|||
|
||||
export function trackPodcastGenerated(searchSpaceId: number, chatId: number) {
|
||||
safeCapture("podcast_generated", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
chat_id: chatId,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackSourcesTabViewed(searchSpaceId: number, tab: string) {
|
||||
safeCapture("sources_tab_viewed", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
|
|
@ -423,7 +423,7 @@ export function trackSearchSpaceInviteSent(
|
|||
}
|
||||
) {
|
||||
safeCapture("search_space_invite_sent", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
role_name: options?.roleName,
|
||||
has_expiry: options?.hasExpiry ?? false,
|
||||
has_max_uses: options?.hasMaxUses ?? false,
|
||||
|
|
@ -436,7 +436,7 @@ export function trackSearchSpaceInviteAccepted(
|
|||
roleName?: string | null
|
||||
) {
|
||||
safeCapture("search_space_invite_accepted", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
search_space_name: searchSpaceName,
|
||||
role_name: roleName,
|
||||
});
|
||||
|
|
@ -454,7 +454,7 @@ export function trackSearchSpaceUserAdded(
|
|||
roleName?: string | null
|
||||
) {
|
||||
safeCapture("search_space_user_added", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
search_space_name: searchSpaceName,
|
||||
role_name: roleName,
|
||||
});
|
||||
|
|
@ -466,7 +466,7 @@ export function trackSearchSpaceUsersViewed(
|
|||
ownerCount: number
|
||||
) {
|
||||
safeCapture("search_space_users_viewed", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
user_count: userCount,
|
||||
owner_count: ownerCount,
|
||||
});
|
||||
|
|
@ -497,7 +497,7 @@ export function trackIndexWithDateRangeOpened(
|
|||
connectorId: number
|
||||
) {
|
||||
safeCapture("index_with_date_range_opened", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
connector_type: connectorType,
|
||||
connector_id: connectorId,
|
||||
});
|
||||
|
|
@ -513,7 +513,7 @@ export function trackIndexWithDateRangeStarted(
|
|||
}
|
||||
) {
|
||||
safeCapture("index_with_date_range_started", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
connector_type: connectorType,
|
||||
connector_id: connectorId,
|
||||
has_start_date: options?.hasStartDate ?? false,
|
||||
|
|
@ -527,7 +527,7 @@ export function trackQuickIndexClicked(
|
|||
connectorId: number
|
||||
) {
|
||||
safeCapture("quick_index_clicked", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
connector_type: connectorType,
|
||||
connector_id: connectorId,
|
||||
});
|
||||
|
|
@ -539,7 +539,7 @@ export function trackConfigurePeriodicIndexingOpened(
|
|||
connectorId: number
|
||||
) {
|
||||
safeCapture("configure_periodic_indexing_opened", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
connector_type: connectorType,
|
||||
connector_id: connectorId,
|
||||
});
|
||||
|
|
@ -552,7 +552,7 @@ export function trackPeriodicIndexingStarted(
|
|||
frequencyMinutes: number
|
||||
) {
|
||||
safeCapture("periodic_indexing_started", {
|
||||
search_space_id: searchSpaceId,
|
||||
workspace_id: searchSpaceId,
|
||||
connector_type: connectorType,
|
||||
connector_id: connectorId,
|
||||
frequency_minutes: frequencyMinutes,
|
||||
|
|
@ -602,7 +602,7 @@ export function trackReferralLanding(refCode: string, landingUrl: string) {
|
|||
// ============================================
|
||||
|
||||
interface AutomationCreatedProps {
|
||||
search_space_id: number;
|
||||
workspace_id: number;
|
||||
automation_id: number;
|
||||
task_count?: number;
|
||||
trigger_type?: string;
|
||||
|
|
@ -617,13 +617,13 @@ export function trackAutomationCreated(props: AutomationCreatedProps) {
|
|||
safeCapture("automation_created", compact(props));
|
||||
}
|
||||
|
||||
export function trackAutomationCreateFailed(props: { search_space_id?: number; error?: string }) {
|
||||
export function trackAutomationCreateFailed(props: { workspace_id?: number; error?: string }) {
|
||||
safeCapture("automation_create_failed", compact(props));
|
||||
}
|
||||
|
||||
export function trackAutomationUpdated(props: {
|
||||
automation_id: number;
|
||||
search_space_id?: number;
|
||||
workspace_id?: number;
|
||||
has_definition_change?: boolean;
|
||||
has_name_change?: boolean;
|
||||
has_description_change?: boolean;
|
||||
|
|
@ -634,7 +634,7 @@ export function trackAutomationUpdated(props: {
|
|||
|
||||
export function trackAutomationStatusChanged(props: {
|
||||
automation_id: number;
|
||||
search_space_id?: number;
|
||||
workspace_id?: number;
|
||||
next_status: string;
|
||||
}) {
|
||||
safeCapture("automation_status_changed", compact(props));
|
||||
|
|
@ -644,7 +644,7 @@ export function trackAutomationUpdateFailed(props: { automation_id: number; erro
|
|||
safeCapture("automation_update_failed", compact(props));
|
||||
}
|
||||
|
||||
export function trackAutomationDeleted(props: { automation_id: number; search_space_id?: number }) {
|
||||
export function trackAutomationDeleted(props: { automation_id: number; workspace_id?: number }) {
|
||||
safeCapture("automation_deleted", compact(props));
|
||||
}
|
||||
|
||||
|
|
@ -699,7 +699,7 @@ export function trackAutomationTriggerRemoveFailed(props: {
|
|||
}
|
||||
|
||||
interface AutomationChatDecisionProps {
|
||||
search_space_id?: number;
|
||||
workspace_id?: number;
|
||||
edited?: boolean;
|
||||
task_count?: number;
|
||||
trigger_type?: string;
|
||||
|
|
@ -712,25 +712,25 @@ export function trackAutomationChatApproved(props: AutomationChatDecisionProps)
|
|||
safeCapture("automation_chat_approved", compact(props));
|
||||
}
|
||||
|
||||
export function trackAutomationChatRejected(props: { search_space_id?: number }) {
|
||||
export function trackAutomationChatRejected(props: { workspace_id?: number }) {
|
||||
safeCapture("automation_chat_rejected", compact(props));
|
||||
}
|
||||
|
||||
export function trackAutomationChatDraftEdited(props: { search_space_id?: number }) {
|
||||
export function trackAutomationChatDraftEdited(props: { workspace_id?: number }) {
|
||||
safeCapture("automation_chat_draft_edited", compact(props));
|
||||
}
|
||||
|
||||
export function trackAutomationChatCreateSucceeded(props: {
|
||||
automation_id: number;
|
||||
name?: string;
|
||||
search_space_id?: number;
|
||||
workspace_id?: number;
|
||||
}) {
|
||||
safeCapture("automation_chat_create_succeeded", compact(props));
|
||||
}
|
||||
|
||||
export function trackAutomationChatCreateFailed(props: {
|
||||
reason: "invalid" | "error";
|
||||
search_space_id?: number;
|
||||
workspace_id?: number;
|
||||
issue_count?: number;
|
||||
message?: string;
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { GetConnectorsRequest } from "@/contracts/types/connector.types";
|
||||
import type { GetDocumentsRequest } from "@/contracts/types/document.types";
|
||||
import type { GetLogsRequest } from "@/contracts/types/log.types";
|
||||
import type { GetSearchSpacesRequest } from "@/contracts/types/search-space.types";
|
||||
import type { GetSearchSpacesRequest } from "@/contracts/types/workspace.types";
|
||||
|
||||
/**
|
||||
* Convert an object to a stable array of [key, value] pairs sorted by key.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
type RouteParams = Record<string, string | string[] | undefined>;
|
||||
|
||||
export function getWorkspaceIdParam(params: RouteParams | null | undefined): string | undefined {
|
||||
const value = params?.workspace_id ?? params?.search_space_id;
|
||||
const value = params?.workspace_id;
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue