| use derive_more::Display; |
| use derive_setters::Setters; |
| use serde::{Deserialize, Serialize}; |
| use uuid::Uuid; |
|
|
| use crate::WorkspaceId; |
|
|
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum SyncProgress { |
| |
| Starting, |
| |
| WorkspaceCreated { |
| |
| workspace_id: WorkspaceId, |
| }, |
| |
| DiscoveringFiles { |
| |
| workspace_id: WorkspaceId, |
| |
| path: std::path::PathBuf, |
| }, |
| |
| FilesDiscovered { |
| |
| count: usize, |
| }, |
| |
| ComparingFiles { |
| |
| remote_files: usize, |
| |
| local_files: usize, |
| }, |
| |
| DiffComputed { |
| |
| added: usize, |
| |
| deleted: usize, |
| |
| modified: usize, |
| }, |
| |
| Syncing { |
| |
| current: usize, |
| |
| total: usize, |
| }, |
| |
| Completed { |
| |
| total_files: usize, |
| |
| uploaded_files: usize, |
| |
| failed_files: usize, |
| }, |
| } |
|
|
| impl SyncProgress { |
| |
| pub fn weight(&self) -> Option<u64> { |
| match self { |
| Self::Syncing { current, total } => { |
| let sync_progress = if *total > 0 { |
| (*current as f64) / (*total as f64) * 100.0 |
| } else { |
| 0.0 |
| }; |
| Some(sync_progress as u64) |
| } |
| _ => None, |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
| pub struct WorkspaceAuth { |
| |
| pub user_id: UserId, |
| |
| pub token: crate::ApiKey, |
| |
| pub created_at: chrono::DateTime<chrono::Utc>, |
| } |
|
|
| impl From<WorkspaceAuth> for crate::AuthDetails { |
| fn from(auth: WorkspaceAuth) -> Self { |
| crate::AuthDetails::ApiKey(auth.token) |
| } |
| } |
|
|
| impl WorkspaceAuth { |
| |
| pub fn new(user_id: UserId, token: crate::ApiKey) -> Self { |
| Self { user_id, token, created_at: chrono::Utc::now() } |
| } |
| } |
|
|
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
| pub struct FileRead { |
| |
| pub path: String, |
| |
| pub content: String, |
| } |
|
|
| impl FileRead { |
| |
| pub fn new(path: String, content: String) -> Self { |
| Self { path, content } |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub struct CodeBase<T> { |
| pub user_id: UserId, |
| pub workspace_id: WorkspaceId, |
| pub data: T, |
| } |
|
|
| impl<T> CodeBase<T> { |
| pub fn new(user_id: UserId, workspace_id: WorkspaceId, data: T) -> Self { |
| Self { user_id, workspace_id, data } |
| } |
| } |
|
|
| #[derive(Debug, Clone, PartialEq, Eq, Setters)] |
| #[setters(strip_option, into)] |
| pub struct SearchParams<'a> { |
| pub query: &'a str, |
| pub limit: Option<usize>, |
| pub top_k: Option<u32>, |
| pub use_case: String, |
| pub starts_with: Option<String>, |
| pub ends_with: Option<Vec<String>>, |
| } |
|
|
| impl<'a> SearchParams<'a> { |
| pub fn new(query: &'a str, use_case: &str) -> Self { |
| Self { |
| query, |
| limit: None, |
| top_k: None, |
| use_case: use_case.to_string(), |
| starts_with: None, |
| ends_with: None, |
| } |
| } |
| } |
|
|
| pub type CodeSearchQuery<'a> = CodeBase<SearchParams<'a>>; |
| pub type FileUpload = CodeBase<Vec<FileRead>>; |
| pub type FileDeletion = CodeBase<Vec<String>>; |
| pub type WorkspaceFiles = CodeBase<()>; |
|
|
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display)] |
| #[display("{}", _0)] |
| pub struct UserId(Uuid); |
|
|
| impl UserId { |
| |
| pub fn generate() -> Self { |
| Self(Uuid::new_v4()) |
| } |
|
|
| |
| |
| |
| |
| pub fn from_string(s: &str) -> anyhow::Result<Self> { |
| Ok(Self(Uuid::parse_str(s)?)) |
| } |
| } |
|
|
| |
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display)] |
| #[display("{}", _0)] |
| pub struct NodeId(String); |
|
|
| impl NodeId { |
| |
| pub fn new(id: impl Into<String>) -> Self { |
| Self(id.into()) |
| } |
|
|
| |
| pub fn as_str(&self) -> &str { |
| &self.0 |
| } |
| } |
|
|
| impl From<String> for NodeId { |
| fn from(s: String) -> Self { |
| Self(s) |
| } |
| } |
|
|
| impl From<&str> for NodeId { |
| fn from(s: &str) -> Self { |
| Self(s.to_string()) |
| } |
| } |
|
|
| impl AsRef<str> for NodeId { |
| fn as_ref(&self) -> &str { |
| &self.0 |
| } |
| } |
|
|
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
| pub struct GitInfo { |
| |
| pub commit: String, |
| |
| pub branch: String, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
| pub struct WorkspaceInfo { |
| |
| pub workspace_id: WorkspaceId, |
| |
| pub working_dir: String, |
| |
| pub node_count: Option<u64>, |
| |
| pub relation_count: Option<u64>, |
| |
| pub last_updated: Option<chrono::DateTime<chrono::Utc>>, |
| |
| pub created_at: chrono::DateTime<chrono::Utc>, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Setters)] |
| pub struct FileUploadResponse { |
| |
| pub workspace_id: WorkspaceId, |
| |
| pub files_processed: usize, |
| |
| pub upload_stats: FileUploadInfo, |
| |
| pub is_new_workspace: bool, |
| } |
|
|
| impl FileUploadResponse { |
| |
| pub fn new( |
| workspace_id: WorkspaceId, |
| files_processed: usize, |
| upload_stats: FileUploadInfo, |
| ) -> Self { |
| Self { |
| workspace_id, |
| files_processed, |
| upload_stats, |
| is_new_workspace: false, |
| } |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] |
| #[non_exhaustive] |
| pub struct FileUploadInfo { |
| |
| pub nodes_created: usize, |
| |
| pub relations_created: usize, |
| } |
|
|
| impl std::ops::Add for FileUploadInfo { |
| type Output = Self; |
|
|
| fn add(self, other: Self) -> Self { |
| Self { |
| nodes_created: self.nodes_created + other.nodes_created, |
| relations_created: self.relations_created + other.relations_created, |
| } |
| } |
| } |
|
|
| impl FileUploadInfo { |
| |
| pub fn new(nodes_created: usize, relations_created: usize) -> Self { |
| Self { nodes_created, relations_created } |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] |
| pub struct CodebaseQueryResult { |
| |
| pub query: String, |
| |
| pub use_case: String, |
| |
| pub results: Vec<Node>, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] |
| pub struct CodebaseSearchResults { |
| |
| pub queries: Vec<CodebaseQueryResult>, |
| } |
|
|
| |
| |
| |
| |
| #[derive( |
| Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_setters::Setters, |
| )] |
| #[setters(strip_option)] |
| pub struct Node { |
| |
| pub node_id: NodeId, |
| |
| #[serde(flatten)] |
| pub node: NodeData, |
| |
| pub relevance: Option<f32>, |
| |
| pub distance: Option<f32>, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] |
| pub struct FileChunk { |
| |
| pub file_path: String, |
| |
| pub content: String, |
| |
| pub start_line: u32, |
| |
| pub end_line: u32, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] |
| pub struct FileNode { |
| |
| pub file_path: String, |
| |
| pub content: String, |
| |
| pub hash: String, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] |
| pub struct FileRef { |
| |
| pub file_path: String, |
| |
| pub file_hash: String, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] |
| pub struct Note { |
| |
| pub content: String, |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] |
| pub struct Task { |
| |
| pub task: String, |
| } |
|
|
| |
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] |
| #[serde(tag = "type", rename_all = "snake_case")] |
| pub enum NodeData { |
| |
| #[from] |
| FileChunk(FileChunk), |
| |
| #[from] |
| File(FileNode), |
| |
| #[from] |
| FileRef(FileRef), |
| |
| #[from] |
| Note(Note), |
| |
| #[from] |
| Task(Task), |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use pretty_assertions::assert_eq; |
|
|
| use super::*; |
|
|
| #[test] |
| fn test_user_id_roundtrip() { |
| let user_id = UserId::generate(); |
| let s = user_id.to_string(); |
| let parsed = UserId::from_string(&s).unwrap(); |
| assert_eq!(user_id, parsed); |
| } |
|
|
| #[test] |
| fn test_workspace_id_roundtrip() { |
| let workspace_id = WorkspaceId::generate(); |
| let s = workspace_id.to_string(); |
| let parsed = WorkspaceId::from_string(&s).unwrap(); |
| assert_eq!(workspace_id, parsed); |
| } |
|
|
| #[test] |
| fn test_search_params_with_file_extension() { |
| let actual = SearchParams::new("retry mechanism", "find retry logic") |
| .limit(10usize) |
| .top_k(20u32) |
| .ends_with(vec![".rs".to_string()]); |
|
|
| let expected = SearchParams { |
| query: "retry mechanism", |
| limit: Some(10), |
| top_k: Some(20), |
| use_case: "find retry logic".to_string(), |
| starts_with: None, |
| ends_with: Some(vec![".rs".to_string()]), |
| }; |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_search_params_with_multiple_file_extensions() { |
| let actual = SearchParams::new("retry mechanism", "find retry logic") |
| .limit(10usize) |
| .top_k(20u32) |
| .ends_with(vec![ |
| ".rs".to_string(), |
| ".ts".to_string(), |
| ".py".to_string(), |
| ]); |
|
|
| let expected = SearchParams { |
| query: "retry mechanism", |
| limit: Some(10), |
| top_k: Some(20), |
| use_case: "find retry logic".to_string(), |
| starts_with: None, |
| ends_with: Some(vec![ |
| ".rs".to_string(), |
| ".ts".to_string(), |
| ".py".to_string(), |
| ]), |
| }; |
|
|
| assert_eq!(actual, expected); |
| } |
|
|
| #[test] |
| fn test_search_params_without_file_extension() { |
| let actual = SearchParams::new("auth logic", "authentication implementation").limit(5usize); |
|
|
| let expected = SearchParams { |
| query: "auth logic", |
| limit: Some(5), |
| top_k: None, |
| use_case: "authentication implementation".to_string(), |
| starts_with: None, |
| ends_with: None, |
| }; |
|
|
| assert_eq!(actual, expected); |
| } |
| } |
|
|