1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Artifact {
16 pub id: Uuid,
17 pub user_id: String,
18 pub artifact_type: ArtifactType,
19 pub title: Option<String>,
20 pub content: String,
21 pub metadata: serde_json::Value,
22 pub chunk_index: i32,
23 pub total_chunks: i32,
24 pub parent_id: Option<Uuid>,
25 pub token_count: i32,
26 pub created_at: DateTime<Utc>,
27}
28
29impl Artifact {
30 pub fn new(
32 user_id: impl Into<String>,
33 artifact_type: ArtifactType,
34 content: impl Into<String>,
35 ) -> Self {
36 Self {
37 id: Uuid::new_v4(),
38 user_id: user_id.into(),
39 artifact_type,
40 title: None,
41 content: content.into(),
42 metadata: serde_json::json!({}),
43 chunk_index: 0,
44 total_chunks: 1,
45 parent_id: None,
46 token_count: 0,
47 created_at: Utc::now(),
48 }
49 }
50
51 pub fn with_title(mut self, title: impl Into<String>) -> Self {
53 self.title = Some(title.into());
54 self
55 }
56
57 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
59 self.metadata = metadata;
60 self
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
68#[cfg_attr(feature = "sqlx", sqlx(type_name = "artifact_type", rename_all = "snake_case"))]
69pub enum ArtifactType {
70 ChatResponse,
72 Document,
74 CodeSnippet,
76 Summary,
78 Analysis,
80 Custom,
82}
83
84impl ArtifactType {
85 pub fn all() -> &'static [ArtifactType] {
87 &[
88 ArtifactType::ChatResponse,
89 ArtifactType::Document,
90 ArtifactType::CodeSnippet,
91 ArtifactType::Summary,
92 ArtifactType::Analysis,
93 ArtifactType::Custom,
94 ]
95 }
96
97 pub fn as_str(&self) -> &'static str {
99 match self {
100 ArtifactType::ChatResponse => "chat_response",
101 ArtifactType::Document => "document",
102 ArtifactType::CodeSnippet => "code_snippet",
103 ArtifactType::Summary => "summary",
104 ArtifactType::Analysis => "analysis",
105 ArtifactType::Custom => "custom",
106 }
107 }
108}
109
110impl std::fmt::Display for ArtifactType {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(f, "{}", self.as_str())
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct SearchResult {
123 pub artifact: Artifact,
124 pub similarity: f32,
125 pub highlights: Vec<String>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct SearchQuery {
131 pub query: String,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub artifact_types: Option<Vec<ArtifactType>>,
136 #[serde(default = "default_min_similarity")]
138 pub min_similarity: f32,
139 #[serde(default = "default_limit")]
141 pub limit: usize,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub tags: Option<Vec<String>>,
145}
146
147fn default_min_similarity() -> f32 {
148 0.5
149}
150
151fn default_limit() -> usize {
152 10
153}
154
155impl Default for SearchQuery {
156 fn default() -> Self {
157 Self {
158 query: String::new(),
159 artifact_types: None,
160 min_similarity: default_min_similarity(),
161 limit: default_limit(),
162 tags: None,
163 }
164 }
165}
166
167impl SearchQuery {
168 pub fn new(query: impl Into<String>) -> Self {
170 Self {
171 query: query.into(),
172 ..Default::default()
173 }
174 }
175
176 pub fn with_types(mut self, types: Vec<ArtifactType>) -> Self {
178 self.artifact_types = Some(types);
179 self
180 }
181
182 pub fn with_min_similarity(mut self, threshold: f32) -> Self {
184 self.min_similarity = threshold;
185 self
186 }
187
188 pub fn with_limit(mut self, limit: usize) -> Self {
190 self.limit = limit;
191 self
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct StoreArtifactRequest {
202 pub artifact_type: ArtifactType,
204 pub content: String,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub title: Option<String>,
209 #[serde(default)]
211 pub metadata: serde_json::Value,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct StoreArtifactResponse {
217 pub ids: Vec<Uuid>,
219 pub chunks: usize,
221 pub token_count: i32,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct SearchArtifactsRequest {
228 pub query: String,
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub artifact_types: Option<Vec<ArtifactType>>,
233 #[serde(default = "default_min_similarity")]
235 pub min_similarity: f32,
236 #[serde(default = "default_limit")]
238 pub limit: usize,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct SearchArtifactsResponse {
244 pub results: Vec<SearchResult>,
245 pub total: usize,
246 pub query: String,
247}
248
249#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_artifact_creation() {
259 let artifact = Artifact::new("user123", ArtifactType::CodeSnippet, "fn main() {}")
260 .with_title("Main function")
261 .with_metadata(serde_json::json!({"language": "rust"}));
262
263 assert_eq!(artifact.user_id, "user123");
264 assert_eq!(artifact.artifact_type, ArtifactType::CodeSnippet);
265 assert_eq!(artifact.title, Some("Main function".to_string()));
266 }
267
268 #[test]
269 fn test_artifact_type_serialization() {
270 let types = vec![
271 (ArtifactType::ChatResponse, "\"chat_response\""),
272 (ArtifactType::Document, "\"document\""),
273 (ArtifactType::CodeSnippet, "\"code_snippet\""),
274 (ArtifactType::Summary, "\"summary\""),
275 (ArtifactType::Analysis, "\"analysis\""),
276 (ArtifactType::Custom, "\"custom\""),
277 ];
278
279 for (artifact_type, expected) in types {
280 let json = serde_json::to_string(&artifact_type).unwrap();
281 assert_eq!(json, expected);
282 }
283 }
284
285 #[test]
286 fn test_search_query_builder() {
287 let query = SearchQuery::new("rust error handling")
288 .with_types(vec![ArtifactType::CodeSnippet, ArtifactType::Document])
289 .with_min_similarity(0.7)
290 .with_limit(5);
291
292 assert_eq!(query.query, "rust error handling");
293 assert_eq!(query.artifact_types.unwrap().len(), 2);
294 assert_eq!(query.min_similarity, 0.7);
295 assert_eq!(query.limit, 5);
296 }
297
298 #[test]
299 fn test_search_result_serialization() {
300 let result = SearchResult {
301 artifact: Artifact::new("user", ArtifactType::Document, "Hello world"),
302 similarity: 0.95,
303 highlights: vec!["Hello".to_string()],
304 };
305
306 let json = serde_json::to_value(&result).unwrap();
307 assert!((json["similarity"].as_f64().unwrap() - 0.95).abs() < 0.01);
308 assert_eq!(json["highlights"][0], "Hello");
309 }
310
311 #[test]
312 fn test_store_request_serialization() {
313 let request = StoreArtifactRequest {
314 artifact_type: ArtifactType::CodeSnippet,
315 content: "let x = 42;".to_string(),
316 title: Some("Variable".to_string()),
317 metadata: serde_json::json!({"language": "rust"}),
318 };
319
320 let json = serde_json::to_value(&request).unwrap();
321 assert_eq!(json["artifact_type"], "code_snippet");
322 assert_eq!(json["content"], "let x = 42;");
323 }
324}