started gitea api impl #2
+24
-22
@@ -9,17 +9,18 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct ReviewResult {
|
pub struct ReviewResult {
|
||||||
reviews: Vec<ReviewItem>,
|
pub reviews: Vec<ReviewItem>,
|
||||||
comment: String,
|
pub comment: String,
|
||||||
|
pub cost: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct ReviewItem {
|
pub struct ReviewItem {
|
||||||
filename: String,
|
pub filename: String,
|
||||||
line: Option<u64>,
|
pub line: Option<u64>,
|
||||||
code: String,
|
pub code: String,
|
||||||
message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a filename to a markdown language identifier for syntax highlighting.
|
/// Map a filename to a markdown language identifier for syntax highlighting.
|
||||||
@@ -69,7 +70,7 @@ pub struct Bot {
|
|||||||
impl Bot {
|
impl Bot {
|
||||||
pub fn new(config: EnvConfig) -> anyhow::Result<Self> {
|
pub fn new(config: EnvConfig) -> anyhow::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
gitea_api: GiteaAPI::new(&config.gitea_url, &config.gitea_token, config.gitea_timeout),
|
gitea_api: GiteaAPI::new(&config.gitea_url, &config.gitea_token, config.gitea_timeout)?,
|
||||||
open_router_client: OpenRouterClient::new(
|
open_router_client: OpenRouterClient::new(
|
||||||
&config.open_router_api_key,
|
&config.open_router_api_key,
|
||||||
&config.open_router_model,
|
&config.open_router_model,
|
||||||
@@ -112,7 +113,7 @@ impl Bot {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let bot_result: Result<String, anyhow::Error> = async {
|
let bot_result: Result<ReviewResult, anyhow::Error> = async {
|
||||||
let git_diff = self
|
let git_diff = self
|
||||||
.download_git_diff(&review_payload.pull_request.diff_url)
|
.download_git_diff(&review_payload.pull_request.diff_url)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -122,7 +123,12 @@ impl Bot {
|
|||||||
.replace("{comment}", &review_payload.comment.body)
|
.replace("{comment}", &review_payload.comment.body)
|
||||||
.replace("{diff}", &git_diff);
|
.replace("{diff}", &git_diff);
|
||||||
|
|
||||||
self.open_router_client.chat(&bot_request).await
|
let chat_result = self.open_router_client.chat(&bot_request).await?;
|
||||||
|
let mut review_result = serde_json::from_str::<ReviewResult>(&chat_result.message)?;
|
||||||
|
|
||||||
|
review_result.cost = chat_result.cost;
|
||||||
|
|
||||||
|
Ok(review_result)
|
||||||
}
|
}
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -142,17 +148,7 @@ impl Bot {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn review_result_to_markdown(&self, result: &str) -> String {
|
fn review_result_to_markdown(&self, review_result: &ReviewResult) -> String {
|
||||||
let review_result: ReviewResult = match serde_json::from_str(result) {
|
|
||||||
Ok(review_result) => review_result,
|
|
||||||
Err(_) => {
|
|
||||||
return format!(
|
|
||||||
"Failed to parse review result. Raw output:\n\n```json\n{}\n```",
|
|
||||||
result
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if review_result.reviews.is_empty() {
|
if review_result.reviews.is_empty() {
|
||||||
return String::from("No issues found. ✅");
|
return String::from("No issues found. ✅");
|
||||||
}
|
}
|
||||||
@@ -181,6 +177,12 @@ impl Bot {
|
|||||||
md.push('\n');
|
md.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(cost) = review_result.cost {
|
||||||
|
md.push_str("\n---\n\n");
|
||||||
|
md.push_str(&format!("### Cost: ${}", cost));
|
||||||
|
md.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
md
|
md
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub const REVIEW_PROMPT: &str = "
|
|||||||
The line number increments by 1 for each context or added line.
|
The line number increments by 1 for each context or added line.
|
||||||
|
|
||||||
Return your feedback, in french, with only this json format, reviews must contain each review
|
Return your feedback, in french, with only this json format, reviews must contain each review
|
||||||
|
All fields are mandatory.
|
||||||
(filename field must contain the full path with extension) and comment must contain a final summary:
|
(filename field must contain the full path with extension) and comment must contain a final summary:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
+14
-7
@@ -12,15 +12,22 @@ pub struct GiteaAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GiteaAPI {
|
impl GiteaAPI {
|
||||||
pub fn new(base_url: &str, token: &str, timeout: u64) -> Self {
|
pub fn new(base_url: &str, token: &str, timeout: u64) -> anyhow::Result<Self> {
|
||||||
Self {
|
let mut default_headers = reqwest::header::HeaderMap::new();
|
||||||
|
default_headers.insert(
|
||||||
|
reqwest::header::HeaderName::from_static("authorization"),
|
||||||
|
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", token))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
base_url: String::from(base_url),
|
base_url: String::from(base_url),
|
||||||
client: reqwest::Client::builder()
|
client: reqwest::Client::builder()
|
||||||
.timeout(Duration::from_secs(timeout))
|
.timeout(Duration::from_secs(timeout))
|
||||||
|
.default_headers(default_headers)
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
token: String::from(token),
|
token: String::from(token),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn comment(
|
pub async fn comment(
|
||||||
@@ -30,8 +37,8 @@ impl GiteaAPI {
|
|||||||
index: u64,
|
index: u64,
|
||||||
) -> anyhow::Result<Comment> {
|
) -> anyhow::Result<Comment> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/api/v1/repos/{}/issues/{}/comments?access_token={}",
|
"{}/api/v1/repos/{}/issues/{}/comments",
|
||||||
self.base_url, full_name, index, self.token
|
self.base_url, full_name, index
|
||||||
);
|
);
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
@@ -53,8 +60,8 @@ impl GiteaAPI {
|
|||||||
comment_id: u64,
|
comment_id: u64,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/api/v1/repos/{}/issues/comments/{}?access_token={}",
|
"{}/api/v1/repos/{}/issues/comments/{}",
|
||||||
self.base_url, full_name, comment_id, self.token
|
self.base_url, full_name, comment_id
|
||||||
);
|
);
|
||||||
|
|
||||||
self.client
|
self.client
|
||||||
|
|||||||
+14
-9
@@ -2,6 +2,11 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use openrouter_rs::{Message, api::chat::ChatCompletionRequest};
|
use openrouter_rs::{Message, api::chat::ChatCompletionRequest};
|
||||||
|
|
||||||
|
pub struct ChatResult {
|
||||||
|
pub message: String,
|
||||||
|
pub cost: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OpenRouterClient {
|
pub struct OpenRouterClient {
|
||||||
client: openrouter_rs::OpenRouterClient,
|
client: openrouter_rs::OpenRouterClient,
|
||||||
model: String,
|
model: String,
|
||||||
@@ -22,21 +27,21 @@ impl OpenRouterClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn chat(&self, msg: &str) -> anyhow::Result<String> {
|
pub async fn chat(&self, msg: &str) -> anyhow::Result<ChatResult> {
|
||||||
let request = ChatCompletionRequest::builder()
|
let request = ChatCompletionRequest::builder()
|
||||||
.model(&self.model)
|
.model(&self.model)
|
||||||
.enable_reasoning()
|
.enable_reasoning()
|
||||||
.messages(vec![Message::new(
|
.messages(vec![Message::new(openrouter_rs::types::Role::User, msg)])
|
||||||
openrouter_rs::types::Role::Developer,
|
|
||||||
msg,
|
|
||||||
)])
|
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let response = self.client.chat().create(&request).await?;
|
let response = self.client.chat().create(&request).await?;
|
||||||
|
|
||||||
response.choices[0]
|
Ok(ChatResult {
|
||||||
.content()
|
message: response.choices[0]
|
||||||
.map(|msg| String::from(msg))
|
.content()
|
||||||
.ok_or(anyhow::anyhow!("No content"))
|
.map(|msg| String::from(msg))
|
||||||
|
.ok_or(anyhow::anyhow!("No content"))?,
|
||||||
|
cost: response.usage.and_then(|u| u.cost),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user