Add API communication
This commit is contained in:
parent
eb2a0d12c4
commit
809ab9a979
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
GROQ_API_KEY=your-api-key
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,3 +14,5 @@ Cargo.lock
|
|||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
# Environment Variables
|
||||||
|
.env
|
||||||
|
@ -4,4 +4,10 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
dotenv = "0.15.0"
|
||||||
eframe = "0.30.0"
|
eframe = "0.30.0"
|
||||||
|
futures = "0.3.31"
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
serde = "1.0.216"
|
||||||
|
serde_json = "1.0.134"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
18
rust-learning/src/config.rs
Normal file
18
rust-learning/src/config.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use dotenv::dotenv;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub groq_api_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
groq_api_key: std::env::var("GROQ_API_KEY").expect("GROQ_API_KEY must be set"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static CONFIG: LazyLock<Config> = LazyLock::new(|| Config::new());
|
104
rust-learning/src/groq.rs
Normal file
104
rust-learning/src/groq.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use types::{
|
||||||
|
ChatCompletionChoice, ChatCompletionMessage, ChatCompletionRequest, ChatCompletionResponse,
|
||||||
|
ChatCompletionUsage, ListModelsResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::config::CONFIG;
|
||||||
|
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub struct Groq {
|
||||||
|
api_key: String,
|
||||||
|
url: String,
|
||||||
|
http_client: reqwest::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Groq {
|
||||||
|
pub fn new(api_key: String) -> Self {
|
||||||
|
Self {
|
||||||
|
api_key,
|
||||||
|
url: "https://api.groq.com/openai/v1".to_string(),
|
||||||
|
http_client: reqwest::Client::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_models(&self) -> Vec<String> {
|
||||||
|
let response = self
|
||||||
|
.http_client
|
||||||
|
.get(format!("{}/models", self.url))
|
||||||
|
.header("Authorization", format!("Bearer {}", self.api_key))
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if response.is_err() {
|
||||||
|
print!("Request Error");
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_models = response.unwrap().json::<ListModelsResponse>().await;
|
||||||
|
|
||||||
|
if response_models.is_err() {
|
||||||
|
print!("Response Parsing Error");
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let model_names = response_models
|
||||||
|
.unwrap()
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.map(|model| model.id.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
model_names
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn chat_completion(&self, model: String, message: String) -> ChatCompletionResponse {
|
||||||
|
let mut error_response = ChatCompletionResponse {
|
||||||
|
model: "Error".to_string(),
|
||||||
|
choices: vec![ChatCompletionChoice {
|
||||||
|
message: ChatCompletionMessage {
|
||||||
|
role: "system".to_string(),
|
||||||
|
content: String::new(),
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
usage: ChatCompletionUsage {
|
||||||
|
total_tokens: 0,
|
||||||
|
total_time: 0.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = ChatCompletionRequest {
|
||||||
|
model,
|
||||||
|
messages: vec![ChatCompletionMessage {
|
||||||
|
role: "user".to_string(),
|
||||||
|
content: message,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.http_client
|
||||||
|
.post(format!("{}/chat/completions", self.url))
|
||||||
|
.header("Authorization", format!("Bearer {}", self.api_key))
|
||||||
|
.json(&request)
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if response.is_err() {
|
||||||
|
error_response.choices[0].message.content = "Request Error".to_string();
|
||||||
|
return error_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_completion = response.unwrap().json::<ChatCompletionResponse>().await;
|
||||||
|
|
||||||
|
if response_completion.is_err() {
|
||||||
|
error_response.choices[0].message.content = "Response Parsing Error".to_string();
|
||||||
|
return error_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
response_completion.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static GROQ_CLIENT: LazyLock<Groq> = LazyLock::new(|| Groq::new(CONFIG.groq_api_key.clone()));
|
41
rust-learning/src/groq/types.rs
Normal file
41
rust-learning/src/groq/types.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct RetrieveModelResponse {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ListModelsResponse {
|
||||||
|
pub data: Vec<RetrieveModelResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ChatCompletionMessage {
|
||||||
|
pub role: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ChatCompletionChoice {
|
||||||
|
pub message: ChatCompletionMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ChatCompletionUsage {
|
||||||
|
pub total_tokens: i32,
|
||||||
|
pub total_time: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ChatCompletionResponse {
|
||||||
|
pub model: String,
|
||||||
|
pub choices: Vec<ChatCompletionChoice>,
|
||||||
|
pub usage: ChatCompletionUsage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ChatCompletionRequest {
|
||||||
|
pub model: String,
|
||||||
|
pub messages: Vec<ChatCompletionMessage>,
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
use eframe::egui;
|
|
||||||
use state::AppState;
|
use state::AppState;
|
||||||
|
|
||||||
pub mod model_response;
|
pub mod model_response;
|
||||||
@ -6,7 +5,7 @@ pub mod model_selection;
|
|||||||
pub mod prompt_input;
|
pub mod prompt_input;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
pub fn main() -> eframe::Result {
|
pub fn main(models_available: Vec<String>) -> eframe::Result {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: eframe::egui::ViewportBuilder::default().with_inner_size([900.0, 600.0]),
|
viewport: eframe::egui::ViewportBuilder::default().with_inner_size([900.0, 600.0]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -15,28 +14,6 @@ pub fn main() -> eframe::Result {
|
|||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"Groq Model Comparison",
|
"Groq Model Comparison",
|
||||||
options,
|
options,
|
||||||
Box::new(|cc| Ok(Box::new(AppState::new(cc)))),
|
Box::new(|cc| Ok(Box::new(AppState::new(cc, models_available)))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for AppState {
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
ui.heading("Groq Model Comparison");
|
|
||||||
ui.label("Compare Groq models with ease!");
|
|
||||||
|
|
||||||
ui.add(model_selection::ModelSelection::new(
|
|
||||||
self,
|
|
||||||
vec!["Model 1".to_string(), "Model 2".to_string()],
|
|
||||||
));
|
|
||||||
|
|
||||||
ui.add(prompt_input::PromptInput::new(self));
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
for model in &self.selected_models {
|
|
||||||
ui.vertical(|ui| ui.add(model));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use eframe::egui::{self, Response, Ui, Widget};
|
use eframe::egui::{self, Response, RichText, Ui, Widget};
|
||||||
|
|
||||||
|
use crate::groq::GROQ_CLIENT;
|
||||||
|
|
||||||
pub struct ModelResponse {
|
pub struct ModelResponse {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub status: i32,
|
pub status: i32,
|
||||||
pub time: i32,
|
pub time: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelResponse {
|
impl ModelResponse {
|
||||||
pub fn new(name: String, message: String, status: i32, time: i32) -> Self {
|
pub fn new(name: String, message: String, status: i32, time: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
message,
|
message,
|
||||||
@ -16,6 +18,13 @@ impl ModelResponse {
|
|||||||
time,
|
time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn chat_completion(&mut self, prompt: String) {
|
||||||
|
let response = GROQ_CLIENT.chat_completion(self.name.clone(), prompt).await;
|
||||||
|
self.message = response.choices[0].message.content.clone();
|
||||||
|
self.status = 200;
|
||||||
|
self.time = response.usage.total_time;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &ModelResponse {
|
impl Widget for &ModelResponse {
|
||||||
@ -25,9 +34,9 @@ impl Widget for &ModelResponse {
|
|||||||
.rounding(4.0)
|
.rounding(4.0)
|
||||||
.fill(egui::Color32::DARK_GRAY)
|
.fill(egui::Color32::DARK_GRAY)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.label(RichText::new(&self.name).strong());
|
||||||
ui.label(&self.name);
|
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Status:");
|
ui.label("Status:");
|
||||||
ui.label(self.status.to_string());
|
ui.label(self.status.to_string());
|
||||||
|
@ -4,15 +4,11 @@ use super::{model_response::ModelResponse, state::AppState};
|
|||||||
|
|
||||||
pub struct ModelSelection<'a> {
|
pub struct ModelSelection<'a> {
|
||||||
app_state: &'a mut AppState,
|
app_state: &'a mut AppState,
|
||||||
pub models_available: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ModelSelection<'a> {
|
impl<'a> ModelSelection<'a> {
|
||||||
pub fn new(app_state: &'a mut AppState, models_available: Vec<String>) -> Self {
|
pub fn new(app_state: &'a mut AppState) -> Self {
|
||||||
Self {
|
Self { app_state }
|
||||||
app_state,
|
|
||||||
models_available,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +18,7 @@ impl<'a> Widget for ModelSelection<'a> {
|
|||||||
egui::ComboBox::from_label("")
|
egui::ComboBox::from_label("")
|
||||||
.selected_text("Models")
|
.selected_text("Models")
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for model in self.models_available {
|
for model in &self.app_state.models_available {
|
||||||
let selected_models = self
|
let selected_models = self
|
||||||
.app_state
|
.app_state
|
||||||
.selected_models
|
.selected_models
|
||||||
@ -31,14 +27,14 @@ impl<'a> Widget for ModelSelection<'a> {
|
|||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
let contained = selected_models.contains(&model);
|
let contained = selected_models.contains(&model);
|
||||||
|
|
||||||
let label = ui.selectable_label(contained, &model);
|
let label = ui.selectable_label(contained, model);
|
||||||
|
|
||||||
if label.clicked() && !contained {
|
if label.clicked() && !contained {
|
||||||
self.app_state.selected_models.push(ModelResponse {
|
self.app_state.selected_models.push(ModelResponse {
|
||||||
name: model.clone(),
|
name: model.clone(),
|
||||||
message: "No message".to_string(),
|
message: "No message".to_string(),
|
||||||
status: 0,
|
status: 0,
|
||||||
time: 0,
|
time: 0.0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,16 +14,19 @@ impl<'a> PromptInput<'a> {
|
|||||||
|
|
||||||
impl<'a> Widget for PromptInput<'a> {
|
impl<'a> Widget for PromptInput<'a> {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
ui.horizontal(|ui| {
|
let label = ui.label("Prompt:");
|
||||||
let label = ui.label("Prompt:");
|
ui.text_edit_multiline(&mut self.app_state.prompt_input)
|
||||||
ui.text_edit_singleline(&mut self.app_state.prompt_input)
|
.labelled_by(label.id);
|
||||||
.labelled_by(label.id);
|
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Submit").clicked() {
|
if ui.button("Submit").clicked() {
|
||||||
|
futures::executor::block_on(self.app_state.handle_submission());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Clear").clicked() {
|
||||||
self.app_state.prompt_input.clear();
|
self.app_state.prompt_input.clear();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.response
|
||||||
ui.label(&self.app_state.prompt_input)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,48 @@
|
|||||||
use eframe::CreationContext;
|
use eframe::{egui, CreationContext};
|
||||||
|
|
||||||
use super::model_response::ModelResponse;
|
use super::{model_response::ModelResponse, model_selection, prompt_input};
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub selected_models: Vec<ModelResponse>,
|
pub selected_models: Vec<ModelResponse>,
|
||||||
pub prompt_input: String,
|
pub prompt_input: String,
|
||||||
|
pub models_available: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(_cc: &CreationContext<'_>) -> Self {
|
pub fn new(_cc: &CreationContext<'_>, models_available: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
selected_models: vec![],
|
selected_models: vec![],
|
||||||
prompt_input: String::new(),
|
prompt_input: String::new(),
|
||||||
|
models_available,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_submission(&mut self) {
|
||||||
|
let mut completions = vec![];
|
||||||
|
|
||||||
|
for model in &mut self.selected_models {
|
||||||
|
completions.push(model.chat_completion(self.prompt_input.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
futures::future::join_all(completions).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for AppState {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.heading("Groq Model Comparison");
|
||||||
|
ui.label("Compare Groq models with ease!");
|
||||||
|
|
||||||
|
ui.add(model_selection::ModelSelection::new(self));
|
||||||
|
|
||||||
|
ui.add(prompt_input::PromptInput::new(self));
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
for model in &self.selected_models {
|
||||||
|
ui.vertical(|ui| ui.add(model));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
pub mod gui;
|
use groq::GROQ_CLIENT;
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
pub mod groq;
|
||||||
gui::main()
|
pub mod gui;
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> eframe::Result {
|
||||||
|
let models = GROQ_CLIENT.list_models().await;
|
||||||
|
|
||||||
|
gui::main(models)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user