87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
|
|
import { z } from "zod";
|
||
|
|
import { apiClient } from "./client";
|
||
|
|
|
||
|
|
const idLike = z.union([z.string(), z.number()]).transform(String);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Research result and session payload are loose by design — the backend
|
||
|
|
* returns `asdict(result)` of a Python dataclass we don't want to mirror
|
||
|
|
* field-for-field. Keep core fields strict and let extras pass through.
|
||
|
|
*/
|
||
|
|
export const researchRunResultSchema = z
|
||
|
|
.object({
|
||
|
|
job_id: idLike.optional(),
|
||
|
|
project_id: idLike.optional(),
|
||
|
|
project_name: z.string().optional(),
|
||
|
|
goal: z.string().optional(),
|
||
|
|
status: z.string().optional(),
|
||
|
|
started_at: z.string().nullable().optional(),
|
||
|
|
finished_at: z.string().nullable().optional(),
|
||
|
|
steps_taken: z.number().optional(),
|
||
|
|
pages_visited: z.number().optional(),
|
||
|
|
entities_found: z.number().optional(),
|
||
|
|
claims_added: z.number().optional(),
|
||
|
|
error: z.string().nullable().optional(),
|
||
|
|
})
|
||
|
|
.passthrough();
|
||
|
|
|
||
|
|
export type ResearchRunResult = z.infer<typeof researchRunResultSchema>;
|
||
|
|
|
||
|
|
export const researchSessionItemSchema = z
|
||
|
|
.object({
|
||
|
|
job_id: idLike,
|
||
|
|
status: z.string().optional(),
|
||
|
|
goal: z.string().nullable().optional(),
|
||
|
|
seed_url: z.string().nullable().optional(),
|
||
|
|
started_at: z.string().nullable().optional(),
|
||
|
|
finished_at: z.string().nullable().optional(),
|
||
|
|
steps_taken: z.number().nullable().optional(),
|
||
|
|
pages_visited: z.number().nullable().optional(),
|
||
|
|
entities_found: z.number().nullable().optional(),
|
||
|
|
})
|
||
|
|
.passthrough();
|
||
|
|
|
||
|
|
export const researchSessionListSchema = z.array(researchSessionItemSchema);
|
||
|
|
export type ResearchSessionItem = z.infer<typeof researchSessionItemSchema>;
|
||
|
|
|
||
|
|
export const researchSessionDetailSchema = researchSessionItemSchema.extend({
|
||
|
|
trace: z.array(z.record(z.string(), z.unknown())).optional(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export type ResearchSessionDetail = z.infer<typeof researchSessionDetailSchema>;
|
||
|
|
|
||
|
|
export interface StartResearchRequest {
|
||
|
|
project_name: string;
|
||
|
|
source_name: string;
|
||
|
|
url?: string;
|
||
|
|
seed_entity_id?: number | null;
|
||
|
|
goal?: string;
|
||
|
|
max_depth?: number;
|
||
|
|
max_steps?: number;
|
||
|
|
max_branch?: number;
|
||
|
|
min_relevance?: number;
|
||
|
|
same_domain_only?: boolean;
|
||
|
|
analyze_page_types?: string[];
|
||
|
|
extractor_provider?: string;
|
||
|
|
extractor_model?: string | null;
|
||
|
|
extractor_base_url?: string | null;
|
||
|
|
check_robots_txt?: boolean;
|
||
|
|
respect_robots_txt?: boolean | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const researchApi = {
|
||
|
|
startByProject: (body: StartResearchRequest) =>
|
||
|
|
apiClient.post("/research/run/by-project", researchRunResultSchema, body),
|
||
|
|
listSessions: (projectName: string, limit = 25) =>
|
||
|
|
apiClient.get(
|
||
|
|
`/projects/${encodeURIComponent(projectName)}/research/sessions`,
|
||
|
|
researchSessionListSchema,
|
||
|
|
{ query: { limit } },
|
||
|
|
),
|
||
|
|
getSession: (jobId: string) =>
|
||
|
|
apiClient.get(
|
||
|
|
`/research/sessions/${encodeURIComponent(jobId)}`,
|
||
|
|
researchSessionDetailSchema,
|
||
|
|
),
|
||
|
|
};
|