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