1use anyhow::Result;
13use reqwest::{Client, StatusCode};
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::time::Duration;
17
18const FEEDBACK_API_BASE: &str = "https://f.8b.is";
19const USER_AGENT: &str = concat!("smart-tree/", env!("CARGO_PKG_VERSION"));
20
21#[derive(Debug, Serialize)]
23pub struct FeedbackRequest {
24 pub category: String,
25 pub title: String,
26 pub description: String,
27 pub impact_score: u8,
28 pub frequency_score: u8,
29 pub affected_command: Option<String>,
30 pub mcp_tool: Option<String>,
31 pub proposed_fix: Option<String>,
32 pub proposed_solution: Option<String>,
33 pub fix_complexity: Option<String>,
34 pub auto_fixable: Option<bool>,
35 pub tags: Vec<String>,
36 pub examples: Vec<FeedbackExample>,
37 pub smart_tree_version: String,
38 pub anonymous: bool,
39 pub github_url: Option<String>,
40}
41
42#[derive(Debug, Serialize)]
43pub struct FeedbackExample {
44 pub description: String,
45 pub code: String,
46 pub expected_output: Option<String>,
47}
48
49#[derive(Debug, Serialize)]
51pub struct ToolRequest {
52 pub tool_name: String,
53 pub description: String,
54 pub use_case: String,
55 pub expected_output: String,
56 pub productivity_impact: String,
57 pub proposed_parameters: Option<Value>,
58 pub smart_tree_version: String,
59 pub anonymous: bool,
60 pub github_url: Option<String>,
61}
62
63#[derive(Debug, Deserialize)]
65pub struct FeedbackResponse {
66 pub feedback_id: String,
67 pub message: String,
68 pub status: String,
69}
70
71#[derive(Debug, Deserialize)]
73pub struct VersionInfo {
74 pub version: String,
75 pub release_date: String,
76 pub download_url: String,
77 pub release_notes_url: String,
78 pub features: Vec<String>,
79 pub ai_benefits: Vec<String>,
80}
81
82pub struct FeedbackClient {
84 client: Client,
85}
86
87impl FeedbackClient {
88 pub fn new() -> Result<Self> {
89 let client = Client::builder()
90 .user_agent(USER_AGENT)
91 .timeout(Duration::from_secs(30))
92 .build()?;
93
94 Ok(Self { client })
95 }
96
97 pub async fn submit_feedback(&self, feedback: FeedbackRequest) -> Result<FeedbackResponse> {
99 let url = format!("{}/api/feedback", FEEDBACK_API_BASE);
100
101 let response = self.client.post(&url).json(&feedback).send().await?;
102
103 match response.status() {
104 StatusCode::OK => {
105 let data = response.json::<FeedbackResponse>().await?;
106 Ok(data)
107 }
108 StatusCode::TOO_MANY_REQUESTS => Err(anyhow::anyhow!(
109 "Rate limit exceeded. Please try again later."
110 )),
111 status => {
112 let error_text = response
113 .text()
114 .await
115 .unwrap_or_else(|_| "Unknown error".to_string());
116 Err(anyhow::anyhow!("API error ({}): {}", status, error_text))
117 }
118 }
119 }
120
121 pub async fn submit_tool_request(&self, request: ToolRequest) -> Result<FeedbackResponse> {
123 let url = format!("{}/api/tool-request", FEEDBACK_API_BASE);
124
125 let response = self.client.post(&url).json(&request).send().await?;
126
127 match response.status() {
128 StatusCode::OK => {
129 let data = response.json::<FeedbackResponse>().await?;
130 Ok(data)
131 }
132 StatusCode::TOO_MANY_REQUESTS => Err(anyhow::anyhow!(
133 "Rate limit exceeded. Please try again later."
134 )),
135 status => {
136 let error_text = response
137 .text()
138 .await
139 .unwrap_or_else(|_| "Unknown error".to_string());
140 Err(anyhow::anyhow!("API error ({}): {}", status, error_text))
141 }
142 }
143 }
144
145 pub async fn check_for_updates(&self) -> Result<VersionInfo> {
147 let url = format!("{}/api/smart-tree/latest", FEEDBACK_API_BASE);
148
149 let response = self.client.get(&url).send().await?;
150
151 match response.status() {
152 StatusCode::OK => {
153 let data = response.json::<VersionInfo>().await?;
154 Ok(data)
155 }
156 status => {
157 let error_text = response
158 .text()
159 .await
160 .unwrap_or_else(|_| "Unknown error".to_string());
161 Err(anyhow::anyhow!("API error ({}): {}", status, error_text))
162 }
163 }
164 }
165}
166
167impl Default for FeedbackClient {
168 fn default() -> Self {
169 Self::new().expect("Failed to create feedback client")
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_feedback_client_creation() {
179 let client = FeedbackClient::new();
180 assert!(client.is_ok());
181 }
182}