Merge branch 'main' into ollama-inline-completions
This commit is contained in:
commit
548feed24f
757 changed files with 24165 additions and 16916 deletions
|
@ -104,7 +104,7 @@ fn register_language_model_providers(
|
|||
cx: &mut Context<LanguageModelRegistry>,
|
||||
) {
|
||||
registry.register_provider(
|
||||
CloudLanguageModelProvider::new(user_store.clone(), client.clone(), cx),
|
||||
CloudLanguageModelProvider::new(user_store, client.clone(), cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
|
|
|
@ -15,11 +15,11 @@ use gpui::{
|
|||
};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCacheConfiguration,
|
||||
LanguageModelCompletionError, LanguageModelId, LanguageModelName, LanguageModelProvider,
|
||||
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
|
||||
LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent, MessageContent,
|
||||
RateLimiter, Role,
|
||||
AuthenticateError, ConfigurationViewTargetAgent, LanguageModel,
|
||||
LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelId,
|
||||
LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,
|
||||
LanguageModelToolResultContent, MessageContent, RateLimiter, Role,
|
||||
};
|
||||
use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason};
|
||||
use schemars::JsonSchema;
|
||||
|
@ -114,7 +114,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.ok();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -133,7 +133,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
|
@ -153,29 +153,14 @@ impl State {
|
|||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let credentials_provider = <dyn CredentialsProvider>::global(cx);
|
||||
let api_url = AllLanguageModelSettings::get_global(cx)
|
||||
.anthropic
|
||||
.api_url
|
||||
.clone();
|
||||
let key = AnthropicLanguageModelProvider::api_key(cx);
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) {
|
||||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?,
|
||||
false,
|
||||
)
|
||||
};
|
||||
let key = key.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.api_key = Some(api_key);
|
||||
this.api_key_from_env = from_env;
|
||||
this.api_key = Some(key.key);
|
||||
this.api_key_from_env = key.from_env;
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
|
@ -184,6 +169,11 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ApiKey {
|
||||
pub key: String,
|
||||
pub from_env: bool,
|
||||
}
|
||||
|
||||
impl AnthropicLanguageModelProvider {
|
||||
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
|
||||
let state = cx.new(|cx| State {
|
||||
|
@ -206,6 +196,33 @@ impl AnthropicLanguageModelProvider {
|
|||
request_limiter: RateLimiter::new(4),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn api_key(cx: &mut App) -> Task<Result<ApiKey>> {
|
||||
let credentials_provider = <dyn CredentialsProvider>::global(cx);
|
||||
let api_url = AllLanguageModelSettings::get_global(cx)
|
||||
.anthropic
|
||||
.api_url
|
||||
.clone();
|
||||
|
||||
if let Ok(key) = std::env::var(ANTHROPIC_API_KEY_VAR) {
|
||||
Task::ready(Ok(ApiKey {
|
||||
key,
|
||||
from_env: true,
|
||||
}))
|
||||
} else {
|
||||
cx.spawn(async move |cx| {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
|
||||
Ok(ApiKey {
|
||||
key: String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?,
|
||||
from_env: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageModelProviderState for AnthropicLanguageModelProvider {
|
||||
|
@ -299,8 +316,13 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
fn configuration_view(
|
||||
&self,
|
||||
target_agent: ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), target_agent, window, cx))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
@ -532,7 +554,7 @@ pub fn into_anthropic(
|
|||
.into_iter()
|
||||
.filter_map(|content| match content {
|
||||
MessageContent::Text(text) => {
|
||||
let text = if text.chars().last().map_or(false, |c| c.is_whitespace()) {
|
||||
let text = if text.chars().last().is_some_and(|c| c.is_whitespace()) {
|
||||
text.trim_end().to_string()
|
||||
} else {
|
||||
text
|
||||
|
@ -611,11 +633,11 @@ pub fn into_anthropic(
|
|||
Role::Assistant => anthropic::Role::Assistant,
|
||||
Role::System => unreachable!("System role should never occur here"),
|
||||
};
|
||||
if let Some(last_message) = new_messages.last_mut() {
|
||||
if last_message.role == anthropic_role {
|
||||
last_message.content.extend(anthropic_message_content);
|
||||
continue;
|
||||
}
|
||||
if let Some(last_message) = new_messages.last_mut()
|
||||
&& last_message.role == anthropic_role
|
||||
{
|
||||
last_message.content.extend(anthropic_message_content);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark the last segment of the message as cached
|
||||
|
@ -791,7 +813,7 @@ impl AnthropicEventMapper {
|
|||
))];
|
||||
}
|
||||
}
|
||||
return vec![];
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
Event::ContentBlockStop { index } => {
|
||||
|
@ -902,12 +924,18 @@ struct ConfigurationView {
|
|||
api_key_editor: Entity<Editor>,
|
||||
state: gpui::Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
target_agent: ConfigurationViewTargetAgent,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
const PLACEHOLDER_TEXT: &'static str = "sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
|
||||
fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
fn new(
|
||||
state: gpui::Entity<State>,
|
||||
target_agent: ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
cx.observe(&state, |_, _, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
|
@ -939,6 +967,7 @@ impl ConfigurationView {
|
|||
}),
|
||||
state,
|
||||
load_credentials_task,
|
||||
target_agent,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1012,7 +1041,10 @@ impl Render for ConfigurationView {
|
|||
v_flex()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::save_api_key))
|
||||
.child(Label::new("To use Zed's agent with Anthropic, you need to add an API key. Follow these steps:"))
|
||||
.child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match self.target_agent {
|
||||
ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Anthropic",
|
||||
ConfigurationViewTargetAgent::Other(agent) => agent,
|
||||
})))
|
||||
.child(
|
||||
List::new()
|
||||
.child(
|
||||
|
@ -1023,7 +1055,7 @@ impl Render for ConfigurationView {
|
|||
)
|
||||
)
|
||||
.child(
|
||||
InstructionListItem::text_only("Paste your API key below and hit enter to start using the assistant")
|
||||
InstructionListItem::text_only("Paste your API key below and hit enter to start using the agent")
|
||||
)
|
||||
)
|
||||
.child(
|
||||
|
|
|
@ -150,7 +150,7 @@ impl State {
|
|||
let credentials_provider = <dyn CredentialsProvider>::global(cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(AMAZON_AWS_URL, &cx)
|
||||
.delete_credentials(AMAZON_AWS_URL, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -174,7 +174,7 @@ impl State {
|
|||
AMAZON_AWS_URL,
|
||||
"Bearer",
|
||||
&serde_json::to_vec(&credentials)?,
|
||||
&cx,
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -206,7 +206,7 @@ impl State {
|
|||
(credentials, true)
|
||||
} else {
|
||||
let (_, credentials) = credentials_provider
|
||||
.read_credentials(AMAZON_AWS_URL, &cx)
|
||||
.read_credentials(AMAZON_AWS_URL, cx)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -348,7 +348,12 @@ impl LanguageModelProvider for BedrockLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
@ -407,10 +412,10 @@ impl BedrockModel {
|
|||
.region(Region::new(region))
|
||||
.timeout_config(TimeoutConfig::disabled());
|
||||
|
||||
if let Some(endpoint_url) = endpoint {
|
||||
if !endpoint_url.is_empty() {
|
||||
config_builder = config_builder.endpoint_url(endpoint_url);
|
||||
}
|
||||
if let Some(endpoint_url) = endpoint
|
||||
&& !endpoint_url.is_empty()
|
||||
{
|
||||
config_builder = config_builder.endpoint_url(endpoint_url);
|
||||
}
|
||||
|
||||
match auth_method {
|
||||
|
@ -460,7 +465,7 @@ impl BedrockModel {
|
|||
Result<BoxStream<'static, Result<BedrockStreamingResponse, BedrockError>>>,
|
||||
> {
|
||||
let Ok(runtime_client) = self
|
||||
.get_or_init_client(&cx)
|
||||
.get_or_init_client(cx)
|
||||
.cloned()
|
||||
.context("Bedrock client not initialized")
|
||||
else {
|
||||
|
@ -723,11 +728,11 @@ pub fn into_bedrock(
|
|||
Role::Assistant => bedrock::BedrockRole::Assistant,
|
||||
Role::System => unreachable!("System role should never occur here"),
|
||||
};
|
||||
if let Some(last_message) = new_messages.last_mut() {
|
||||
if last_message.role == bedrock_role {
|
||||
last_message.content.extend(bedrock_message_content);
|
||||
continue;
|
||||
}
|
||||
if let Some(last_message) = new_messages.last_mut()
|
||||
&& last_message.role == bedrock_role
|
||||
{
|
||||
last_message.content.extend(bedrock_message_content);
|
||||
continue;
|
||||
}
|
||||
new_messages.push(
|
||||
BedrockMessage::builder()
|
||||
|
@ -912,7 +917,7 @@ pub fn map_to_language_model_completion_events(
|
|||
Some(ContentBlockDelta::ReasoningContent(thinking)) => match thinking {
|
||||
ReasoningContentBlockDelta::Text(thoughts) => {
|
||||
Some(Ok(LanguageModelCompletionEvent::Thinking {
|
||||
text: thoughts.clone(),
|
||||
text: thoughts,
|
||||
signature: None,
|
||||
}))
|
||||
}
|
||||
|
@ -963,7 +968,7 @@ pub fn map_to_language_model_completion_events(
|
|||
id: tool_use.id.into(),
|
||||
name: tool_use.name.into(),
|
||||
is_input_complete: true,
|
||||
raw_input: tool_use.input_json.clone(),
|
||||
raw_input: tool_use.input_json,
|
||||
input,
|
||||
},
|
||||
))
|
||||
|
@ -1081,21 +1086,18 @@ impl ConfigurationView {
|
|||
.access_key_id_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string();
|
||||
let secret_access_key = self
|
||||
.secret_access_key_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string();
|
||||
let session_token = self
|
||||
.session_token_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string();
|
||||
let session_token = if session_token.is_empty() {
|
||||
|
@ -1103,13 +1105,7 @@ impl ConfigurationView {
|
|||
} else {
|
||||
Some(session_token)
|
||||
};
|
||||
let region = self
|
||||
.region_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string();
|
||||
let region = self.region_editor.read(cx).text(cx).trim().to_string();
|
||||
let region = if region.is_empty() {
|
||||
"us-east-1".to_string()
|
||||
} else {
|
||||
|
|
|
@ -140,7 +140,7 @@ impl State {
|
|||
Self {
|
||||
client: client.clone(),
|
||||
llm_api_token: LlmApiToken::default(),
|
||||
user_store: user_store.clone(),
|
||||
user_store,
|
||||
status,
|
||||
accept_terms_of_service_task: None,
|
||||
models: Vec::new(),
|
||||
|
@ -193,7 +193,7 @@ impl State {
|
|||
fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
cx.spawn(async move |state, cx| {
|
||||
client.sign_in_with_optional_connect(true, &cx).await?;
|
||||
client.sign_in_with_optional_connect(true, cx).await?;
|
||||
state.update(cx, |_, cx| cx.notify())
|
||||
})
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ impl State {
|
|||
if response.status().is_success() {
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
return Ok(serde_json::from_str(&body)?);
|
||||
Ok(serde_json::from_str(&body)?)
|
||||
} else {
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
|
@ -307,7 +307,7 @@ impl CloudLanguageModelProvider {
|
|||
|
||||
Self {
|
||||
client,
|
||||
state: state.clone(),
|
||||
state,
|
||||
_maintain_client_status: maintain_client_status,
|
||||
}
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ impl CloudLanguageModelProvider {
|
|||
Arc::new(CloudLanguageModel {
|
||||
id: LanguageModelId(SharedString::from(model.id.0.clone())),
|
||||
model,
|
||||
llm_api_token: llm_api_token.clone(),
|
||||
llm_api_token,
|
||||
client: self.client.clone(),
|
||||
request_limiter: RateLimiter::new(4),
|
||||
})
|
||||
|
@ -391,7 +391,12 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
|
|||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
_: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|_| ConfigurationView::new(self.state.clone()))
|
||||
.into()
|
||||
}
|
||||
|
@ -592,15 +597,13 @@ impl CloudLanguageModel {
|
|||
.headers()
|
||||
.get(SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME)
|
||||
.and_then(|resource| resource.to_str().ok())
|
||||
{
|
||||
if let Some(plan) = response
|
||||
&& let Some(plan) = response
|
||||
.headers()
|
||||
.get(CURRENT_PLAN_HEADER_NAME)
|
||||
.and_then(|plan| plan.to_str().ok())
|
||||
.and_then(|plan| cloud_llm_client::Plan::from_str(plan).ok())
|
||||
{
|
||||
return Err(anyhow!(ModelRequestLimitReachedError { plan }));
|
||||
}
|
||||
{
|
||||
return Err(anyhow!(ModelRequestLimitReachedError { plan }));
|
||||
}
|
||||
} else if status == StatusCode::PAYMENT_REQUIRED {
|
||||
return Err(anyhow!(PaymentRequiredError));
|
||||
|
@ -657,29 +660,29 @@ where
|
|||
|
||||
impl From<ApiError> for LanguageModelCompletionError {
|
||||
fn from(error: ApiError) -> Self {
|
||||
if let Ok(cloud_error) = serde_json::from_str::<CloudApiError>(&error.body) {
|
||||
if cloud_error.code.starts_with("upstream_http_") {
|
||||
let status = if let Some(status) = cloud_error.upstream_status {
|
||||
status
|
||||
} else if cloud_error.code.ends_with("_error") {
|
||||
error.status
|
||||
} else {
|
||||
// If there's a status code in the code string (e.g. "upstream_http_429")
|
||||
// then use that; otherwise, see if the JSON contains a status code.
|
||||
cloud_error
|
||||
.code
|
||||
.strip_prefix("upstream_http_")
|
||||
.and_then(|code_str| code_str.parse::<u16>().ok())
|
||||
.and_then(|code| StatusCode::from_u16(code).ok())
|
||||
.unwrap_or(error.status)
|
||||
};
|
||||
if let Ok(cloud_error) = serde_json::from_str::<CloudApiError>(&error.body)
|
||||
&& cloud_error.code.starts_with("upstream_http_")
|
||||
{
|
||||
let status = if let Some(status) = cloud_error.upstream_status {
|
||||
status
|
||||
} else if cloud_error.code.ends_with("_error") {
|
||||
error.status
|
||||
} else {
|
||||
// If there's a status code in the code string (e.g. "upstream_http_429")
|
||||
// then use that; otherwise, see if the JSON contains a status code.
|
||||
cloud_error
|
||||
.code
|
||||
.strip_prefix("upstream_http_")
|
||||
.and_then(|code_str| code_str.parse::<u16>().ok())
|
||||
.and_then(|code| StatusCode::from_u16(code).ok())
|
||||
.unwrap_or(error.status)
|
||||
};
|
||||
|
||||
return LanguageModelCompletionError::UpstreamProviderError {
|
||||
message: cloud_error.message,
|
||||
status,
|
||||
retry_after: cloud_error.retry_after.map(Duration::from_secs_f64),
|
||||
};
|
||||
}
|
||||
return LanguageModelCompletionError::UpstreamProviderError {
|
||||
message: cloud_error.message,
|
||||
status,
|
||||
retry_after: cloud_error.retry_after.map(Duration::from_secs_f64),
|
||||
};
|
||||
}
|
||||
|
||||
let retry_after = None;
|
||||
|
|
|
@ -176,7 +176,12 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider {
|
|||
Task::ready(Err(err.into()))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
_: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
let state = self.state.clone();
|
||||
cx.new(|cx| ConfigurationView::new(state, cx)).into()
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -96,7 +96,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await?;
|
||||
this.update(cx, |this, cx| {
|
||||
this.api_key = Some(api_key);
|
||||
|
@ -120,7 +120,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -229,7 +229,12 @@ impl LanguageModelProvider for DeepSeekLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ use gpui::{
|
|||
};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelToolChoice, LanguageModelToolSchemaFormat, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MessageContent, StopReason,
|
||||
AuthenticateError, ConfigurationViewTargetAgent, LanguageModelCompletionError,
|
||||
LanguageModelCompletionEvent, LanguageModelToolChoice, LanguageModelToolSchemaFormat,
|
||||
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, StopReason,
|
||||
};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider,
|
||||
|
@ -37,6 +37,8 @@ use util::ResultExt;
|
|||
use crate::AllLanguageModelSettings;
|
||||
use crate::ui::InstructionListItem;
|
||||
|
||||
use super::anthropic::ApiKey;
|
||||
|
||||
const PROVIDER_ID: LanguageModelProviderId = language_model::GOOGLE_PROVIDER_ID;
|
||||
const PROVIDER_NAME: LanguageModelProviderName = language_model::GOOGLE_PROVIDER_NAME;
|
||||
|
||||
|
@ -110,7 +112,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -129,7 +131,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await?;
|
||||
this.update(cx, |this, cx| {
|
||||
this.api_key = Some(api_key);
|
||||
|
@ -156,7 +158,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -198,6 +200,33 @@ impl GoogleLanguageModelProvider {
|
|||
request_limiter: RateLimiter::new(4),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn api_key(cx: &mut App) -> Task<Result<ApiKey>> {
|
||||
let credentials_provider = <dyn CredentialsProvider>::global(cx);
|
||||
let api_url = AllLanguageModelSettings::get_global(cx)
|
||||
.google
|
||||
.api_url
|
||||
.clone();
|
||||
|
||||
if let Ok(key) = std::env::var(GEMINI_API_KEY_VAR) {
|
||||
Task::ready(Ok(ApiKey {
|
||||
key,
|
||||
from_env: true,
|
||||
}))
|
||||
} else {
|
||||
cx.spawn(async move |cx| {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
|
||||
Ok(ApiKey {
|
||||
key: String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?,
|
||||
from_env: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageModelProviderState for GoogleLanguageModelProvider {
|
||||
|
@ -277,8 +306,13 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
fn configuration_view(
|
||||
&self,
|
||||
target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), target_agent, window, cx))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
@ -382,7 +416,7 @@ impl LanguageModel for GoogleLanguageModel {
|
|||
cx: &App,
|
||||
) -> BoxFuture<'static, Result<u64>> {
|
||||
let model_id = self.model.request_id().to_string();
|
||||
let request = into_google(request, model_id.clone(), self.model.mode());
|
||||
let request = into_google(request, model_id, self.model.mode());
|
||||
let http_client = self.http_client.clone();
|
||||
let api_key = self.state.read(cx).api_key.clone();
|
||||
|
||||
|
@ -525,7 +559,7 @@ pub fn into_google(
|
|||
let system_instructions = if request
|
||||
.messages
|
||||
.first()
|
||||
.map_or(false, |msg| matches!(msg.role, Role::System))
|
||||
.is_some_and(|msg| matches!(msg.role, Role::System))
|
||||
{
|
||||
let message = request.messages.remove(0);
|
||||
Some(SystemInstruction {
|
||||
|
@ -572,7 +606,7 @@ pub fn into_google(
|
|||
top_k: None,
|
||||
}),
|
||||
safety_settings: None,
|
||||
tools: (request.tools.len() > 0).then(|| {
|
||||
tools: (!request.tools.is_empty()).then(|| {
|
||||
vec![google_ai::Tool {
|
||||
function_declarations: request
|
||||
.tools
|
||||
|
@ -771,11 +805,17 @@ fn convert_usage(usage: &UsageMetadata) -> language_model::TokenUsage {
|
|||
struct ConfigurationView {
|
||||
api_key_editor: Entity<Editor>,
|
||||
state: gpui::Entity<State>,
|
||||
target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
fn new(
|
||||
state: gpui::Entity<State>,
|
||||
target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
cx.observe(&state, |_, _, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
|
@ -805,6 +845,7 @@ impl ConfigurationView {
|
|||
editor.set_placeholder_text("AIzaSy...", cx);
|
||||
editor
|
||||
}),
|
||||
target_agent,
|
||||
state,
|
||||
load_credentials_task,
|
||||
}
|
||||
|
@ -880,7 +921,10 @@ impl Render for ConfigurationView {
|
|||
v_flex()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::save_api_key))
|
||||
.child(Label::new("To use Zed's agent with Google AI, you need to add an API key. Follow these steps:"))
|
||||
.child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match self.target_agent {
|
||||
ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Google AI",
|
||||
ConfigurationViewTargetAgent::Other(agent) => agent,
|
||||
})))
|
||||
.child(
|
||||
List::new()
|
||||
.child(InstructionListItem::new(
|
||||
|
|
|
@ -210,7 +210,7 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider {
|
|||
.map(|model| {
|
||||
Arc::new(LmStudioLanguageModel {
|
||||
id: LanguageModelId::from(model.name.clone()),
|
||||
model: model.clone(),
|
||||
model,
|
||||
http_client: self.http_client.clone(),
|
||||
request_limiter: RateLimiter::new(4),
|
||||
}) as Arc<dyn LanguageModel>
|
||||
|
@ -226,7 +226,12 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, _window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
_window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
let state = self.state.clone();
|
||||
cx.new(|cx| ConfigurationView::new(state, cx)).into()
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -95,7 +95,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await?;
|
||||
this.update(cx, |this, cx| {
|
||||
this.api_key = Some(api_key);
|
||||
|
@ -119,7 +119,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -243,7 +243,12 @@ impl LanguageModelProvider for MistralLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
|
|||
.map(|model| {
|
||||
Arc::new(OllamaLanguageModel {
|
||||
id: LanguageModelId::from(model.name.clone()),
|
||||
model: model.clone(),
|
||||
model,
|
||||
http_client: self.http_client.clone(),
|
||||
request_limiter: RateLimiter::new(4),
|
||||
}) as Arc<dyn LanguageModel>
|
||||
|
@ -283,7 +283,12 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
let state = self.state.clone();
|
||||
cx.new(|cx| ConfigurationView::new(state, window, cx))
|
||||
.into()
|
||||
|
|
|
@ -75,7 +75,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -94,7 +94,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -119,7 +119,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -233,7 +233,12 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
@ -399,7 +404,7 @@ pub fn into_open_ai(
|
|||
match content {
|
||||
MessageContent::Text(text) | MessageContent::Thinking { text, .. } => {
|
||||
add_message_content_part(
|
||||
open_ai::MessagePart::Text { text: text },
|
||||
open_ai::MessagePart::Text { text },
|
||||
message.role,
|
||||
&mut messages,
|
||||
)
|
||||
|
|
|
@ -38,6 +38,27 @@ pub struct AvailableModel {
|
|||
pub max_tokens: u64,
|
||||
pub max_output_tokens: Option<u64>,
|
||||
pub max_completion_tokens: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub capabilities: ModelCapabilities,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ModelCapabilities {
|
||||
pub tools: bool,
|
||||
pub images: bool,
|
||||
pub parallel_tool_calls: bool,
|
||||
pub prompt_cache_key: bool,
|
||||
}
|
||||
|
||||
impl Default for ModelCapabilities {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tools: true,
|
||||
images: false,
|
||||
parallel_tool_calls: false,
|
||||
prompt_cache_key: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenAiCompatibleLanguageModelProvider {
|
||||
|
@ -66,7 +87,7 @@ impl State {
|
|||
let api_url = self.settings.api_url.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -82,7 +103,7 @@ impl State {
|
|||
let api_url = self.settings.api_url.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -105,7 +126,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -222,7 +243,12 @@ impl LanguageModelProvider for OpenAiCompatibleLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
@ -293,17 +319,17 @@ impl LanguageModel for OpenAiCompatibleLanguageModel {
|
|||
}
|
||||
|
||||
fn supports_tools(&self) -> bool {
|
||||
true
|
||||
self.model.capabilities.tools
|
||||
}
|
||||
|
||||
fn supports_images(&self) -> bool {
|
||||
false
|
||||
self.model.capabilities.images
|
||||
}
|
||||
|
||||
fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
|
||||
match choice {
|
||||
LanguageModelToolChoice::Auto => true,
|
||||
LanguageModelToolChoice::Any => true,
|
||||
LanguageModelToolChoice::Auto => self.model.capabilities.tools,
|
||||
LanguageModelToolChoice::Any => self.model.capabilities.tools,
|
||||
LanguageModelToolChoice::None => true,
|
||||
}
|
||||
}
|
||||
|
@ -355,13 +381,11 @@ impl LanguageModel for OpenAiCompatibleLanguageModel {
|
|||
LanguageModelCompletionError,
|
||||
>,
|
||||
> {
|
||||
let supports_parallel_tool_call = true;
|
||||
let supports_prompt_cache_key = false;
|
||||
let request = into_open_ai(
|
||||
request,
|
||||
&self.model.name,
|
||||
supports_parallel_tool_call,
|
||||
supports_prompt_cache_key,
|
||||
self.model.capabilities.parallel_tool_calls,
|
||||
self.model.capabilities.prompt_cache_key,
|
||||
self.max_output_tokens(),
|
||||
None,
|
||||
);
|
||||
|
|
|
@ -112,7 +112,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -131,7 +131,7 @@ impl State {
|
|||
.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -157,7 +157,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -306,7 +306,12 @@ impl LanguageModelProvider for OpenRouterLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ impl State {
|
|||
};
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -92,7 +92,7 @@ impl State {
|
|||
};
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -119,7 +119,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -230,7 +230,12 @@ impl LanguageModelProvider for VercelLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ impl State {
|
|||
};
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.delete_credentials(&api_url, &cx)
|
||||
.delete_credentials(&api_url, cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -92,7 +92,7 @@ impl State {
|
|||
};
|
||||
cx.spawn(async move |this, cx| {
|
||||
credentials_provider
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx)
|
||||
.write_credentials(&api_url, "Bearer", api_key.as_bytes(), cx)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -119,7 +119,7 @@ impl State {
|
|||
(api_key, true)
|
||||
} else {
|
||||
let (_, api_key) = credentials_provider
|
||||
.read_credentials(&api_url, &cx)
|
||||
.read_credentials(&api_url, cx)
|
||||
.await?
|
||||
.ok_or(AuthenticateError::CredentialsNotFound)?;
|
||||
(
|
||||
|
@ -230,7 +230,12 @@ impl LanguageModelProvider for XAiLanguageModelProvider {
|
|||
self.state.update(cx, |state, cx| state.authenticate(cx))
|
||||
}
|
||||
|
||||
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
|
||||
fn configuration_view(
|
||||
&self,
|
||||
_target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyView {
|
||||
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ impl IntoElement for InstructionListItem {
|
|||
let item_content = if let (Some(button_label), Some(button_link)) =
|
||||
(self.button_label, self.button_link)
|
||||
{
|
||||
let link = button_link.clone();
|
||||
let link = button_link;
|
||||
let unique_id = SharedString::from(format!("{}-button", self.label));
|
||||
|
||||
h_flex()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue