···
2
-
(** TODO:claude Typesense API client for Bushel *)
1
+
(** Typesense API client for Bushel *)
···
| Json_error msg -> Fmt.pf fmt "JSON error: %s" msg
| Connection_error msg -> Fmt.pf fmt "Connection error: %s" msg
20
-
(** TODO:claude Create authentication headers for Typesense API *)
19
+
(** Create authentication headers for Typesense API *)
let auth_headers api_key =
|> Requests.Headers.set "X-TYPESENSE-API-KEY" api_key
|> Requests.Headers.set "Content-Type" "application/json"
26
-
(** TODO:claude Make HTTP request to Typesense API *)
25
+
(** Make HTTP request to Typesense API *)
let make_request ~sw ~env ?(meth=`GET) ?(body="") config path =
let uri = Uri.of_string (config.endpoint ^ path) in
let headers = auth_headers config.api_key in
···
Error (Connection_error (Printexc.to_string exn))
52
-
(** TODO:claude Create a collection with given schema *)
51
+
(** Create a collection with given schema *)
let create_collection ~sw ~env config (schema : Ezjsonm.value) =
let body = Ezjsonm.value_to_string schema in
make_request ~sw ~env ~meth:`POST ~body config "/collections"
57
-
(** TODO:claude Check if collection exists *)
56
+
(** Check if collection exists *)
let collection_exists ~sw ~env config name =
let result = make_request ~sw ~env config ("/collections/" ^ name) in
···
| Error (Http_error (404, _)) -> false
65
-
(** TODO:claude Delete a collection *)
64
+
(** Delete a collection *)
let delete_collection ~sw ~env config name =
make_request ~sw ~env ~meth:`DELETE config ("/collections/" ^ name)
69
-
(** TODO:claude Upload documents to a collection in batch *)
68
+
(** Upload documents to a collection in batch *)
let upload_documents ~sw ~env config collection_name (documents : Ezjsonm.value list) =
let jsonl_lines = List.map (fun doc -> Ezjsonm.value_to_string doc) documents in
let body = String.concat "\n" jsonl_lines in
···
(Printf.sprintf "/collections/%s/documents/import?action=upsert" collection_name)
77
-
(** TODO:claude Convert Bushel objects to Typesense documents *)
76
+
(** Convert Bushel objects to Typesense documents *)
79
-
(** TODO:claude Helper function to truncate long strings for embedding *)
78
+
(** Helper function to truncate long strings for embedding *)
let truncate_for_embedding ?(max_chars=20000) text =
if String.length text <= max_chars then text
else String.sub text 0 max_chars
84
-
(** TODO:claude Helper function to convert Ptime to Unix timestamp *)
83
+
(** Helper function to convert Ptime to Unix timestamp *)
let ptime_to_timestamp ptime =
let span = Ptime.to_span ptime in
let seconds = Ptime.Span.to_int_s span in
···
| Some s -> Int64.of_int s
92
-
(** TODO:claude Helper function to convert date tuple to Unix timestamp *)
91
+
(** Helper function to convert date tuple to Unix timestamp *)
let date_to_timestamp (year, month, day) =
match Ptime.of_date (year, month, day) with
| Some ptime -> ptime_to_timestamp ptime
···
("thumbnail_url", string (Option.value ~default:"" thumbnail_url));
324
-
(** TODO:claude Helper function to add embedding field to schema *)
323
+
(** Helper function to add embedding field to schema *)
let add_embedding_field_to_schema schema config embedding_from_fields =
let fields = get_dict schema |> List.assoc "fields" |> get_list (fun f -> f) in
···
let updated_fields = fields @ [embedding_field] in
340
-
let updated_schema =
339
+
let updated_schema =
if k = "fields" then (k, list (fun f -> f) updated_fields)
···
348
-
(** TODO:claude Upload all bushel objects to their respective collections *)
347
+
(** Upload all bushel objects to their respective collections *)
let upload_all ~sw ~env config entries =
print_string "Uploading bushel data to Typesense\n";
···
List.iter upload_collection collections
415
-
(** TODO:claude Re-export search types from Typesense_cliente *)
414
+
(** Re-export search types from Typesense_cliente *)
type search_result = Typesense_cliente.search_result = {
···
432
-
(** TODO:claude Convert bushel config to client config *)
431
+
(** Convert bushel config to client config *)
let to_client_config (config : config) =
Typesense_cliente.{ endpoint = config.endpoint; api_key = config.api_key }
436
-
(** TODO:claude Search a single collection *)
435
+
(** Search a single collection *)
let search_collection ~sw ~env (config : config) collection_name query ?(limit=10) ?(offset=0) () =
let client_config = to_client_config config in
let requests_session = Requests.create ~sw env in
···
| Error (Typesense_cliente.Json_error msg) -> Error (Json_error msg)
| Error (Typesense_cliente.Connection_error msg) -> Error (Connection_error msg)
448
-
(** TODO:claude Search across all collections - use client multisearch *)
447
+
(** Search across all collections - use client multisearch *)
let search_all ~sw ~env (config : config) query ?(limit=10) ?(offset=0) () =
let client_config = to_client_config config in
let requests_session = Requests.create ~sw env in
···
| Error (Typesense_cliente.Json_error msg) -> Error (Json_error msg)
| Error (Typesense_cliente.Connection_error msg) -> Error (Connection_error msg)
462
-
(** TODO:claude List all collections *)
461
+
(** List all collections *)
let list_collections ~sw ~env (config : config) =
let client_config = to_client_config config in
let requests_session = Requests.create ~sw env in
···
| Error (Typesense_cliente.Json_error msg) -> Error (Json_error msg)
| Error (Typesense_cliente.Connection_error msg) -> Error (Connection_error msg)
474
-
(** TODO:claude Re-export multisearch types from Typesense_cliente *)
473
+
(** Re-export multisearch types from Typesense_cliente *)
type multisearch_response = Typesense_cliente.multisearch_response = {
results: search_response list;
479
-
(** TODO:claude Perform multisearch across all collections *)
478
+
(** Perform multisearch across all collections *)
let multisearch ~sw ~env (config : config) query ?(limit=10) () =
let client_config = to_client_config config in
let requests_session = Requests.create ~sw env in
···
| Error (Typesense_cliente.Json_error msg) -> Error (Json_error msg)
| Error (Typesense_cliente.Connection_error msg) -> Error (Connection_error msg)
491
-
(** TODO:claude Combine multisearch results into single result set *)
490
+
(** Combine multisearch results into single result set *)
let combine_multisearch_results (multisearch_resp : multisearch_response) ?(limit=10) ?(offset=0) () =
Typesense_cliente.combine_multisearch_results multisearch_resp ~limit ~offset ()
495
-
(** TODO:claude Load configuration from files *)
494
+
(** Load configuration from files *)
let load_config_from_files () =
let read_file_if_exists filename =
if Sys.file_exists filename then
···
Some (String.trim content)
506
-
let endpoint = match read_file_if_exists ".typesense-url" with
508
-
| None -> "http://localhost:8108"
511
-
let api_key = match read_file_if_exists ".typesense-key" with
514
-
try Sys.getenv "TYPESENSE_API_KEY"
515
-
with Not_found -> ""
518
-
let openai_key = match read_file_if_exists ".openrouter-api" with
521
-
try Sys.getenv "OPENAI_API_KEY"
522
-
with Not_found -> ""
525
-
{ endpoint; api_key; openai_key }
527
-
(** TODO:claude Re-export pretty printer from Typesense_cliente *)
528
-
let pp_search_result_oneline = Typesense_cliente.pp_search_result_oneline
529
-
||||||| parent of 40c9549 (bushel)
532
-
open Cohttp_lwt_unix
534
-
(** TODO:claude Typesense API client for Bushel *)
539
-
openai_key : string;
543
-
| Http_error of int * string
544
-
| Json_error of string
545
-
| Connection_error of string
547
-
let pp_error fmt = function
548
-
| Http_error (code, msg) -> Fmt.pf fmt "HTTP %d: %s" code msg
549
-
| Json_error msg -> Fmt.pf fmt "JSON error: %s" msg
550
-
| Connection_error msg -> Fmt.pf fmt "Connection error: %s" msg
552
-
(** TODO:claude Create authentication headers for Typesense API *)
553
-
let auth_headers api_key =
554
-
Cohttp.Header.of_list [
555
-
("X-TYPESENSE-API-KEY", api_key);
556
-
("Content-Type", "application/json");
559
-
(** TODO:claude Make HTTP request to Typesense API *)
560
-
let make_request ?(meth=`GET) ?(body="") config path =
561
-
let uri = Uri.of_string (config.endpoint ^ path) in
562
-
let headers = auth_headers config.api_key in
563
-
let body = if body = "" then `Empty else `String body in
564
-
Lwt.catch (fun () ->
565
-
let* resp, body = Client.call ~headers ~body meth uri in
566
-
let status = Cohttp.Code.code_of_status (Response.status resp) in
567
-
let* body_str = Cohttp_lwt.Body.to_string body in
568
-
if status >= 200 && status < 300 then
569
-
Lwt.return_ok body_str
571
-
Lwt.return_error (Http_error (status, body_str))
573
-
Lwt.return_error (Connection_error (Printexc.to_string exn))
576
-
(** TODO:claude Create a collection with given schema *)
577
-
let create_collection config (schema : Ezjsonm.value) =
578
-
let body = Ezjsonm.value_to_string schema in
579
-
make_request ~meth:`POST ~body config "/collections"
581
-
(** TODO:claude Check if collection exists *)
582
-
let collection_exists config name =
583
-
let* result = make_request config ("/collections/" ^ name) in
585
-
| Ok _ -> Lwt.return true
586
-
| Error (Http_error (404, _)) -> Lwt.return false
587
-
| Error _ -> Lwt.return false
589
-
(** TODO:claude Delete a collection *)
590
-
let delete_collection config name =
591
-
make_request ~meth:`DELETE config ("/collections/" ^ name)
593
-
(** TODO:claude Upload documents to a collection in batch *)
594
-
let upload_documents config collection_name (documents : Ezjsonm.value list) =
595
-
let jsonl_lines = List.map (fun doc -> Ezjsonm.value_to_string doc) documents in
596
-
let body = String.concat "\n" jsonl_lines in
597
-
make_request ~meth:`POST ~body config
598
-
(Printf.sprintf "/collections/%s/documents/import?action=upsert" collection_name)
601
-
(** TODO:claude Convert Bushel objects to Typesense documents *)
603
-
(** TODO:claude Helper function to truncate long strings for embedding *)
604
-
let truncate_for_embedding ?(max_chars=20000) text =
605
-
if String.length text <= max_chars then text
606
-
else String.sub text 0 max_chars
608
-
(** TODO:claude Helper function to convert Ptime to Unix timestamp *)
609
-
let ptime_to_timestamp ptime =
610
-
let span = Ptime.to_span ptime in
611
-
let seconds = Ptime.Span.to_int_s span in
613
-
| Some s -> Int64.of_int s
616
-
(** TODO:claude Helper function to convert date tuple to Unix timestamp *)
617
-
let date_to_timestamp (year, month, day) =
618
-
match Ptime.of_date (year, month, day) with
619
-
| Some ptime -> ptime_to_timestamp ptime
622
-
(** Resolve author handles to full names in a list *)
623
-
let resolve_author_list contacts authors =
624
-
List.map (fun author ->
625
-
(* Strip '@' prefix if present *)
627
-
if String.length author > 0 && author.[0] = '@' then
628
-
String.sub author 1 (String.length author - 1)
632
-
(* Try to look up as a contact handle *)
633
-
match Contact.find_by_handle contacts handle with
634
-
| Some contact -> Contact.name contact
635
-
| None -> author (* Keep original if not found *)
638
-
let contact_to_document (contact : Contact.t) =
639
-
let open Ezjsonm in
640
-
let safe_string_list_from_opt = function
645
-
("id", string (Contact.handle contact));
646
-
("handle", string (Contact.handle contact));
647
-
("name", string (Contact.name contact));
648
-
("names", list string (Contact.names contact));
649
-
("email", list string (safe_string_list_from_opt (Contact.email contact)));
650
-
("icon", list string (safe_string_list_from_opt (Contact.icon contact)));
651
-
("github", list string (safe_string_list_from_opt (Contact.github contact)));
652
-
("twitter", list string (safe_string_list_from_opt (Contact.twitter contact)));
653
-
("url", list string (safe_string_list_from_opt (Contact.url contact)));
656
-
let paper_to_document entries (paper : Paper.t) =
657
-
let date_tuple = Paper.date paper in
658
-
let contacts = Entry.contacts entries in
660
-
(* Helper to extract string arrays from JSON, handling both single strings and arrays *)
661
-
let extract_string_array_from_json json_field_name =
663
-
(* Access the raw JSON from the paper record *)
664
-
let paper_json = Paper.raw_json paper in
665
-
let value = Ezjsonm.get_dict paper_json |> List.assoc json_field_name in
668
-
| `A l -> List.filter_map (function `String s -> Some s | _ -> None) l
673
-
(* Resolve author handles to full names *)
674
-
let authors = resolve_author_list contacts (Paper.authors paper) in
676
-
(* Convert abstract markdown to plain text *)
677
-
let abstract = Md.markdown_to_plaintext entries (Paper.abstract paper) |> truncate_for_embedding in
679
-
(* Extract publication metadata *)
680
-
let bibtype = Paper.bibtype paper in
684
-
| "article" -> Printf.sprintf "Journal: %s" (Paper.journal paper)
685
-
| "inproceedings" -> Printf.sprintf "Proceedings: %s" (Paper.journal paper)
686
-
| "misc" | "techreport" -> Printf.sprintf "Preprint: %s" (Paper.journal paper)
687
-
| _ -> Printf.sprintf "%s: %s" (String.capitalize_ascii bibtype) (Paper.journal paper)
691
-
(* Get bibtex from raw JSON *)
694
-
let paper_json = Paper.raw_json paper in
695
-
Ezjsonm.get_dict paper_json
696
-
|> List.assoc "bibtex"
697
-
|> Ezjsonm.get_string
701
-
let thumbnail_url = Entry.thumbnail entries (`Paper paper) in
703
-
("id", Ezjsonm.string (Paper.slug paper));
704
-
("title", Ezjsonm.string (Paper.title paper));
705
-
("authors", Ezjsonm.list Ezjsonm.string authors);
706
-
("abstract", Ezjsonm.string abstract);
707
-
("metadata", Ezjsonm.string metadata);
708
-
("bibtex", Ezjsonm.string bibtex);
709
-
("date", Ezjsonm.string (let y, m, d = date_tuple in Printf.sprintf "%04d-%02d-%02d" y m d));
710
-
("date_timestamp", Ezjsonm.int64 (date_to_timestamp date_tuple));
711
-
("tags", Ezjsonm.list Ezjsonm.string (Paper.tags paper));
712
-
("doi", Ezjsonm.list Ezjsonm.string (extract_string_array_from_json "doi"));
713
-
("pdf_url", Ezjsonm.list Ezjsonm.string (extract_string_array_from_json "pdf_url"));
714
-
("journal", Ezjsonm.list Ezjsonm.string (extract_string_array_from_json "journal"));
715
-
("related_projects", Ezjsonm.list Ezjsonm.string (Paper.project_slugs paper));
716
-
("thumbnail_url", Ezjsonm.string (Option.value ~default:"" thumbnail_url));
719
-
let project_to_document entries (project : Project.t) =
720
-
let open Ezjsonm in
721
-
(* Use January 1st of start year as the date for sorting *)
722
-
let date_timestamp = date_to_timestamp (project.start, 1, 1) in
724
-
(* Convert body markdown to plain text *)
725
-
let description = Md.markdown_to_plaintext entries (Project.body project) |> truncate_for_embedding in
727
-
let thumbnail_url = Entry.thumbnail entries (`Project project) in
729
-
("id", string project.slug);
730
-
("title", string (Project.title project));
731
-
("description", string description);
732
-
("start", int project.start);
733
-
("finish", option int project.finish);
734
-
("start_year", int project.start);
735
-
("date", string (Printf.sprintf "%04d-01-01" project.start));
736
-
("date_timestamp", int64 date_timestamp);
737
-
("tags", list string (Project.tags project));
738
-
("thumbnail_url", string (Option.value ~default:"" thumbnail_url));
741
-
let video_to_document entries (video : Video.t) =
742
-
let open Ezjsonm in
743
-
let datetime = Video.datetime video in
744
-
let safe_string_list_from_opt = function
749
-
(* Convert body markdown to plain text *)
750
-
let description = Md.markdown_to_plaintext entries (Video.body video) |> truncate_for_embedding in
752
-
(* Resolve paper and project slugs to titles *)
753
-
let paper_title = match Video.paper video with
755
-
(match Entry.lookup entries slug with
756
-
| Some entry -> Some (Entry.title entry)
757
-
| None -> Some slug) (* Fallback to slug if not found *)
760
-
let project_title = match Video.project video with
762
-
(match Entry.lookup entries slug with
763
-
| Some entry -> Some (Entry.title entry)
764
-
| None -> Some slug) (* Fallback to slug if not found *)
768
-
let thumbnail_url = Entry.thumbnail entries (`Video video) in
770
-
("id", string (Video.slug video));
771
-
("title", string (Video.title video));
772
-
("description", string description);
773
-
("published_date", string (Ptime.to_rfc3339 datetime));
774
-
("date", string (Ptime.to_rfc3339 datetime));
775
-
("date_timestamp", int64 (ptime_to_timestamp datetime));
776
-
("url", string (Video.url video));
777
-
("uuid", string (Video.uuid video));
778
-
("is_talk", bool (Video.talk video));
779
-
("paper", list string (safe_string_list_from_opt paper_title));
780
-
("project", list string (safe_string_list_from_opt project_title));
781
-
("tags", list string video.tags);
782
-
("thumbnail_url", string (Option.value ~default:"" thumbnail_url));
785
-
let note_to_document entries (note : Note.t) =
786
-
let open Ezjsonm in
787
-
let datetime = Note.datetime note in
788
-
let safe_string_list_from_opt = function
793
-
(* Convert body markdown to plain text *)
794
-
let content = Md.markdown_to_plaintext entries (Note.body note) |> truncate_for_embedding in
796
-
let thumbnail_url = Entry.thumbnail entries (`Note note) in
797
-
let word_count = Note.words note in
799
-
("id", string (Note.slug note));
800
-
("title", string (Note.title note));
801
-
("date", string (Ptime.to_rfc3339 datetime));
802
-
("date_timestamp", int64 (ptime_to_timestamp datetime));
803
-
("content", string content);
804
-
("tags", list string (Note.tags note));
805
-
("draft", bool (Note.draft note));
806
-
("synopsis", list string (safe_string_list_from_opt (Note.synopsis note)));
807
-
("thumbnail_url", string (Option.value ~default:"" thumbnail_url));
808
-
("words", int word_count);
811
-
let idea_to_document entries (idea : Idea.t) =
812
-
let open Ezjsonm in
813
-
let contacts = Entry.contacts entries in
814
-
(* Use January 1st of the year as the date for sorting *)
815
-
let date_timestamp = date_to_timestamp (Idea.year idea, 1, 1) in
817
-
(* Convert body markdown to plain text *)
818
-
let description = Md.markdown_to_plaintext entries (Idea.body idea) |> truncate_for_embedding in
820
-
(* Resolve supervisor and student handles to full names *)
821
-
let supervisors = resolve_author_list contacts (Idea.supervisors idea) in
822
-
let students = resolve_author_list contacts (Idea.students idea) in
824
-
(* Resolve project slug to project title *)
825
-
let project_title =
826
-
match Entry.lookup entries (Idea.project idea) with
827
-
| Some entry -> Entry.title entry
828
-
| None -> Idea.project idea (* Fallback to slug if not found *)
831
-
let thumbnail_url = Entry.thumbnail entries (`Idea idea) in
833
-
("id", string idea.slug);
834
-
("title", string (Idea.title idea));
835
-
("description", string description);
836
-
("level", string (Idea.level_to_string (Idea.level idea)));
837
-
("project", string project_title);
838
-
("status", string (Idea.status_to_string (Idea.status idea)));
839
-
("year", int (Idea.year idea));
840
-
("date", string (Printf.sprintf "%04d-01-01" (Idea.year idea)));
841
-
("date_timestamp", int64 date_timestamp);
842
-
("supervisors", list string supervisors);
843
-
("students", list string students);
844
-
("tags", list string idea.tags);
845
-
("thumbnail_url", string (Option.value ~default:"" thumbnail_url));
848
-
(** TODO:claude Helper function to add embedding field to schema *)
849
-
let add_embedding_field_to_schema schema config embedding_from_fields =
850
-
let open Ezjsonm in
851
-
let fields = get_dict schema |> List.assoc "fields" |> get_list (fun f -> f) in
852
-
let embedding_field = dict [
853
-
("name", string "embedding");
854
-
("type", string "float[]");
856
-
("from", list string embedding_from_fields);
857
-
("model_config", dict [
858
-
("model_name", string "openai/text-embedding-3-small");
859
-
("api_key", string config.openai_key);
863
-
let updated_fields = fields @ [embedding_field] in
864
-
let updated_schema =
865
-
List.map (fun (k, v) ->
866
-
if k = "fields" then (k, list (fun f -> f) updated_fields)
868
-
) (get_dict schema)
870
-
dict updated_schema
872
-
(** TODO:claude Upload all bushel objects to their respective collections *)
873
-
let upload_all config entries =
874
-
let* () = Lwt_io.write Lwt_io.stdout "Uploading bushel data to Typesense\n" in
876
-
let contacts = Entry.contacts entries in
877
-
let papers = Entry.papers entries in
878
-
let projects = Entry.projects entries in
879
-
let notes = Entry.notes entries in
880
-
let videos = Entry.videos entries in
881
-
let ideas = Entry.ideas entries in
883
-
let collections = [
884
-
("contacts", add_embedding_field_to_schema Contact.typesense_schema config ["name"; "names"], (List.map contact_to_document contacts : Ezjsonm.value list));
885
-
("papers", add_embedding_field_to_schema Paper.typesense_schema config ["title"; "abstract"; "authors"], (List.map (paper_to_document entries) papers : Ezjsonm.value list));
886
-
("videos", add_embedding_field_to_schema Video.typesense_schema config ["title"; "description"], (List.map (video_to_document entries) videos : Ezjsonm.value list));
887
-
("projects", add_embedding_field_to_schema Project.typesense_schema config ["title"; "description"; "tags"], (List.map (project_to_document entries) projects : Ezjsonm.value list));
888
-
("notes", add_embedding_field_to_schema Note.typesense_schema config ["title"; "content"; "tags"], (List.map (note_to_document entries) notes : Ezjsonm.value list));
889
-
("ideas", add_embedding_field_to_schema Idea.typesense_schema config ["title"; "description"; "tags"], (List.map (idea_to_document entries) ideas : Ezjsonm.value list));
892
-
let upload_collection ((name, schema, documents) : string * Ezjsonm.value * Ezjsonm.value list) =
893
-
let* () = Lwt_io.write Lwt_io.stdout (Fmt.str "Processing collection: %s\n" name) in
894
-
let* exists = collection_exists config name in
897
-
let* () = Lwt_io.write Lwt_io.stdout (Fmt.str "Collection %s exists, deleting...\n" name) in
898
-
let* result = delete_collection config name in
900
-
| Ok _ -> Lwt_io.write Lwt_io.stdout (Fmt.str "Deleted collection %s\n" name)
902
-
let err_str = Fmt.str "%a" pp_error err in
903
-
Lwt_io.write Lwt_io.stdout (Fmt.str "Failed to delete collection %s: %s\n" name err_str)
907
-
let* () = Lwt_io.write Lwt_io.stdout (Fmt.str "Creating collection %s with %d documents\n" name (List.length documents)) in
908
-
let* result = create_collection config schema in
911
-
let* () = Lwt_io.write Lwt_io.stdout (Fmt.str "Created collection %s\n" name) in
912
-
if documents = [] then
913
-
Lwt_io.write Lwt_io.stdout (Fmt.str "No documents to upload for %s\n" name)
915
-
let* result = upload_documents config name documents in
918
-
(* Count successes and failures *)
919
-
let lines = String.split_on_char '\n' response in
920
-
let successes = List.fold_left (fun acc line ->
921
-
if String.contains line ':' && Str.string_match (Str.regexp ".*success.*true.*") line 0 then acc + 1 else acc) 0 lines in
922
-
let failures = List.fold_left (fun acc line ->
923
-
if String.contains line ':' && Str.string_match (Str.regexp ".*success.*false.*") line 0 then acc + 1 else acc) 0 lines in
924
-
let* () = Lwt_io.write Lwt_io.stdout (Fmt.str "Upload results for %s: %d successful, %d failed out of %d total\n"
925
-
name successes failures (List.length documents)) in
926
-
if failures > 0 then
927
-
let* () = Lwt_io.write Lwt_io.stdout (Fmt.str "Failed documents in %s:\n" name) in
928
-
let failed_lines = List.filter (fun line -> Str.string_match (Str.regexp ".*success.*false.*") line 0) lines in
929
-
Lwt_list.iter_s (fun line -> Lwt_io.write Lwt_io.stdout (line ^ "\n")) failed_lines
933
-
let err_str = Fmt.str "%a" pp_error err in
934
-
Lwt_io.write Lwt_io.stdout (Fmt.str "Failed to upload documents to %s: %s\n" name err_str)
937
-
let err_str = Fmt.str "%a" pp_error err in
938
-
Lwt_io.write Lwt_io.stdout (Fmt.str "Failed to create collection %s: %s\n" name err_str)
941
-
Lwt_list.iter_s upload_collection collections
943
-
(** TODO:claude Re-export search types from Typesense_client *)
944
-
type search_result = Typesense_client.search_result = {
949
-
collection: string;
950
-
highlights: (string * string list) list;
951
-
document: Ezjsonm.value;
954
-
type search_response = Typesense_client.search_response = {
955
-
hits: search_result list;
960
-
(** TODO:claude Convert bushel config to client config *)
961
-
let to_client_config (config : config) =
962
-
Typesense_client.{ endpoint = config.endpoint; api_key = config.api_key }
964
-
(** TODO:claude Search a single collection *)
965
-
let search_collection (config : config) collection_name query ?(limit=10) ?(offset=0) () =
966
-
let client_config = to_client_config config in
967
-
let* result = Typesense_client.search_collection client_config collection_name query ~limit ~offset () in
969
-
| Ok response -> Lwt.return_ok response
970
-
| Error (Typesense_client.Http_error (code, msg)) -> Lwt.return_error (Http_error (code, msg))
971
-
| Error (Typesense_client.Json_error msg) -> Lwt.return_error (Json_error msg)
972
-
| Error (Typesense_client.Connection_error msg) -> Lwt.return_error (Connection_error msg)
974
-
(** TODO:claude Search across all collections - use client multisearch *)
975
-
let search_all (config : config) query ?(limit=10) ?(offset=0) () =
976
-
let client_config = to_client_config config in
977
-
let* result = Typesense_client.multisearch client_config query ~limit:50 () in
979
-
| Ok multisearch_resp ->
980
-
let combined_response = Typesense_client.combine_multisearch_results multisearch_resp ~limit ~offset () in
981
-
Lwt.return_ok combined_response
982
-
| Error (Typesense_client.Http_error (code, msg)) -> Lwt.return_error (Http_error (code, msg))
983
-
| Error (Typesense_client.Json_error msg) -> Lwt.return_error (Json_error msg)
984
-
| Error (Typesense_client.Connection_error msg) -> Lwt.return_error (Connection_error msg)
986
-
(** TODO:claude List all collections *)
987
-
let list_collections (config : config) =
988
-
let client_config = to_client_config config in
989
-
let* result = Typesense_client.list_collections client_config in
991
-
| Ok collections -> Lwt.return_ok collections
992
-
| Error (Typesense_client.Http_error (code, msg)) -> Lwt.return_error (Http_error (code, msg))
993
-
| Error (Typesense_client.Json_error msg) -> Lwt.return_error (Json_error msg)
994
-
| Error (Typesense_client.Connection_error msg) -> Lwt.return_error (Connection_error msg)
996
-
(** TODO:claude Re-export multisearch types from Typesense_client *)
997
-
type multisearch_response = Typesense_client.multisearch_response = {
998
-
results: search_response list;
1001
-
(** TODO:claude Perform multisearch across all collections *)
1002
-
let multisearch (config : config) query ?(limit=10) () =
1003
-
let client_config = to_client_config config in
1004
-
let* result = Typesense_client.multisearch client_config query ~limit () in
1006
-
| Ok multisearch_resp -> Lwt.return_ok multisearch_resp
1007
-
| Error (Typesense_client.Http_error (code, msg)) -> Lwt.return_error (Http_error (code, msg))
1008
-
| Error (Typesense_client.Json_error msg) -> Lwt.return_error (Json_error msg)
1009
-
| Error (Typesense_client.Connection_error msg) -> Lwt.return_error (Connection_error msg)
1011
-
(** TODO:claude Combine multisearch results into single result set *)
1012
-
let combine_multisearch_results (multisearch_resp : multisearch_response) ?(limit=10) ?(offset=0) () =
1013
-
Typesense_client.combine_multisearch_results multisearch_resp ~limit ~offset ()
1015
-
(** TODO:claude Load configuration from files *)
1016
-
let load_config_from_files () =
1017
-
let read_file_if_exists filename =
1018
-
if Sys.file_exists filename then
1019
-
let ic = open_in filename in
1020
-
let content = really_input_string ic (in_channel_length ic) in
1022
-
Some (String.trim content)
let endpoint = match read_file_if_exists ".typesense-url" with
| None -> "http://localhost:8108"
let api_key = match read_file_if_exists ".typesense-key" with
try Sys.getenv "TYPESENSE_API_KEY"
let openai_key = match read_file_if_exists ".openrouter-api" with
try Sys.getenv "OPENAI_API_KEY"
{ endpoint; api_key; openai_key }
1047
-
(** TODO:claude Re-export pretty printer from Typesense_client *)
1048
-
let pp_search_result_oneline = Typesense_client.pp_search_result_oneline
1049
-
>>>>>>> 40c9549 (bushel)
526
+
(** Re-export pretty printer from Typesense_cliente *)
527
+
let pp_search_result_oneline = Typesense_cliente.pp_search_result_oneline