ui: show tool number (#2071)
This commit is contained in:
@@ -18,7 +18,8 @@ use mcp_core::tool::{Tool, ToolAnnotations};
|
|||||||
super::routes::config_management::remove_extension,
|
super::routes::config_management::remove_extension,
|
||||||
super::routes::config_management::get_extensions,
|
super::routes::config_management::get_extensions,
|
||||||
super::routes::config_management::read_all_config,
|
super::routes::config_management::read_all_config,
|
||||||
super::routes::config_management::providers
|
super::routes::config_management::providers,
|
||||||
|
super::routes::agent::get_tools,
|
||||||
),
|
),
|
||||||
components(schemas(
|
components(schemas(
|
||||||
super::routes::config_management::UpsertConfigQuery,
|
super::routes::config_management::UpsertConfigQuery,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use goose::config::Config;
|
use goose::config::Config;
|
||||||
use goose::{agents::AgentFactory, model::ModelConfig, providers};
|
use goose::{agents::AgentFactory, model::ModelConfig, providers};
|
||||||
|
use mcp_core::Tool;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
@@ -163,11 +164,45 @@ async fn list_providers() -> Json<Vec<ProviderList>> {
|
|||||||
Json(response)
|
Json(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/agent/tools",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Tools retrieved successfully", body = Vec<Tool>),
|
||||||
|
(status = 401, description = "Unauthorized - invalid secret key"),
|
||||||
|
(status = 424, description = "Agent not initialized"),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
async fn get_tools(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> Result<Json<Vec<Tool>>, StatusCode> {
|
||||||
|
let secret_key = headers
|
||||||
|
.get("X-Secret-Key")
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
.ok_or(StatusCode::UNAUTHORIZED)?;
|
||||||
|
|
||||||
|
if secret_key != state.secret_key {
|
||||||
|
return Err(StatusCode::UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut agent = state.agent.write().await;
|
||||||
|
let agent = agent.as_mut().ok_or(StatusCode::PRECONDITION_REQUIRED)?;
|
||||||
|
|
||||||
|
// Since list_tools() now returns Vec<Tool> directly, not a Result
|
||||||
|
let tools = agent.list_tools().await;
|
||||||
|
|
||||||
|
// Return the tools directly
|
||||||
|
Ok(Json(tools))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn routes(state: AppState) -> Router {
|
pub fn routes(state: AppState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/agent/versions", get(get_versions))
|
.route("/agent/versions", get(get_versions))
|
||||||
.route("/agent/providers", get(list_providers))
|
.route("/agent/providers", get(list_providers))
|
||||||
.route("/agent/prompt", post(extend_prompt))
|
.route("/agent/prompt", post(extend_prompt))
|
||||||
|
.route("/agent/tools", get(get_tools))
|
||||||
.route("/agent", post(create_agent))
|
.route("/agent", post(create_agent))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use super::extension::{ExtensionConfig, ExtensionResult};
|
|||||||
use crate::providers::base::Provider;
|
use crate::providers::base::Provider;
|
||||||
use crate::session;
|
use crate::session;
|
||||||
use crate::{message::Message, permission::PermissionConfirmation};
|
use crate::{message::Message, permission::PermissionConfirmation};
|
||||||
use mcp_core::{prompt::Prompt, protocol::GetPromptResult, Content, ToolResult};
|
use mcp_core::{prompt::Prompt, protocol::GetPromptResult, Content, Tool, ToolResult};
|
||||||
|
|
||||||
/// Session configuration for an agent
|
/// Session configuration for an agent
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -43,6 +43,9 @@ pub trait Agent: Send + Sync {
|
|||||||
// TODO this needs to also include status so we can tell if extensions are dropped
|
// TODO this needs to also include status so we can tell if extensions are dropped
|
||||||
async fn list_extensions(&self) -> Vec<String>;
|
async fn list_extensions(&self) -> Vec<String>;
|
||||||
|
|
||||||
|
/// List the tools this agent has access to
|
||||||
|
async fn list_tools(&self) -> Vec<Tool>;
|
||||||
|
|
||||||
/// Pass through a JSON-RPC request to a specific extension
|
/// Pass through a JSON-RPC request to a specific extension
|
||||||
async fn passthrough(&self, extension: &str, request: Value) -> ExtensionResult<Value>;
|
async fn passthrough(&self, extension: &str, request: Value) -> ExtensionResult<Value>;
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ mod types;
|
|||||||
|
|
||||||
pub use agent::{Agent, SessionConfig};
|
pub use agent::{Agent, SessionConfig};
|
||||||
pub use capabilities::Capabilities;
|
pub use capabilities::Capabilities;
|
||||||
pub use extension::ExtensionConfig;
|
pub use extension::{ExtensionConfig, ExtensionResult};
|
||||||
pub use factory::{register_agent, AgentFactory};
|
pub use factory::{register_agent, AgentFactory};
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ impl Agent for ReferenceAgent {
|
|||||||
capabilities.add_extension(extension).await
|
capabilities.add_extension(extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn list_tools(&self) -> Vec<Tool> {
|
||||||
|
let mut capabilities = self.capabilities.lock().await;
|
||||||
|
capabilities.get_prefixed_tools().await.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
async fn remove_extension(&mut self, name: &str) {
|
async fn remove_extension(&mut self, name: &str) {
|
||||||
let mut capabilities = self.capabilities.lock().await;
|
let mut capabilities = self.capabilities.lock().await;
|
||||||
capabilities
|
capabilities
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ impl Agent for SummarizeAgent {
|
|||||||
capabilities.add_extension(extension).await
|
capabilities.add_extension(extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn list_tools(&self) -> Vec<Tool> {
|
||||||
|
let mut capabilities = self.capabilities.lock().await;
|
||||||
|
capabilities.get_prefixed_tools().await.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
async fn remove_extension(&mut self, name: &str) {
|
async fn remove_extension(&mut self, name: &str) {
|
||||||
let mut capabilities = self.capabilities.lock().await;
|
let mut capabilities = self.capabilities.lock().await;
|
||||||
capabilities
|
capabilities
|
||||||
|
|||||||
@@ -178,6 +178,11 @@ impl Agent for TruncateAgent {
|
|||||||
capabilities.add_extension(extension).await
|
capabilities.add_extension(extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn list_tools(&self) -> Vec<Tool> {
|
||||||
|
let mut capabilities = self.capabilities.lock().await;
|
||||||
|
capabilities.get_prefixed_tools().await.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
async fn remove_extension(&mut self, name: &str) {
|
async fn remove_extension(&mut self, name: &str) {
|
||||||
let mut capabilities = self.capabilities.lock().await;
|
let mut capabilities = self.capabilities.lock().await;
|
||||||
capabilities
|
capabilities
|
||||||
|
|||||||
@@ -13,6 +13,38 @@
|
|||||||
"version": "1.0.17"
|
"version": "1.0.17"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/agent/tools": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::agent"
|
||||||
|
],
|
||||||
|
"operationId": "get_tools",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Tools retrieved successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Tool"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized - invalid secret key"
|
||||||
|
},
|
||||||
|
"424": {
|
||||||
|
"description": "Agent not initialized"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/config": {
|
"/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
|
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
|
||||||
import type { ReadAllConfigData, ReadAllConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse } from './types.gen';
|
import type { GetToolsData, GetToolsResponse, ReadAllConfigData, ReadAllConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse } from './types.gen';
|
||||||
import { client as _heyApiClient } from './client.gen';
|
import { client as _heyApiClient } from './client.gen';
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
||||||
@@ -18,6 +18,13 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
|
|||||||
meta?: Record<string, unknown>;
|
meta?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTools = <ThrowOnError extends boolean = false>(options?: Options<GetToolsData, ThrowOnError>) => {
|
||||||
|
return (options?.client ?? _heyApiClient).get<GetToolsResponse, unknown, ThrowOnError>({
|
||||||
|
url: '/agent/tools',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const readAllConfig = <ThrowOnError extends boolean = false>(options?: Options<ReadAllConfigData, ThrowOnError>) => {
|
export const readAllConfig = <ThrowOnError extends boolean = false>(options?: Options<ReadAllConfigData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).get<ReadAllConfigResponse, unknown, ThrowOnError>({
|
return (options?.client ?? _heyApiClient).get<ReadAllConfigResponse, unknown, ThrowOnError>({
|
||||||
url: '/config',
|
url: '/config',
|
||||||
|
|||||||
@@ -210,6 +210,37 @@ export type UpsertConfigQuery = {
|
|||||||
value: unknown;
|
value: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetToolsData = {
|
||||||
|
body?: never;
|
||||||
|
path?: never;
|
||||||
|
query?: never;
|
||||||
|
url: '/agent/tools';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetToolsErrors = {
|
||||||
|
/**
|
||||||
|
* Unauthorized - invalid secret key
|
||||||
|
*/
|
||||||
|
401: unknown;
|
||||||
|
/**
|
||||||
|
* Agent not initialized
|
||||||
|
*/
|
||||||
|
424: unknown;
|
||||||
|
/**
|
||||||
|
* Internal server error
|
||||||
|
*/
|
||||||
|
500: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetToolsResponses = {
|
||||||
|
/**
|
||||||
|
* Tools retrieved successfully
|
||||||
|
*/
|
||||||
|
200: Array<Tool>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetToolsResponse = GetToolsResponses[keyof GetToolsResponses];
|
||||||
|
|
||||||
export type ReadAllConfigData = {
|
export type ReadAllConfigData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { View } from '../App';
|
|||||||
import { settingsV2Enabled } from '../flags';
|
import { settingsV2Enabled } from '../flags';
|
||||||
import { BottomMenuModeSelection } from './BottomMenuModeSelection';
|
import { BottomMenuModeSelection } from './BottomMenuModeSelection';
|
||||||
import ModelsBottomBar from './settings_v2/models/bottom_bar/ModelsBottomBar';
|
import ModelsBottomBar from './settings_v2/models/bottom_bar/ModelsBottomBar';
|
||||||
|
import ToolCount from './ToolCount';
|
||||||
|
|
||||||
export default function BottomMenu({
|
export default function BottomMenu({
|
||||||
hasMessages,
|
hasMessages,
|
||||||
@@ -78,77 +79,82 @@ export default function BottomMenu({
|
|||||||
{/* Goose Mode Selector Dropdown */}
|
{/* Goose Mode Selector Dropdown */}
|
||||||
<BottomMenuModeSelection />
|
<BottomMenuModeSelection />
|
||||||
|
|
||||||
{/* Model Selector Dropdown */}
|
{/* Right-side section with ToolCount and Model Selector together */}
|
||||||
{settingsV2Enabled ? (
|
<div className="flex items-center mr-4 space-x-1">
|
||||||
<ModelsBottomBar dropdownRef={dropdownRef} setView={setView} />
|
{/* Tool count */}
|
||||||
) : (
|
<ToolCount />
|
||||||
<div className="relative flex items-center ml-auto mr-4" ref={dropdownRef}>
|
{/* Model Selector Dropdown */}
|
||||||
<div
|
{settingsV2Enabled ? (
|
||||||
className="flex items-center cursor-pointer"
|
<ModelsBottomBar dropdownRef={dropdownRef} setView={setView} />
|
||||||
onClick={() => setIsModelMenuOpen(!isModelMenuOpen)}
|
) : (
|
||||||
>
|
<div className="relative flex items-center ml-0 mr-4" ref={dropdownRef}>
|
||||||
<span>{(currentModel?.alias ?? currentModel?.name) || 'Select Model'}</span>
|
<div
|
||||||
{isModelMenuOpen ? (
|
className="flex items-center cursor-pointer"
|
||||||
<ChevronDown className="w-4 h-4 ml-1" />
|
onClick={() => setIsModelMenuOpen(!isModelMenuOpen)}
|
||||||
) : (
|
>
|
||||||
<ChevronUp className="w-4 h-4 ml-1" />
|
<span>{(currentModel?.alias ?? currentModel?.name) || 'Select Model'}</span>
|
||||||
)}
|
{isModelMenuOpen ? (
|
||||||
</div>
|
<ChevronDown className="w-4 h-4 ml-1" />
|
||||||
|
) : (
|
||||||
|
<ChevronUp className="w-4 h-4 ml-1" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Dropdown Menu */}
|
{/* Dropdown Menu */}
|
||||||
{isModelMenuOpen && (
|
{isModelMenuOpen && (
|
||||||
<div className="absolute bottom-[24px] right-0 w-[300px] bg-bgApp rounded-lg border border-borderSubtle">
|
<div className="absolute bottom-[24px] right-0 w-[300px] bg-bgApp rounded-lg border border-borderSubtle">
|
||||||
<div className="">
|
<div className="">
|
||||||
<ModelRadioList
|
<ModelRadioList
|
||||||
className="divide-y divide-borderSubtle"
|
className="divide-y divide-borderSubtle"
|
||||||
renderItem={({ model, isSelected, onSelect }) => (
|
renderItem={({ model, isSelected, onSelect }) => (
|
||||||
<label key={model.alias ?? model.name} className="block cursor-pointer">
|
<label key={model.alias ?? model.name} className="block cursor-pointer">
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between p-2 text-textStandard hover:bg-bgSubtle transition-colors"
|
className="flex items-center justify-between p-2 text-textStandard hover:bg-bgSubtle transition-colors"
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm ">{model.alias ?? model.name}</p>
|
<p className="text-sm ">{model.alias ?? model.name}</p>
|
||||||
<p className="text-xs text-textSubtle">
|
<p className="text-xs text-textSubtle">
|
||||||
{model.subtext ?? model.provider}
|
{model.subtext ?? model.provider}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="recentModels"
|
name="recentModels"
|
||||||
value={model.name}
|
value={model.name}
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
onChange={onSelect}
|
onChange={onSelect}
|
||||||
className="peer sr-only"
|
className="peer sr-only"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="h-4 w-4 rounded-full border border-gray-400 dark:border-gray-500
|
className="h-4 w-4 rounded-full border border-gray-400 dark:border-gray-500
|
||||||
peer-checked:border-[6px] peer-checked:border-black dark:peer-checked:border-white
|
peer-checked:border-[6px] peer-checked:border-black dark:peer-checked:border-white
|
||||||
peer-checked:bg-white dark:peer-checked:bg-black
|
peer-checked:bg-white dark:peer-checked:bg-black
|
||||||
transition-all duration-200 ease-in-out"
|
transition-all duration-200 ease-in-out"
|
||||||
></div>
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</label>
|
||||||
</label>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
<div
|
||||||
<div
|
className="flex items-center justify-between text-textStandard p-2 cursor-pointer hover:bg-bgStandard
|
||||||
className="flex items-center justify-between text-textStandard p-2 cursor-pointer hover:bg-bgStandard
|
|
||||||
border-t border-borderSubtle mt-2"
|
border-t border-borderSubtle mt-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModelMenuOpen(false);
|
setIsModelMenuOpen(false);
|
||||||
setView('settings');
|
setView('settings');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="text-sm">Tools and Settings</span>
|
<span className="text-sm">Tools and Settings</span>
|
||||||
<Sliders className="w-5 h-5 ml-2 rotate-90" />
|
<Sliders className="w-5 h-5 ml-2 rotate-90" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { getTools } from '../api';
|
||||||
|
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
|
||||||
|
import { HammerIcon } from 'lucide-react';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
|
||||||
|
|
||||||
|
const SUGGESTED_MAX_TOOLS = 15;
|
||||||
|
|
||||||
|
export default function ToolCount() {
|
||||||
|
const [toolCount, setToolCount] = useState(null);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTools = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getTools();
|
||||||
|
if (response.error) {
|
||||||
|
console.error('failed to get tool count');
|
||||||
|
setError(true);
|
||||||
|
} else {
|
||||||
|
setToolCount(response.data.length);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching tools:', err);
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchTools();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolCount === null) {
|
||||||
|
return <div>...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolCount < SUGGESTED_MAX_TOOLS) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button className="flex items-center justify-center p-0 border-0 bg-transparent cursor-pointer">
|
||||||
|
<HammerIcon size={16} />
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="p-3 w-auto" side="top">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-sm text-black dark:text-white">Tool count: {toolCount}</p>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button className="flex items-center justify-center p-0 border-0 bg-transparent cursor-pointer">
|
||||||
|
<ExclamationTriangleIcon color="orange" />
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="p-3" side="top">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-sm font-medium">Warning: High Tool Count</h4>
|
||||||
|
<p className="text-xs text-black dark:text-white">
|
||||||
|
Too many tools can degrade goose's performance. Consider turning a few extensions
|
||||||
|
off.
|
||||||
|
</p>
|
||||||
|
<p className="text-xs font-medium">Tool count: {toolCount}</p>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user