1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6pub struct CitationsConfig {
7 pub enabled: bool,
8}
9
10impl CitationsConfig {
11 pub const fn enabled() -> Self {
12 Self { enabled: true }
13 }
14
15 pub const fn disabled() -> Self {
16 Self { enabled: false }
17 }
18}
19
20impl Default for CitationsConfig {
21 fn default() -> Self {
22 Self::enabled()
23 }
24}
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27#[serde(tag = "type", rename_all = "snake_case")]
28pub enum Citation {
29 CharLocation(CharLocationCitation),
30 PageLocation(PageLocationCitation),
31 ContentBlockLocation(ContentBlockLocationCitation),
32 SearchResultLocation(SearchResultLocationCitation),
33 WebSearchResultLocation(WebSearchResultLocationCitation),
34}
35
36impl Citation {
37 pub fn cited_text(&self) -> &str {
38 match self {
39 Self::CharLocation(c) => &c.cited_text,
40 Self::PageLocation(c) => &c.cited_text,
41 Self::ContentBlockLocation(c) => &c.cited_text,
42 Self::SearchResultLocation(c) => &c.cited_text,
43 Self::WebSearchResultLocation(c) => &c.cited_text,
44 }
45 }
46
47 pub fn document_title(&self) -> Option<&str> {
48 match self {
49 Self::CharLocation(c) => c.document_title.as_deref(),
50 Self::PageLocation(c) => c.document_title.as_deref(),
51 Self::ContentBlockLocation(c) => c.document_title.as_deref(),
52 Self::SearchResultLocation(c) => c.title.as_deref(),
53 Self::WebSearchResultLocation(c) => c.title.as_deref(),
54 }
55 }
56
57 pub fn document_index(&self) -> Option<usize> {
58 match self {
59 Self::CharLocation(c) => Some(c.document_index),
60 Self::PageLocation(c) => Some(c.document_index),
61 Self::ContentBlockLocation(c) => Some(c.document_index),
62 Self::SearchResultLocation(_) | Self::WebSearchResultLocation(_) => None,
63 }
64 }
65
66 pub fn is_search_result(&self) -> bool {
67 matches!(
68 self,
69 Self::SearchResultLocation(_) | Self::WebSearchResultLocation(_)
70 )
71 }
72
73 pub fn url(&self) -> Option<&str> {
74 match self {
75 Self::WebSearchResultLocation(c) => Some(&c.url),
76 Self::SearchResultLocation(c) => Some(&c.source),
77 _ => None,
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct CharLocationCitation {
84 pub cited_text: String,
85 pub document_index: usize,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub document_title: Option<String>,
88 pub start_char_index: usize,
89 pub end_char_index: usize,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct PageLocationCitation {
94 pub cited_text: String,
95 pub document_index: usize,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub document_title: Option<String>,
98 pub start_page_number: u32,
99 pub end_page_number: u32,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub struct ContentBlockLocationCitation {
104 pub cited_text: String,
105 pub document_index: usize,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub document_title: Option<String>,
108 pub start_block_index: usize,
109 pub end_block_index: usize,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct SearchResultLocationCitation {
114 pub cited_text: String,
115 pub source: String,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub title: Option<String>,
118 pub search_result_index: usize,
119 pub start_block_index: usize,
120 pub end_block_index: usize,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct WebSearchResultLocationCitation {
125 pub cited_text: String,
126 pub url: String,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub title: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub encrypted_index: Option<String>,
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_citation_serialization() {
139 let citation = Citation::CharLocation(CharLocationCitation {
140 cited_text: "test".to_string(),
141 document_index: 0,
142 document_title: Some("Doc".to_string()),
143 start_char_index: 0,
144 end_char_index: 4,
145 });
146
147 let json = serde_json::to_string(&citation).unwrap();
148 assert!(json.contains("char_location"));
149
150 let parsed: Citation = serde_json::from_str(&json).unwrap();
151 assert_eq!(parsed.cited_text(), "test");
152 }
153
154 #[test]
155 fn test_search_result_citation() {
156 let citation = Citation::SearchResultLocation(SearchResultLocationCitation {
157 cited_text: "search text".to_string(),
158 source: "https://example.com".to_string(),
159 title: Some("Example".to_string()),
160 search_result_index: 0,
161 start_block_index: 0,
162 end_block_index: 1,
163 });
164
165 assert!(citation.is_search_result());
166 assert!(citation.document_index().is_none());
167 }
168}