Add a /perplexity slash command in an extension (#16438)
Release Notes: - N/A
This commit is contained in:
parent
b9176fe4bb
commit
43e13df9f3
6 changed files with 196 additions and 0 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -7607,6 +7607,14 @@ dependencies = [
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "perplexity"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"zed_extension_api 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.7.11"
|
version = "2.7.11"
|
||||||
|
|
|
@ -146,6 +146,7 @@ members = [
|
||||||
"extensions/lua",
|
"extensions/lua",
|
||||||
"extensions/ocaml",
|
"extensions/ocaml",
|
||||||
"extensions/php",
|
"extensions/php",
|
||||||
|
"extensions/perplexity",
|
||||||
"extensions/prisma",
|
"extensions/prisma",
|
||||||
"extensions/purescript",
|
"extensions/purescript",
|
||||||
"extensions/ruff",
|
"extensions/ruff",
|
||||||
|
|
16
extensions/perplexity/Cargo.toml
Normal file
16
extensions/perplexity/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "perplexity"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/perplexity.rs"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = "1"
|
||||||
|
zed_extension_api = { path = "../../crates/extension_api" }
|
1
extensions/perplexity/LICENSE-APACHE
Symbolic link
1
extensions/perplexity/LICENSE-APACHE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE-APACHE
|
12
extensions/perplexity/extension.toml
Normal file
12
extensions/perplexity/extension.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
id = "perplexity"
|
||||||
|
name = "Perplexity"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Ask questions to Perplexity AI directly from Zed"
|
||||||
|
authors = ["Zed Industries <support@zed.dev>"]
|
||||||
|
repository = "https://github.com/zed-industries/zed-perplexity"
|
||||||
|
schema_version = 1
|
||||||
|
|
||||||
|
[slash_commands.perplexity]
|
||||||
|
description = "Ask a question to Perplexity AI"
|
||||||
|
requires_argument = true
|
||||||
|
tooltip_text = "Ask Perplexity"
|
158
extensions/perplexity/src/perplexity.rs
Normal file
158
extensions/perplexity/src/perplexity.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use zed::{
|
||||||
|
http_client::HttpMethod,
|
||||||
|
http_client::HttpRequest,
|
||||||
|
serde_json::{self, json},
|
||||||
|
};
|
||||||
|
use zed_extension_api::{self as zed, http_client::RedirectPolicy, Result};
|
||||||
|
|
||||||
|
struct Perplexity;
|
||||||
|
|
||||||
|
impl zed::Extension for Perplexity {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_slash_command(
|
||||||
|
&self,
|
||||||
|
command: zed::SlashCommand,
|
||||||
|
argument: Vec<String>,
|
||||||
|
worktree: Option<&zed::Worktree>,
|
||||||
|
) -> zed::Result<zed::SlashCommandOutput> {
|
||||||
|
// Check if the command is 'perplexity'
|
||||||
|
if command.name != "perplexity" {
|
||||||
|
return Err("Invalid command. Expected 'perplexity'.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let worktree = worktree.ok_or("Worktree is required")?;
|
||||||
|
// Join arguments with space as the query
|
||||||
|
let query = argument.join(" ");
|
||||||
|
if query.is_empty() {
|
||||||
|
return Ok(zed::SlashCommandOutput {
|
||||||
|
text: "Error: Query not provided. Please enter a question or topic.".to_string(),
|
||||||
|
sections: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the API key from the environment
|
||||||
|
let env_vars = worktree.shell_env();
|
||||||
|
let api_key = env_vars
|
||||||
|
.iter()
|
||||||
|
.find(|(key, _)| key == "PERPLEXITY_API_KEY")
|
||||||
|
.map(|(_, value)| value.clone())
|
||||||
|
.ok_or("PERPLEXITY_API_KEY not found in environment")?;
|
||||||
|
|
||||||
|
// Prepare the request
|
||||||
|
let request = HttpRequest {
|
||||||
|
method: HttpMethod::Post,
|
||||||
|
url: "https://api.perplexity.ai/chat/completions".to_string(),
|
||||||
|
headers: vec![
|
||||||
|
("Authorization".to_string(), format!("Bearer {}", api_key)),
|
||||||
|
("Content-Type".to_string(), "application/json".to_string()),
|
||||||
|
],
|
||||||
|
body: Some(
|
||||||
|
serde_json::to_vec(&json!({
|
||||||
|
"model": "llama-3.1-sonar-small-128k-online",
|
||||||
|
"messages": [{"role": "user", "content": query}],
|
||||||
|
"stream": true,
|
||||||
|
}))
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
redirect_policy: RedirectPolicy::FollowAll,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make the HTTP request
|
||||||
|
match zed::http_client::fetch_stream(&request) {
|
||||||
|
Ok(stream) => {
|
||||||
|
let mut full_content = String::new();
|
||||||
|
let mut buffer = String::new();
|
||||||
|
while let Ok(Some(chunk)) = stream.next_chunk() {
|
||||||
|
buffer.push_str(&String::from_utf8_lossy(&chunk));
|
||||||
|
for line in buffer.lines() {
|
||||||
|
if let Some(json) = line.strip_prefix("data: ") {
|
||||||
|
if let Ok(event) = serde_json::from_str::<StreamEvent>(json) {
|
||||||
|
if let Some(choice) = event.choices.first() {
|
||||||
|
full_content.push_str(&choice.delta.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
Ok(zed::SlashCommandOutput {
|
||||||
|
text: full_content,
|
||||||
|
sections: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => Ok(zed::SlashCommandOutput {
|
||||||
|
text: format!("API request failed. Error: {}. API Key: {}", e, api_key),
|
||||||
|
sections: vec![],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_slash_command_argument(
|
||||||
|
&self,
|
||||||
|
_command: zed::SlashCommand,
|
||||||
|
query: Vec<String>,
|
||||||
|
) -> zed::Result<Vec<zed::SlashCommandArgumentCompletion>> {
|
||||||
|
let suggestions = vec!["How do I develop a Zed extension?"];
|
||||||
|
let query = query.join(" ").to_lowercase();
|
||||||
|
|
||||||
|
Ok(suggestions
|
||||||
|
.into_iter()
|
||||||
|
.filter(|suggestion| suggestion.to_lowercase().contains(&query))
|
||||||
|
.map(|suggestion| zed::SlashCommandArgumentCompletion {
|
||||||
|
label: suggestion.to_string(),
|
||||||
|
new_text: suggestion.to_string(),
|
||||||
|
run_command: true,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn language_server_command(
|
||||||
|
&mut self,
|
||||||
|
_language_server_id: &zed_extension_api::LanguageServerId,
|
||||||
|
_worktree: &zed_extension_api::Worktree,
|
||||||
|
) -> Result<zed_extension_api::Command> {
|
||||||
|
Err("Not implemented".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct StreamEvent {
|
||||||
|
id: String,
|
||||||
|
model: String,
|
||||||
|
created: u64,
|
||||||
|
usage: Usage,
|
||||||
|
object: String,
|
||||||
|
choices: Vec<Choice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Usage {
|
||||||
|
prompt_tokens: u32,
|
||||||
|
completion_tokens: u32,
|
||||||
|
total_tokens: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Choice {
|
||||||
|
index: u32,
|
||||||
|
finish_reason: Option<String>,
|
||||||
|
message: Message,
|
||||||
|
delta: Delta,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Message {
|
||||||
|
role: String,
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Delta {
|
||||||
|
role: String,
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
zed::register_extension!(Perplexity);
|
Loading…
Add table
Add a link
Reference in a new issue