Skip to content

Commit e451ce0

Browse files
authored
Add support for groq (#25)
* Add support for groq * Update README to include Groq as a supported provider
1 parent 5d084ba commit e451ce0

File tree

6 files changed

+121
-3
lines changed

6 files changed

+121
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
Features:
1616
- [x] Translation: Translate inputted text to a target language.
1717
- [x] Local LLM: Use Ollama as local LLM (offline mode).
18-
- [x] Providers: OpenAI, Ollama, Gemini.
18+
- [x] Providers: OpenAI, Ollama, Gemini, Groq.
1919
- [x] Hot key: Quickly translate a selected text via shortcut key (Currently is: `Cmd + E`)
2020
- [] Linux support.
2121
- [x] Windows support.

src-tauri/src/providers/base.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::future::Future;
22
use tauri::AppHandle;
33
use tauri_plugin_store::StoreBuilder;
44

5-
use super::{gemini::GeminiProvider, ollama::OllamaProvider, openai::OpenAIProvider};
5+
use super::{gemini::GeminiProvider, groq::GroqProvider, ollama::OllamaProvider, openai::OpenAIProvider};
66

77
pub trait Provider {
88
fn completion(&self, prompt: &str) -> impl Future<Output = Result<String, String>>;
@@ -12,6 +12,7 @@ pub enum ProviderEnum {
1212
OllamaProvider(OllamaProvider),
1313
OpenAIProvider(OpenAIProvider),
1414
GeminiProvider(GeminiProvider),
15+
GroqProvider(GroqProvider),
1516
}
1617

1718
impl Provider for ProviderEnum {
@@ -20,6 +21,7 @@ impl Provider for ProviderEnum {
2021
ProviderEnum::OllamaProvider(provider) => provider.completion(prompt).await,
2122
ProviderEnum::OpenAIProvider(provider) => provider.completion(prompt).await,
2223
ProviderEnum::GeminiProvider(provider) => provider.completion(prompt).await,
24+
ProviderEnum::GroqProvider(provider) => provider.completion(prompt).await,
2325
}
2426
}
2527
}
@@ -57,6 +59,21 @@ pub fn get_provider(app_handler: AppHandle, provider: &str, model: &str) -> Prov
5759

5860
ProviderEnum::GeminiProvider(GeminiProvider::new(Some(api_key), Some(model)))
5961
}
62+
"groq" => {
63+
let store = StoreBuilder::new(&app_handler, "store.bin")
64+
.build()
65+
.expect("Failed to build store");
66+
67+
let api_key_value = store
68+
.get("LLM_API_KEY")
69+
.expect("Failed to get API key from store");
70+
71+
let api_key = api_key_value
72+
.as_str()
73+
.expect("API key is not a valid string");
74+
75+
ProviderEnum::GroqProvider(GroqProvider::new(Some(api_key), Some(model)))
76+
}
6077
_ => panic!("Invalid provider"),
6178
}
6279
}

src-tauri/src/providers/groq.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use crate::providers::base::Provider;
2+
use reqwest::Client;
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Serialize)]
6+
struct GroqChatCompletionRequest<'a> {
7+
model: &'a str,
8+
messages: Vec<GroqMessage<'a>>,
9+
}
10+
11+
#[derive(Serialize)]
12+
struct GroqMessage<'a> {
13+
role: &'a str,
14+
content: &'a str,
15+
}
16+
17+
#[derive(Deserialize, Debug)]
18+
struct GroqChatCompletionResponse {
19+
choices: Vec<Choice>,
20+
}
21+
22+
#[derive(Deserialize, Debug)]
23+
struct Choice {
24+
message: Message,
25+
}
26+
27+
#[derive(Deserialize, Debug)]
28+
struct Message {
29+
content: String,
30+
}
31+
32+
pub struct GroqProvider {
33+
client: Client,
34+
api_key: String,
35+
model: String,
36+
}
37+
38+
impl GroqProvider {
39+
pub fn new(api_key: Option<&str>, model: Option<&str>) -> Self {
40+
Self {
41+
client: Client::new(),
42+
api_key: api_key.unwrap_or("").to_string(),
43+
model: model.unwrap_or("llama-3.1-8b-instant").to_string(),
44+
}
45+
}
46+
}
47+
48+
impl Provider for GroqProvider {
49+
async fn completion(&self, prompt: &str) -> Result<String, String> {
50+
let url = "https://api.groq.com/openai/v1/chat/completions";
51+
52+
let messages = vec![GroqMessage {
53+
role: "user",
54+
content: prompt,
55+
}];
56+
57+
let body = GroqChatCompletionRequest {
58+
model: &self.model,
59+
messages,
60+
};
61+
62+
let res = self
63+
.client
64+
.post(url)
65+
.bearer_auth(&self.api_key)
66+
.json(&body)
67+
.send()
68+
.await;
69+
70+
match res {
71+
Ok(res) => {
72+
if res.status().is_success() {
73+
let response_body = res.json::<GroqChatCompletionResponse>().await;
74+
match response_body {
75+
Ok(data) => {
76+
if let Some(choice) = data.choices.get(0) {
77+
Ok(choice.message.content.clone())
78+
} else {
79+
Err("No content in Groq response".to_string())
80+
}
81+
}
82+
Err(err) => Err(format!("Failed to deserialize Groq response: {}", err)),
83+
}
84+
} else {
85+
let error_text = res.text().await.unwrap_or_else(|e| e.to_string());
86+
Err(format!("Groq API request failed: {}", error_text))
87+
}
88+
}
89+
Err(err) => Err(err.to_string()),
90+
}
91+
}
92+
}

src-tauri/src/providers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod base;
22
pub mod gemini;
3+
pub mod groq;
34
pub mod ollama;
45
pub mod openai;

src/app/sections/settings.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ const Settings = ({
103103
<SelectItem key="gemini">
104104
Gemini
105105
</SelectItem>
106+
<SelectItem key="groq">
107+
Groq
108+
</SelectItem>
106109
</Select>
107110
<Select
108111
label="Model"

src/app/types/settings.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ export const providerMap: Record<string, Provider> = {
1414
gemini: {
1515
name: "gemini",
1616
label: "Gemini",
17-
models: ["gemini-2.0-flash-lite", "gemini-2.5-flash"],
17+
models: ["gemini-pro", "gemini-1.5-flash-latest"],
18+
},
19+
groq: {
20+
name: "groq",
21+
label: "Groq",
22+
models: ["llama-3.1-8b-instant", "gemma2-9b-it"],
1823
},
1924
};
2025

0 commit comments

Comments
 (0)