1use chrono::{DateTime, NaiveDate, Utc};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use utoipa::ToSchema;
6use uuid::Uuid;
7
8use crate::Visibility;
9
10#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
11pub struct SearchRequest {
12 pub query: String,
13 #[serde(default = "default_limit")]
14 pub limit: usize,
15 #[serde(default)]
16 pub source_key: Option<String>,
17 #[serde(default)]
18 pub group_path: Option<String>,
19 #[serde(default)]
20 pub published_after: Option<NaiveDate>,
21 #[serde(default)]
22 pub published_before: Option<NaiveDate>,
23}
24
25fn default_limit() -> usize {
26 8
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
30#[serde(rename_all = "snake_case")]
31pub enum SearchMode {
32 Vector,
33 Hybrid,
34}
35
36impl SearchMode {
37 pub fn as_str(self) -> &'static str {
38 match self {
39 Self::Vector => "vector",
40 Self::Hybrid => "hybrid",
41 }
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
46pub struct SearchHit {
47 pub chunk_id: Uuid,
48 pub document_id: i64,
49 pub group_key: String,
50 pub group_path: String,
51 pub visibility: Visibility,
52 pub source_key: String,
53 pub external_id: String,
54 pub title: String,
55 pub summary: Option<String>,
56 pub source_uri: String,
57 pub published_at: Option<NaiveDate>,
58 pub chunk_index: i32,
59 pub chunk_text: String,
60 pub score: f32,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub vector_score: Option<f32>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub keyword_score: Option<f32>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub rerank_score: Option<f32>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub match_reason: Option<String>,
69 #[serde(default)]
70 #[schema(value_type = Object)]
71 pub metadata_json: Value,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub library_file_id: Option<Uuid>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub library_section_label: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub library_path: Option<String>,
78 #[serde(default)]
79 pub is_library_file: bool,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
83pub struct SearchResponse {
84 pub query: String,
85 pub hits: Vec<SearchHit>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
89pub struct DocumentResponse {
90 pub document_id: i64,
91 pub group_key: String,
92 pub group_path: String,
93 pub visibility: Visibility,
94 pub source_key: String,
95 pub external_id: String,
96 pub title: String,
97 pub summary: Option<String>,
98 pub source_uri: String,
99 pub published_at: Option<NaiveDate>,
100 pub updated_at: DateTime<Utc>,
101 pub record_hash: String,
102 #[serde(default)]
103 #[schema(value_type = Object)]
104 pub metadata_json: Value,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub library_file_id: Option<Uuid>,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub library_section_label: Option<String>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub library_path: Option<String>,
111 #[serde(default)]
112 pub is_library_file: bool,
113 pub chunks: Vec<DocumentChunkResponse>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
117pub struct DocumentChunkResponse {
118 pub chunk_id: Uuid,
119 pub chunk_index: i32,
120 pub text: String,
121}