UI form builder for editing executor config (#619)
* ## 🎉 Implementation Complete! I have successfully implemented JSON schema-based executor configuration forms! Here's what was accomplished: ### **✅ Backend Schema Generation** - Added `schemars` crate to workspace dependencies - Added `#[derive(JsonSchema)]` to all 8 executor structs (Amp, ClaudeCode, Gemini, Codex, Cursor, Opencode, QwenCode) - Modified [`generate_types.rs`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-4b73-use-the-sc/crates/server/src/bin/generate_types.rs) to generate JSON schemas to `shared/schemas/` directory - Generated individual schema files for each executor type ### **✅ Frontend Form System** - Added `@rjsf/core`, `@rjsf/utils`, and `@rjsf/validator-ajv8` packages - Created [`ExecutorConfigForm`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-4b73-use-the-sc/frontend/src/components/ExecutorConfigForm.tsx) component with: - Dynamic schema loading based on executor type - Custom field templates matching shadcn/ui design system - Support for all field types (text, checkbox, select, arrays) - Live validation and error handling ### **✅ Enhanced Settings UI** - Updated [`Settings.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-4b73-use-the-sc/frontend/src/pages/Settings.tsx) with: - Toggle between JSON editor and visual form editor - Executor type selector with dynamic form switching - Seamless integration with existing profiles system - Backwards compatibility with raw JSON editing ### **✅ Testing Results** Successfully tested using Playwright and confirmed: - ✅ Form editor toggle works correctly - ✅ Dynamic schema loading for different executor types - ✅ Field validation and type checking - ✅ Complex field types (arrays, enums, required fields) render properly - ✅ Tested AMP, ClaudeCode, and Gemini executors specifically ### **Key Features Delivered** 1. **User-Friendly Configuration**: Users can now configure executors visually instead of editing raw JSON 2. **Type Safety**: Generated schemas ensure configuration validity 3. **Dynamic Forms**: Each executor type shows only relevant fields 4. **Backwards Compatibility**: Users can still switch back to JSON mode 5. **Professional UI**: Custom templates match the existing design system 6. **Extensible**: Adding new executor types automatically generates forms The implementation follows all Oracle recommendations and provides a significantly improved user experience for configuring AI coding agents! * Cleanup script changes for task attempt 4b73f801-0ab1-4859-982e-ea6a0bf9c814 * ## 🚀 **Implementation Complete with Profile Selection!** Your question was perfectly answered! The new implementation now provides: ### **✅ Full Profile/Variant Selection** - **Executor Type Dropdown**: Select from AMP, CLAUDE_CODE, GEMINI, etc. - **Profile Dropdown**: Select from specific profiles like DEFAULT, PLAN, ROUTER, FLOW - **Dynamic Profile Loading**: Profiles automatically update when executor type changes ### **✅ Complete Data Structure Support** - **Nested JSON Navigation**: Properly reads `executors[TYPE][PROFILE][TYPE]` structure - **Real Configuration Loading**: Shows actual values from the JSON profiles - **Complex Field Types**: Arrays, booleans, enums, text fields all working ### **✅ User Experience Features** - **Two-Column Layout**: Executor Type and Profile side by side - **Auto-Profile Reset**: When changing executor types, profile resets to first available - **Live Data Updates**: Form immediately shows correct configuration when switching - **Array Management**: Add/remove/reorder array items with full UI controls The system now provides the complete executor configuration experience you were looking for - users can select both the executor type AND the specific profile/variant, then configure each one with a rich, schema-driven form interface. * Cleanup script changes for task attempt 4b73f801-0ab1-4859-982e-ea6a0bf9c814 * improvements * append_prompt * generate forms * order * settings * amp MCP config update * form styles * textarea * style additional params * validate * menu styles * prevent reload * fmt * add and delete configurations * lint * fmnt * clippy * prettier * copy * remove old MCP * Auto detect schemas on FE * wipe shared before generation * fmt * clippy fmt * fixes * fmt * update shared types check * disable clippy for large enum * copy * tweaks * fmt * fmt
This commit is contained in:
committed by
GitHub
parent
71fda5eb90
commit
3c05db3c49
@@ -14,6 +14,7 @@ tracing = "0.1"
|
|||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
openssl-sys = { version = "0.9", features = ["vendored"] }
|
openssl-sys = { version = "0.9", features = ["vendored"] }
|
||||||
ts-rs = { git = "https://github.com/xazukx/ts-rs.git", branch = "use-ts-enum", features = ["uuid-impl", "chrono-impl", "no-serde-warnings"] }
|
ts-rs = { git = "https://github.com/xazukx/ts-rs.git", branch = "use-ts-enum", features = ["uuid-impl", "chrono-impl", "no-serde-warnings"] }
|
||||||
|
schemars = { version = "1.0.4", features = ["derive", "chrono04", "uuid1", "preserve_order"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ tracing-subscriber = { workspace = true }
|
|||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
uuid = { version = "1.0", features = ["v4", "serde"] }
|
uuid = { version = "1.0", features = ["v4", "serde"] }
|
||||||
ts-rs = { workspace = true, features = ["serde-json-impl"]}
|
ts-rs = { workspace = true, features = ["serde-json-impl"]}
|
||||||
|
schemars = { workspace = true }
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
xdg = "3.0"
|
xdg = "3.0"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema, Default)]
|
||||||
pub struct CmdOverrides {
|
pub struct CmdOverrides {
|
||||||
|
#[schemars(
|
||||||
|
title = "Base Command Override",
|
||||||
|
description = "Override the base command with a custom command"
|
||||||
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub base_command_override: Option<String>,
|
pub base_command_override: Option<String>,
|
||||||
|
#[schemars(
|
||||||
|
title = "Additional Parameters",
|
||||||
|
description = "Additional parameters to append to the base command"
|
||||||
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub additional_params: Option<Vec<String>>,
|
pub additional_params: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
pub struct CommandBuilder {
|
pub struct CommandBuilder {
|
||||||
/// Base executable command (e.g., "npx -y @anthropic-ai/claude-code@latest")
|
/// Base executable command (e.g., "npx -y @anthropic-ai/claude-code@latest")
|
||||||
pub base: String,
|
pub base: String,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::{path::Path, process::Stdio, sync::Arc};
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{io::AsyncWriteExt, process::Command};
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -10,18 +11,21 @@ use utils::{msg_store::MsgStore, shell::get_shell_command};
|
|||||||
use crate::{
|
use crate::{
|
||||||
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
||||||
executors::{
|
executors::{
|
||||||
ExecutorError, StandardCodingAgentExecutor,
|
AppendPrompt, ExecutorError, StandardCodingAgentExecutor,
|
||||||
claude::{ClaudeLogProcessor, HistoryStrategy},
|
claude::{ClaudeLogProcessor, HistoryStrategy},
|
||||||
},
|
},
|
||||||
logs::{stderr_processor::normalize_stderr_logs, utils::EntryIndexProvider},
|
logs::{stderr_processor::normalize_stderr_logs, utils::EntryIndexProvider},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An executor that uses Amp to process tasks
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
pub struct Amp {
|
pub struct Amp {
|
||||||
|
#[serde(default)]
|
||||||
|
pub append_prompt: AppendPrompt,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub append_prompt: Option<String>,
|
#[schemars(
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
title = "Dangerously Allow All",
|
||||||
|
description = "Allow all commands to be executed, even if they are not safe."
|
||||||
|
)]
|
||||||
pub dangerously_allow_all: Option<bool>,
|
pub dangerously_allow_all: Option<bool>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub cmd: CmdOverrides,
|
pub cmd: CmdOverrides,
|
||||||
@@ -48,7 +52,7 @@ impl StandardCodingAgentExecutor for Amp {
|
|||||||
let (shell_cmd, shell_arg) = get_shell_command();
|
let (shell_cmd, shell_arg) = get_shell_command();
|
||||||
let amp_command = self.build_command_builder().build_initial();
|
let amp_command = self.build_command_builder().build_initial();
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -118,7 +122,7 @@ impl StandardCodingAgentExecutor for Amp {
|
|||||||
new_thread_id.clone(),
|
new_thread_id.clone(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -158,6 +162,6 @@ impl StandardCodingAgentExecutor for Amp {
|
|||||||
|
|
||||||
// MCP configuration methods
|
// MCP configuration methods
|
||||||
fn default_mcp_config_path(&self) -> Option<std::path::PathBuf> {
|
fn default_mcp_config_path(&self) -> Option<std::path::PathBuf> {
|
||||||
dirs::config_dir().map(|config| config.join("amp").join("settings.json"))
|
dirs::home_dir().map(|home| home.join(".config").join("amp").join("settings.json"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::{path::Path, process::Stdio, sync::Arc};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{io::AsyncWriteExt, process::Command};
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -16,7 +17,7 @@ use utils::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
||||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
executors::{AppendPrompt, ExecutorError, StandardCodingAgentExecutor},
|
||||||
logs::{
|
logs::{
|
||||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||||
stderr_processor::normalize_stderr_logs,
|
stderr_processor::normalize_stderr_logs,
|
||||||
@@ -32,14 +33,13 @@ fn base_command(claude_code_router: bool) -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An executor that uses Claude CLI to process tasks
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
pub struct ClaudeCode {
|
pub struct ClaudeCode {
|
||||||
|
#[serde(default)]
|
||||||
|
pub append_prompt: AppendPrompt,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub claude_code_router: Option<bool>,
|
pub claude_code_router: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub append_prompt: Option<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub plan: Option<bool>,
|
pub plan: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub dangerously_skip_permissions: Option<bool>,
|
pub dangerously_skip_permissions: Option<bool>,
|
||||||
@@ -88,7 +88,7 @@ impl StandardCodingAgentExecutor for ClaudeCode {
|
|||||||
base_command
|
base_command
|
||||||
};
|
};
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -128,7 +128,7 @@ impl StandardCodingAgentExecutor for ClaudeCode {
|
|||||||
base_command
|
base_command
|
||||||
};
|
};
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -1502,7 +1502,7 @@ mod tests {
|
|||||||
let executor = ClaudeCode {
|
let executor = ClaudeCode {
|
||||||
claude_code_router: Some(false),
|
claude_code_router: Some(false),
|
||||||
plan: None,
|
plan: None,
|
||||||
append_prompt: None,
|
append_prompt: AppendPrompt::default(),
|
||||||
dangerously_skip_permissions: None,
|
dangerously_skip_permissions: None,
|
||||||
cmd: crate::command::CmdOverrides {
|
cmd: crate::command::CmdOverrides {
|
||||||
base_command_override: None,
|
base_command_override: None,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use async_trait::async_trait;
|
|||||||
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum_macros::AsRefStr;
|
use strum_macros::AsRefStr;
|
||||||
use tokio::{io::AsyncWriteExt, process::Command};
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
@@ -21,7 +22,7 @@ use utils::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
||||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
executors::{AppendPrompt, ExecutorError, StandardCodingAgentExecutor},
|
||||||
logs::{
|
logs::{
|
||||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType,
|
ActionType, FileChange, NormalizedEntry, NormalizedEntryType,
|
||||||
utils::{EntryIndexProvider, patch::ConversationPatch},
|
utils::{EntryIndexProvider, patch::ConversationPatch},
|
||||||
@@ -29,7 +30,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Sandbox policy modes for Codex
|
/// Sandbox policy modes for Codex
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, AsRefStr)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema, AsRefStr)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[strum(serialize_all = "kebab-case")]
|
#[strum(serialize_all = "kebab-case")]
|
||||||
pub enum SandboxMode {
|
pub enum SandboxMode {
|
||||||
@@ -39,7 +40,7 @@ pub enum SandboxMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Approval policy for Codex
|
/// Approval policy for Codex
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, AsRefStr)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, AsRefStr, JsonSchema)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[strum(serialize_all = "kebab-case")]
|
#[strum(serialize_all = "kebab-case")]
|
||||||
pub enum ApprovalPolicy {
|
pub enum ApprovalPolicy {
|
||||||
@@ -201,11 +202,10 @@ impl SessionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An executor that uses Codex CLI to process tasks
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
pub struct Codex {
|
pub struct Codex {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default)]
|
||||||
pub append_prompt: Option<String>,
|
pub append_prompt: AppendPrompt,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub sandbox: Option<SandboxMode>,
|
pub sandbox: Option<SandboxMode>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
@@ -256,7 +256,7 @@ impl StandardCodingAgentExecutor for Codex {
|
|||||||
let (shell_cmd, shell_arg) = get_shell_command();
|
let (shell_cmd, shell_arg) = get_shell_command();
|
||||||
let codex_command = self.build_command_builder().build_initial();
|
let codex_command = self.build_command_builder().build_initial();
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -297,7 +297,7 @@ impl StandardCodingAgentExecutor for Codex {
|
|||||||
format!("experimental_resume={}", rollout_file_path.display()),
|
format!("experimental_resume={}", rollout_file_path.display()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::{path::Path, process::Stdio, sync::Arc, time::Duration};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{io::AsyncWriteExt, process::Command};
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -19,7 +20,7 @@ use utils::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
||||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
executors::{AppendPrompt, ExecutorError, StandardCodingAgentExecutor},
|
||||||
logs::{
|
logs::{
|
||||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||||
plain_text_processor::PlainTextLogProcessor,
|
plain_text_processor::PlainTextLogProcessor,
|
||||||
@@ -27,11 +28,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Executor for running Cursor CLI and normalizing its JSONL stream
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
pub struct Cursor {
|
pub struct Cursor {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default)]
|
||||||
pub append_prompt: Option<String>,
|
pub append_prompt: AppendPrompt,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub force: Option<bool>,
|
pub force: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
@@ -67,7 +67,7 @@ impl StandardCodingAgentExecutor for Cursor {
|
|||||||
let (shell_cmd, shell_arg) = get_shell_command();
|
let (shell_cmd, shell_arg) = get_shell_command();
|
||||||
let agent_cmd = self.build_command_builder().build_initial();
|
let agent_cmd = self.build_command_builder().build_initial();
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -100,7 +100,7 @@ impl StandardCodingAgentExecutor for Cursor {
|
|||||||
.build_command_builder()
|
.build_command_builder()
|
||||||
.build_follow_up(&["--resume".to_string(), session_id.to_string()]);
|
.build_follow_up(&["--resume".to_string(), session_id.to_string()]);
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -1072,7 +1072,7 @@ mod tests {
|
|||||||
// Avoid relying on feature flag in tests; construct with a dummy command
|
// Avoid relying on feature flag in tests; construct with a dummy command
|
||||||
let executor = Cursor {
|
let executor = Cursor {
|
||||||
// No command field needed anymore
|
// No command field needed anymore
|
||||||
append_prompt: None,
|
append_prompt: AppendPrompt::default(),
|
||||||
force: None,
|
force: None,
|
||||||
model: None,
|
model: None,
|
||||||
cmd: Default::default(),
|
cmd: Default::default(),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::{
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
||||||
use futures::{StreamExt, stream::BoxStream};
|
use futures::{StreamExt, stream::BoxStream};
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{self, OpenOptions},
|
fs::{self, OpenOptions},
|
||||||
@@ -18,7 +19,7 @@ use utils::{msg_store::MsgStore, shell::get_shell_command};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
||||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
executors::{AppendPrompt, ExecutorError, StandardCodingAgentExecutor},
|
||||||
logs::{
|
logs::{
|
||||||
NormalizedEntry, NormalizedEntryType, plain_text_processor::PlainTextLogProcessor,
|
NormalizedEntry, NormalizedEntryType, plain_text_processor::PlainTextLogProcessor,
|
||||||
stderr_processor::normalize_stderr_logs, utils::EntryIndexProvider,
|
stderr_processor::normalize_stderr_logs, utils::EntryIndexProvider,
|
||||||
@@ -26,8 +27,7 @@ use crate::{
|
|||||||
stdout_dup,
|
stdout_dup,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Model variant of Gemini to use
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum GeminiModel {
|
pub enum GeminiModel {
|
||||||
Default, // no --model flag
|
Default, // no --model flag
|
||||||
@@ -50,13 +50,12 @@ impl GeminiModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An executor that uses Gemini to process tasks
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
pub struct Gemini {
|
pub struct Gemini {
|
||||||
|
#[serde(default)]
|
||||||
|
pub append_prompt: AppendPrompt,
|
||||||
pub model: GeminiModel,
|
pub model: GeminiModel,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub append_prompt: Option<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub yolo: Option<bool>,
|
pub yolo: Option<bool>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub cmd: CmdOverrides,
|
pub cmd: CmdOverrides,
|
||||||
@@ -84,7 +83,7 @@ impl StandardCodingAgentExecutor for Gemini {
|
|||||||
let (shell_cmd, shell_arg) = get_shell_command();
|
let (shell_cmd, shell_arg) = get_shell_command();
|
||||||
let gemini_command = self.build_command_builder().build_initial();
|
let gemini_command = self.build_command_builder().build_initial();
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -364,7 +363,7 @@ The following is the conversation history from this session:
|
|||||||
=== INSTRUCTIONS ===
|
=== INSTRUCTIONS ===
|
||||||
You are continuing work on the above task. The execution history shows the previous conversation in this session. Please continue from where the previous execution left off, taking into account all the context provided above.{}
|
You are continuing work on the above task. The execution history shows the previous conversation in this session. Please continue from where the previous execution left off, taking into account all the context provided above.{}
|
||||||
"#,
|
"#,
|
||||||
self.append_prompt.clone().unwrap_or_default(),
|
self.append_prompt.get().unwrap_or_default(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use async_trait::async_trait;
|
|||||||
use command_group::AsyncGroupChild;
|
use command_group::AsyncGroupChild;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use futures_io::Error as FuturesIoError;
|
use futures_io::Error as FuturesIoError;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::Type;
|
use sqlx::Type;
|
||||||
use strum_macros::{Display, EnumDiscriminants, EnumString, VariantNames};
|
use strum_macros::{Display, EnumDiscriminants, EnumString, VariantNames};
|
||||||
@@ -167,3 +168,26 @@ pub trait StandardCodingAgentExecutor {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
#[schemars(
|
||||||
|
title = "Append Prompt",
|
||||||
|
description = "Extra text appended to the prompt",
|
||||||
|
extend("format" = "textarea")
|
||||||
|
)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct AppendPrompt(pub Option<String>);
|
||||||
|
|
||||||
|
impl AppendPrompt {
|
||||||
|
pub fn get(&self) -> Option<String> {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn combine_prompt(&self, prompt: &str) -> String {
|
||||||
|
match self {
|
||||||
|
AppendPrompt(Some(value)) => format!("{prompt}{value}"),
|
||||||
|
AppendPrompt(None) => prompt.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use fork_stream::StreamExt as _;
|
|||||||
use futures::{StreamExt, future::ready, stream::BoxStream};
|
use futures::{StreamExt, future::ready, stream::BoxStream};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{io::AsyncWriteExt, process::Command};
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -21,7 +22,7 @@ use utils::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
||||||
executors::{ExecutorError, StandardCodingAgentExecutor},
|
executors::{AppendPrompt, ExecutorError, StandardCodingAgentExecutor},
|
||||||
logs::{
|
logs::{
|
||||||
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
ActionType, FileChange, NormalizedEntry, NormalizedEntryType, TodoItem,
|
||||||
plain_text_processor::{MessageBoundary, PlainTextLogProcessor},
|
plain_text_processor::{MessageBoundary, PlainTextLogProcessor},
|
||||||
@@ -29,11 +30,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An executor that uses OpenCode to process tasks
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
pub struct Opencode {
|
pub struct Opencode {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default)]
|
||||||
pub append_prompt: Option<String>,
|
pub append_prompt: AppendPrompt,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
@@ -69,7 +69,7 @@ impl StandardCodingAgentExecutor for Opencode {
|
|||||||
let (shell_cmd, shell_arg) = get_shell_command();
|
let (shell_cmd, shell_arg) = get_shell_command();
|
||||||
let opencode_command = self.build_command_builder().build_initial();
|
let opencode_command = self.build_command_builder().build_initial();
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -104,7 +104,7 @@ impl StandardCodingAgentExecutor for Opencode {
|
|||||||
.build_command_builder()
|
.build_command_builder()
|
||||||
.build_follow_up(&["--session".to_string(), session_id.to_string()]);
|
.build_follow_up(&["--session".to_string(), session_id.to_string()]);
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -383,7 +383,7 @@ pub enum Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// TODO information structure
|
/// TODO information structure
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
pub struct TodoInfo {
|
pub struct TodoInfo {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
@@ -392,7 +392,7 @@ pub struct TodoInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Web fetch format options
|
/// Web fetch format options
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum WebFetchFormat {
|
pub enum WebFetchFormat {
|
||||||
Text,
|
Text,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::{path::Path, process::Stdio, sync::Arc};
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{io::AsyncWriteExt, process::Command};
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -9,15 +10,14 @@ use utils::{msg_store::MsgStore, shell::get_shell_command};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
command::{CmdOverrides, CommandBuilder, apply_overrides},
|
||||||
executors::{ExecutorError, StandardCodingAgentExecutor, gemini::Gemini},
|
executors::{AppendPrompt, ExecutorError, StandardCodingAgentExecutor, gemini::Gemini},
|
||||||
logs::{stderr_processor::normalize_stderr_logs, utils::EntryIndexProvider},
|
logs::{stderr_processor::normalize_stderr_logs, utils::EntryIndexProvider},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An executor that uses QwenCode CLI to process tasks
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
|
|
||||||
pub struct QwenCode {
|
pub struct QwenCode {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default)]
|
||||||
pub append_prompt: Option<String>,
|
pub append_prompt: AppendPrompt,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub yolo: Option<bool>,
|
pub yolo: Option<bool>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@@ -46,7 +46,7 @@ impl StandardCodingAgentExecutor for QwenCode {
|
|||||||
let (shell_cmd, shell_arg) = get_shell_command();
|
let (shell_cmd, shell_arg) = get_shell_command();
|
||||||
let qwen_command = self.build_command_builder().build_initial();
|
let qwen_command = self.build_command_builder().build_initial();
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
@@ -80,7 +80,7 @@ impl StandardCodingAgentExecutor for QwenCode {
|
|||||||
.build_command_builder()
|
.build_command_builder()
|
||||||
.build_follow_up(&["--resume".to_string(), session_id.to_string()]);
|
.build_follow_up(&["--resume".to_string(), session_id.to_string()]);
|
||||||
|
|
||||||
let combined_prompt = utils::text::combine_prompt(&self.append_prompt, prompt);
|
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||||
|
|
||||||
let mut command = Command::new(shell_cmd);
|
let mut command = Command::new(shell_cmd);
|
||||||
command
|
command
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ enum PatchOperation {
|
|||||||
Remove,
|
Remove,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Serialize, TS)]
|
#[derive(Serialize, TS)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE", tag = "type", content = "content")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE", tag = "type", content = "content")]
|
||||||
pub enum PatchType {
|
pub enum PatchType {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ command-group = { version = "5.0", features = ["with-tokio"] }
|
|||||||
nix = { version = "0.29", features = ["signal", "process"] }
|
nix = { version = "0.29", features = ["signal", "process"] }
|
||||||
openssl-sys = { workspace = true }
|
openssl-sys = { workspace = true }
|
||||||
rmcp = { version = "0.5.0", features = ["server", "transport-io"] }
|
rmcp = { version = "0.5.0", features = ["server", "transport-io"] }
|
||||||
schemars = "0.8"
|
schemars = { workspace = true }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
sentry = { version = "0.41.0", features = ["anyhow", "backtrace", "panic", "debug-images"] }
|
sentry = { version = "0.41.0", features = ["anyhow", "backtrace", "panic", "debug-images"] }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::{env, fs, path::Path};
|
use std::{env, fs, path::Path};
|
||||||
|
|
||||||
|
use schemars::{JsonSchema, Schema, SchemaGenerator, generate::SchemaSettings};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
fn generate_types_content() -> String {
|
fn generate_types_content() -> String {
|
||||||
@@ -74,6 +75,7 @@ fn generate_types_content() -> String {
|
|||||||
executors::executors::cursor::Cursor::decl(),
|
executors::executors::cursor::Cursor::decl(),
|
||||||
executors::executors::opencode::Opencode::decl(),
|
executors::executors::opencode::Opencode::decl(),
|
||||||
executors::executors::qwen::QwenCode::decl(),
|
executors::executors::qwen::QwenCode::decl(),
|
||||||
|
executors::executors::AppendPrompt::decl(),
|
||||||
executors::actions::coding_agent_initial::CodingAgentInitialRequest::decl(),
|
executors::actions::coding_agent_initial::CodingAgentInitialRequest::decl(),
|
||||||
executors::actions::coding_agent_follow_up::CodingAgentFollowUpRequest::decl(),
|
executors::actions::coding_agent_follow_up::CodingAgentFollowUpRequest::decl(),
|
||||||
server::routes::task_attempts::CreateTaskAttemptBody::decl(),
|
server::routes::task_attempts::CreateTaskAttemptBody::decl(),
|
||||||
@@ -125,13 +127,54 @@ fn generate_types_content() -> String {
|
|||||||
format!("{HEADER}\n\n{body}")
|
format!("{HEADER}\n\n{body}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_schema<T: JsonSchema>(
|
||||||
|
name: &str,
|
||||||
|
schemas_dir: &std::path::Path,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Draft-07, inline everything (no $defs)
|
||||||
|
let mut settings = SchemaSettings::draft07();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
|
||||||
|
let generator: SchemaGenerator = settings.into_generator();
|
||||||
|
let schema: Schema = generator.into_root_schema_for::<T>();
|
||||||
|
|
||||||
|
// Convert to JSON value to manipulate it
|
||||||
|
let mut schema_value: serde_json::Value = serde_json::to_value(&schema)?;
|
||||||
|
|
||||||
|
// Remove the title from root schema to prevent RJSF from creating an outer field container
|
||||||
|
if let Some(obj) = schema_value.as_object_mut() {
|
||||||
|
obj.remove("title");
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema_json = serde_json::to_string_pretty(&schema_value)?;
|
||||||
|
std::fs::write(schemas_dir.join(format!("{name}.json")), schema_json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_schemas() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Create schemas directory
|
||||||
|
let schemas_dir = Path::new("shared/schemas");
|
||||||
|
fs::create_dir_all(schemas_dir)?;
|
||||||
|
|
||||||
|
println!("Generating JSON schemas…");
|
||||||
|
|
||||||
|
// Generate schemas for all executor types
|
||||||
|
write_schema::<executors::executors::amp::Amp>("amp", schemas_dir)?;
|
||||||
|
write_schema::<executors::executors::claude::ClaudeCode>("claude_code", schemas_dir)?;
|
||||||
|
write_schema::<executors::executors::gemini::Gemini>("gemini", schemas_dir)?;
|
||||||
|
write_schema::<executors::executors::codex::Codex>("codex", schemas_dir)?;
|
||||||
|
write_schema::<executors::executors::cursor::Cursor>("cursor", schemas_dir)?;
|
||||||
|
write_schema::<executors::executors::opencode::Opencode>("opencode", schemas_dir)?;
|
||||||
|
write_schema::<executors::executors::qwen::QwenCode>("qwen_code", schemas_dir)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let check_mode = args.iter().any(|arg| arg == "--check");
|
let check_mode = args.iter().any(|arg| arg == "--check");
|
||||||
|
|
||||||
// 1. Make sure ../shared exists
|
|
||||||
let shared_path = Path::new("shared");
|
let shared_path = Path::new("shared");
|
||||||
fs::create_dir_all(shared_path).expect("cannot create shared");
|
|
||||||
|
|
||||||
println!("Generating TypeScript types…");
|
println!("Generating TypeScript types…");
|
||||||
|
|
||||||
@@ -151,8 +194,22 @@ fn main() {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Wipe existing shared
|
||||||
|
fs::remove_dir_all(shared_path).ok();
|
||||||
|
|
||||||
|
// Recreate folder
|
||||||
|
fs::create_dir_all(shared_path).expect("cannot create shared");
|
||||||
|
|
||||||
// Write the file as before
|
// Write the file as before
|
||||||
fs::write(&types_path, generated).expect("unable to write types.ts");
|
fs::write(&types_path, generated).expect("unable to write types.ts");
|
||||||
println!("✅ TypeScript types generated in shared/");
|
println!("✅ TypeScript types generated in shared/");
|
||||||
|
|
||||||
|
// Generate JSON schemas
|
||||||
|
if let Err(e) = generate_schemas() {
|
||||||
|
eprintln!("❌ Failed to generate schemas: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✅ JSON schemas generated in shared/schemas/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,3 @@ pub fn short_uuid(u: &Uuid) -> String {
|
|||||||
let full = u.simple().to_string();
|
let full = u.simple().to_string();
|
||||||
full.chars().take(4).collect() // grab the first 4 chars
|
full.chars().take(4).collect() // grab the first 4 chars
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn combine_prompt(append: &Option<String>, prompt: &str) -> String {
|
|
||||||
match append {
|
|
||||||
Some(append) => format!("{prompt}{append}"),
|
|
||||||
None => prompt.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
262
frontend/package-lock.json
generated
262
frontend/package-lock.json
generated
@@ -25,6 +25,9 @@
|
|||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@radix-ui/react-tooltip": "^1.2.7",
|
"@radix-ui/react-tooltip": "^1.2.7",
|
||||||
|
"@rjsf/core": "^5.24.13",
|
||||||
|
"@rjsf/utils": "^5.24.13",
|
||||||
|
"@rjsf/validator-ajv8": "^5.24.13",
|
||||||
"@sentry/react": "^9.34.0",
|
"@sentry/react": "^9.34.0",
|
||||||
"@sentry/vite-plugin": "^3.5.0",
|
"@sentry/vite-plugin": "^3.5.0",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
@@ -2273,6 +2276,90 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rjsf/core": {
|
||||||
|
"version": "5.24.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.13.tgz",
|
||||||
|
"integrity": "sha512-ONTr14s7LFIjx2VRFLuOpagL76sM/HPy6/OhdBfq6UukINmTIs6+aFN0GgcR0aXQHFDXQ7f/fel0o/SO05Htdg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"markdown-to-jsx": "^7.4.1",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@rjsf/utils": "^5.24.x",
|
||||||
|
"react": "^16.14.0 || >=17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rjsf/utils": {
|
||||||
|
"version": "5.24.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.13.tgz",
|
||||||
|
"integrity": "sha512-rNF8tDxIwTtXzz5O/U23QU73nlhgQNYJ+Sv5BAwQOIyhIE2Z3S5tUiSVMwZHt0julkv/Ryfwi+qsD4FiE5rOuw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"json-schema-merge-allof": "^0.8.1",
|
||||||
|
"jsonpointer": "^5.0.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"react-is": "^18.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.14.0 || >=17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rjsf/utils/node_modules/react-is": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@rjsf/validator-ajv8": {
|
||||||
|
"version": "5.24.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.13.tgz",
|
||||||
|
"integrity": "sha512-oWHP7YK581M8I5cF1t+UXFavnv+bhcqjtL1a7MG/Kaffi0EwhgcYjODrD8SsnrhncsEYMqSECr4ZOEoirnEUWw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^8.12.0",
|
||||||
|
"ajv-formats": "^2.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@rjsf/utils": "^5.24.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rjsf/validator-ajv8/node_modules/ajv": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fast-uri": "^3.0.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rjsf/validator-ajv8/node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.11",
|
"version": "1.0.0-beta.11",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
|
||||||
@@ -3513,6 +3600,45 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ajv-formats": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ajv": "^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ajv": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ajv-formats/node_modules/ajv": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fast-uri": "^3.0.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
@@ -3959,6 +4085,27 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/compute-gcd": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==",
|
||||||
|
"dependencies": {
|
||||||
|
"validate.io-array": "^1.0.3",
|
||||||
|
"validate.io-function": "^1.0.2",
|
||||||
|
"validate.io-integer-array": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/compute-lcm": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"compute-gcd": "^1.2.1",
|
||||||
|
"validate.io-array": "^1.0.3",
|
||||||
|
"validate.io-function": "^1.0.2",
|
||||||
|
"validate.io-integer-array": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -4558,7 +4705,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-diff": {
|
"node_modules/fast-diff": {
|
||||||
@@ -4609,6 +4755,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.19.1",
|
"version": "1.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||||
@@ -5241,6 +5403,29 @@
|
|||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-schema-compare": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/json-schema-merge-allof": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"compute-lcm": "^1.1.2",
|
||||||
|
"json-schema-compare": "^0.2.2",
|
||||||
|
"lodash": "^4.17.20"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
@@ -5267,6 +5452,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonpointer": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@@ -5324,6 +5518,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash-es": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.castarray": {
|
"node_modules/lodash.castarray": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||||
@@ -5409,6 +5615,18 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/markdown-to-jsx": {
|
||||||
|
"version": "7.7.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.13.tgz",
|
||||||
|
"integrity": "sha512-DiueEq2bttFcSxUs85GJcQVrOr0+VVsPfj9AEUPqmExJ3f8P/iQNvZHltV4tm1XVhu1kl0vWBZWT3l99izRMaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 0.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdast-util-from-markdown": {
|
"node_modules/mdast-util-from-markdown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
|
||||||
@@ -6960,6 +7178,15 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-from-string": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
@@ -7821,6 +8048,39 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/validate.io-array": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/validate.io-function": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ=="
|
||||||
|
},
|
||||||
|
"node_modules/validate.io-integer": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"validate.io-number": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/validate.io-integer-array": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==",
|
||||||
|
"dependencies": {
|
||||||
|
"validate.io-array": "^1.0.3",
|
||||||
|
"validate.io-integer": "^1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/validate.io-number": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg=="
|
||||||
|
},
|
||||||
"node_modules/vfile": {
|
"node_modules/vfile": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@radix-ui/react-tooltip": "^1.2.7",
|
"@radix-ui/react-tooltip": "^1.2.7",
|
||||||
|
"@rjsf/shadcn": "6.0.0-beta.10",
|
||||||
"@sentry/react": "^9.34.0",
|
"@sentry/react": "^9.34.0",
|
||||||
"@sentry/vite-plugin": "^3.5.0",
|
"@sentry/vite-plugin": "^3.5.0",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
@@ -58,6 +59,9 @@
|
|||||||
"zustand": "^4.5.4"
|
"zustand": "^4.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rjsf/core": "6.0.0-beta.11",
|
||||||
|
"@rjsf/utils": "6.0.0-beta.11",
|
||||||
|
"@rjsf/validator-ajv8": "6.0.0-beta.11",
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@types/react": "^18.2.43",
|
"@types/react": "^18.2.43",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.17",
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { BrowserRouter, Route, Routes, useLocation } from 'react-router-dom';
|
import {
|
||||||
|
BrowserRouter,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
useLocation,
|
||||||
|
Navigate,
|
||||||
|
} from 'react-router-dom';
|
||||||
import { Navbar } from '@/components/layout/navbar';
|
import { Navbar } from '@/components/layout/navbar';
|
||||||
import { Projects } from '@/pages/projects';
|
import { Projects } from '@/pages/projects';
|
||||||
import { ProjectTasks } from '@/pages/project-tasks';
|
import { ProjectTasks } from '@/pages/project-tasks';
|
||||||
|
|
||||||
import { Settings } from '@/pages/Settings';
|
import {
|
||||||
import { McpServers } from '@/pages/McpServers';
|
SettingsLayout,
|
||||||
|
GeneralSettings,
|
||||||
|
AgentSettings,
|
||||||
|
McpSettings,
|
||||||
|
} from '@/pages/settings/';
|
||||||
import { DisclaimerDialog } from '@/components/DisclaimerDialog';
|
import { DisclaimerDialog } from '@/components/DisclaimerDialog';
|
||||||
import { OnboardingDialog } from '@/components/OnboardingDialog';
|
import { OnboardingDialog } from '@/components/OnboardingDialog';
|
||||||
import { PrivacyOptInDialog } from '@/components/PrivacyOptInDialog';
|
import { PrivacyOptInDialog } from '@/components/PrivacyOptInDialog';
|
||||||
@@ -237,8 +247,17 @@ function AppContent() {
|
|||||||
path="/projects/:projectId/tasks/:taskId"
|
path="/projects/:projectId/tasks/:taskId"
|
||||||
element={<ProjectTasks />}
|
element={<ProjectTasks />}
|
||||||
/>
|
/>
|
||||||
<Route path="/settings" element={<Settings />} />
|
<Route path="/settings/*" element={<SettingsLayout />}>
|
||||||
<Route path="/mcp-servers" element={<McpServers />} />
|
<Route index element={<Navigate to="general" replace />} />
|
||||||
|
<Route path="general" element={<GeneralSettings />} />
|
||||||
|
<Route path="agents" element={<AgentSettings />} />
|
||||||
|
<Route path="mcp" element={<McpSettings />} />
|
||||||
|
</Route>
|
||||||
|
{/* Redirect old MCP route */}
|
||||||
|
<Route
|
||||||
|
path="/mcp-servers"
|
||||||
|
element={<Navigate to="/settings/mcp" replace />}
|
||||||
|
/>
|
||||||
</SentryRoutes>
|
</SentryRoutes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
138
frontend/src/components/ExecutorConfigForm.tsx
Normal file
138
frontend/src/components/ExecutorConfigForm.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { useMemo, useEffect, useState } from 'react';
|
||||||
|
import Form from '@rjsf/core';
|
||||||
|
import { RJSFValidationError } from '@rjsf/utils';
|
||||||
|
import validator from '@rjsf/validator-ajv8';
|
||||||
|
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { shadcnTheme } from './rjsf';
|
||||||
|
// Using custom shadcn/ui widgets instead of @rjsf/shadcn theme
|
||||||
|
|
||||||
|
type ExecutorType =
|
||||||
|
| 'AMP'
|
||||||
|
| 'CLAUDE_CODE'
|
||||||
|
| 'GEMINI'
|
||||||
|
| 'CODEX'
|
||||||
|
| 'CURSOR'
|
||||||
|
| 'OPENCODE'
|
||||||
|
| 'QWEN_CODE';
|
||||||
|
|
||||||
|
interface ExecutorConfigFormProps {
|
||||||
|
executor: ExecutorType;
|
||||||
|
value: any;
|
||||||
|
onSubmit?: (formData: any) => void;
|
||||||
|
onChange?: (formData: any) => void;
|
||||||
|
onSave?: (formData: any) => Promise<void>;
|
||||||
|
disabled?: boolean;
|
||||||
|
isSaving?: boolean;
|
||||||
|
isDirty?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
import schemas from 'virtual:executor-schemas';
|
||||||
|
|
||||||
|
export function ExecutorConfigForm({
|
||||||
|
executor,
|
||||||
|
value,
|
||||||
|
onSubmit,
|
||||||
|
onChange,
|
||||||
|
onSave,
|
||||||
|
disabled = false,
|
||||||
|
isSaving = false,
|
||||||
|
isDirty = false,
|
||||||
|
}: ExecutorConfigFormProps) {
|
||||||
|
const [formData, setFormData] = useState(value || {});
|
||||||
|
const [validationErrors, setValidationErrors] = useState<
|
||||||
|
RJSFValidationError[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const schema = useMemo(() => {
|
||||||
|
return schemas[executor];
|
||||||
|
}, [executor]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormData(value || {});
|
||||||
|
setValidationErrors([]);
|
||||||
|
}, [value, executor]);
|
||||||
|
|
||||||
|
const handleChange = ({ formData: newFormData }: any) => {
|
||||||
|
setFormData(newFormData);
|
||||||
|
if (onChange) {
|
||||||
|
onChange(newFormData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async ({ formData: submitData }: any) => {
|
||||||
|
setValidationErrors([]);
|
||||||
|
if (onSave) {
|
||||||
|
await onSave(submitData);
|
||||||
|
} else if (onSubmit) {
|
||||||
|
onSubmit(submitData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (errors: RJSFValidationError[]) => {
|
||||||
|
setValidationErrors(errors);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!schema) {
|
||||||
|
return (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>
|
||||||
|
Schema not found for executor type: {executor}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<Form
|
||||||
|
schema={schema}
|
||||||
|
formData={formData}
|
||||||
|
onChange={handleChange}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onError={handleError}
|
||||||
|
validator={validator}
|
||||||
|
disabled={disabled}
|
||||||
|
liveValidate
|
||||||
|
showErrorList={false}
|
||||||
|
widgets={shadcnTheme.widgets}
|
||||||
|
templates={shadcnTheme.templates}
|
||||||
|
>
|
||||||
|
{onSave && (
|
||||||
|
<div className="flex justify-end pt-4">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!isDirty || validationErrors.length > 0 || isSaving}
|
||||||
|
>
|
||||||
|
{isSaving && (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
)}
|
||||||
|
Save Configuration
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{validationErrors.length > 0 && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>
|
||||||
|
<ul className="list-disc list-inside space-y-1">
|
||||||
|
{validationErrors.map((error, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
{error.property}: {error.message}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
FolderOpen,
|
FolderOpen,
|
||||||
Settings,
|
Settings,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
Server,
|
|
||||||
MessageCircleQuestion,
|
MessageCircleQuestion,
|
||||||
Menu,
|
Menu,
|
||||||
Plus,
|
Plus,
|
||||||
@@ -27,7 +26,6 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
const INTERNAL_NAV = [
|
const INTERNAL_NAV = [
|
||||||
{ label: 'Projects', icon: FolderOpen, to: '/projects' },
|
{ label: 'Projects', icon: FolderOpen, to: '/projects' },
|
||||||
{ label: 'MCP Servers', icon: Server, to: '/mcp-servers' },
|
|
||||||
{ label: 'Settings', icon: Settings, to: '/settings' },
|
{ label: 'Settings', icon: Settings, to: '/settings' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ export function Navbar() {
|
|||||||
|
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
{INTERNAL_NAV.map((item) => {
|
{INTERNAL_NAV.map((item) => {
|
||||||
const active = location.pathname === item.to;
|
const active = location.pathname.startsWith(item.to);
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
|||||||
3
frontend/src/components/rjsf/index.ts
Normal file
3
frontend/src/components/rjsf/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { shadcnTheme, customWidgets, customTemplates } from './theme';
|
||||||
|
export * from './widgets';
|
||||||
|
export * from './templates';
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
ArrayFieldTemplateProps,
|
||||||
|
ArrayFieldTemplateItemType,
|
||||||
|
} from '@rjsf/utils';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Plus, X } from 'lucide-react';
|
||||||
|
|
||||||
|
export const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
|
||||||
|
const { canAdd, items, onAddClick, disabled, readonly } = props;
|
||||||
|
|
||||||
|
if (!items || (items.length === 0 && !canAdd)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
{items.length > 0 &&
|
||||||
|
items.map((element: ArrayFieldTemplateItemType) => (
|
||||||
|
<ArrayItem
|
||||||
|
key={element.key}
|
||||||
|
element={element}
|
||||||
|
disabled={disabled}
|
||||||
|
readonly={readonly}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{canAdd && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onAddClick}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
Add Item
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ArrayItemProps {
|
||||||
|
element: ArrayFieldTemplateItemType;
|
||||||
|
disabled?: boolean;
|
||||||
|
readonly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArrayItem = ({ element, disabled, readonly }: ArrayItemProps) => {
|
||||||
|
const { children } = element;
|
||||||
|
const elementAny = element as any; // Type assertion needed for RJSF v6 beta properties
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="flex-1">{children}</div>
|
||||||
|
|
||||||
|
{/* Remove button */}
|
||||||
|
{elementAny.buttonsProps?.hasRemove && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={elementAny.buttonsProps.onDropIndexClick(
|
||||||
|
elementAny.buttonsProps.index
|
||||||
|
)}
|
||||||
|
disabled={disabled || readonly || elementAny.buttonsProps.disabled}
|
||||||
|
className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-all duration-200 shrink-0"
|
||||||
|
title="Remove item"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
59
frontend/src/components/rjsf/templates/FieldTemplate.tsx
Normal file
59
frontend/src/components/rjsf/templates/FieldTemplate.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { FieldTemplateProps } from '@rjsf/utils';
|
||||||
|
|
||||||
|
export const FieldTemplate = (props: FieldTemplateProps) => {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
rawErrors = [],
|
||||||
|
rawHelp,
|
||||||
|
rawDescription,
|
||||||
|
label,
|
||||||
|
required,
|
||||||
|
schema,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (schema.type === 'object') {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two-column layout for other field types
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 py-6">
|
||||||
|
{/* Left column: Label and description */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{label && (
|
||||||
|
<div className="text-sm font-bold leading-relaxed">
|
||||||
|
{label}
|
||||||
|
{required && <span className="text-destructive ml-1">*</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rawDescription && (
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
|
{rawDescription}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rawHelp && (
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
|
{rawHelp}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right column: Field content */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{children}
|
||||||
|
|
||||||
|
{rawErrors.length > 0 && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{rawErrors.map((error, index) => (
|
||||||
|
<p key={index} className="text-sm text-destructive">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
5
frontend/src/components/rjsf/templates/FormTemplate.tsx
Normal file
5
frontend/src/components/rjsf/templates/FormTemplate.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const FormTemplate = (props: any) => {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
return <div className="w-full">{children}</div>;
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ObjectFieldTemplateProps } from '@rjsf/utils';
|
||||||
|
|
||||||
|
export const ObjectFieldTemplate = (props: ObjectFieldTemplateProps) => {
|
||||||
|
const { properties } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="divide-y">
|
||||||
|
{properties.map((element) => (
|
||||||
|
<div key={element.name}>{element.content}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
4
frontend/src/components/rjsf/templates/index.ts
Normal file
4
frontend/src/components/rjsf/templates/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { ArrayFieldTemplate } from './ArrayFieldTemplate';
|
||||||
|
export { FieldTemplate } from './FieldTemplate';
|
||||||
|
export { ObjectFieldTemplate } from './ObjectFieldTemplate';
|
||||||
|
export { FormTemplate } from './FormTemplate';
|
||||||
33
frontend/src/components/rjsf/theme.ts
Normal file
33
frontend/src/components/rjsf/theme.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { RegistryWidgetsType } from '@rjsf/utils';
|
||||||
|
import {
|
||||||
|
TextWidget,
|
||||||
|
SelectWidget,
|
||||||
|
CheckboxWidget,
|
||||||
|
TextareaWidget,
|
||||||
|
} from './widgets';
|
||||||
|
import {
|
||||||
|
ArrayFieldTemplate,
|
||||||
|
FieldTemplate,
|
||||||
|
ObjectFieldTemplate,
|
||||||
|
FormTemplate,
|
||||||
|
} from './templates';
|
||||||
|
|
||||||
|
export const customWidgets: RegistryWidgetsType = {
|
||||||
|
TextWidget,
|
||||||
|
SelectWidget,
|
||||||
|
CheckboxWidget,
|
||||||
|
TextareaWidget,
|
||||||
|
textarea: TextareaWidget,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customTemplates = {
|
||||||
|
ArrayFieldTemplate,
|
||||||
|
FieldTemplate,
|
||||||
|
ObjectFieldTemplate,
|
||||||
|
FormTemplate,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shadcnTheme = {
|
||||||
|
widgets: customWidgets,
|
||||||
|
templates: customTemplates,
|
||||||
|
};
|
||||||
23
frontend/src/components/rjsf/widgets/CheckboxWidget.tsx
Normal file
23
frontend/src/components/rjsf/widgets/CheckboxWidget.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { WidgetProps } from '@rjsf/utils';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
|
||||||
|
export const CheckboxWidget = (props: WidgetProps) => {
|
||||||
|
const { id, value, disabled, readonly, onChange } = props;
|
||||||
|
|
||||||
|
const handleChange = (checked: boolean) => {
|
||||||
|
onChange(checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checked = Boolean(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={id}
|
||||||
|
checked={checked}
|
||||||
|
onCheckedChange={handleChange}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
69
frontend/src/components/rjsf/widgets/SelectWidget.tsx
Normal file
69
frontend/src/components/rjsf/widgets/SelectWidget.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { WidgetProps } from '@rjsf/utils';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
|
||||||
|
export const SelectWidget = (props: WidgetProps) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
disabled,
|
||||||
|
readonly,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
options,
|
||||||
|
schema,
|
||||||
|
placeholder,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { enumOptions } = options;
|
||||||
|
|
||||||
|
const handleChange = (newValue: string) => {
|
||||||
|
// Handle nullable enum values - '__null__' means null for nullable types
|
||||||
|
const finalValue = newValue === '__null__' ? options.emptyValue : newValue;
|
||||||
|
onChange(finalValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (open: boolean) => {
|
||||||
|
if (!open && onBlur) {
|
||||||
|
onBlur(id, value);
|
||||||
|
}
|
||||||
|
if (open && onFocus) {
|
||||||
|
onFocus(id, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert enumOptions to the format expected by our Select component
|
||||||
|
const selectOptions = enumOptions || [];
|
||||||
|
|
||||||
|
// Handle nullable types by adding a null option
|
||||||
|
const isNullable = Array.isArray(schema.type) && schema.type.includes('null');
|
||||||
|
const allOptions = isNullable
|
||||||
|
? [{ value: '__null__', label: 'None' }, ...selectOptions]
|
||||||
|
: selectOptions;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={value === null ? '__null__' : (value ?? '')}
|
||||||
|
onValueChange={handleChange}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
>
|
||||||
|
<SelectTrigger id={id}>
|
||||||
|
<SelectValue placeholder={placeholder || 'Select an option...'} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{allOptions.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={String(option.value)}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
45
frontend/src/components/rjsf/widgets/TextWidget.tsx
Normal file
45
frontend/src/components/rjsf/widgets/TextWidget.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { WidgetProps } from '@rjsf/utils';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
|
||||||
|
export const TextWidget = (props: WidgetProps) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
disabled,
|
||||||
|
readonly,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
placeholder,
|
||||||
|
options,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
onChange(newValue === '' ? options.emptyValue : newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||||
|
if (onBlur) {
|
||||||
|
onBlur(id, event.target.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||||
|
if (onFocus) {
|
||||||
|
onFocus(id, event.target.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
value={value ?? ''}
|
||||||
|
placeholder={placeholder || ''}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
53
frontend/src/components/rjsf/widgets/TextareaWidget.tsx
Normal file
53
frontend/src/components/rjsf/widgets/TextareaWidget.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { WidgetProps } from '@rjsf/utils';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
|
||||||
|
export const TextareaWidget = (props: WidgetProps) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
disabled,
|
||||||
|
readonly,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
placeholder,
|
||||||
|
options,
|
||||||
|
schema,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
onChange(newValue === '' ? options.emptyValue : newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = (event: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (onBlur) {
|
||||||
|
onBlur(id, event.target.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus = (event: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (onFocus) {
|
||||||
|
onFocus(id, event.target.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get rows from ui:options or default based on field name
|
||||||
|
const rows =
|
||||||
|
options.rows ||
|
||||||
|
((schema.title || '').toLowerCase().includes('prompt') ? 4 : 3);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Textarea
|
||||||
|
id={id}
|
||||||
|
value={value ?? ''}
|
||||||
|
placeholder={placeholder || ''}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
rows={rows}
|
||||||
|
className="resize-vertical"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
4
frontend/src/components/rjsf/widgets/index.ts
Normal file
4
frontend/src/components/rjsf/widgets/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { TextWidget } from './TextWidget';
|
||||||
|
export { SelectWidget } from './SelectWidget';
|
||||||
|
export { CheckboxWidget } from './CheckboxWidget';
|
||||||
|
export { TextareaWidget } from './TextareaWidget';
|
||||||
@@ -12,7 +12,7 @@ const buttonVariants = cva(
|
|||||||
default:
|
default:
|
||||||
'text-primary-foreground hover:bg-primary/90 border border-foreground',
|
'text-primary-foreground hover:bg-primary/90 border border-foreground',
|
||||||
destructive:
|
destructive:
|
||||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
'border border-destructive text-destructive hover:bg-destructive/10',
|
||||||
outline:
|
outline:
|
||||||
'border border-input hover:bg-accent hover:text-accent-foreground',
|
'border border-input hover:bg-accent hover:text-accent-foreground',
|
||||||
secondary: 'text-secondary-foreground hover:bg-secondary/80 border',
|
secondary: 'text-secondary-foreground hover:bg-secondary/80 border',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const Textarea = React.forwardRef<
|
|||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
'flex min-h-[80px] w-full bg-transparent border px-3 py-2 text-sm ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
71
frontend/src/hooks/useProfiles.ts
Normal file
71
frontend/src/hooks/useProfiles.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { profilesApi } from '@/lib/api';
|
||||||
|
|
||||||
|
export type UseProfilesReturn = {
|
||||||
|
// data
|
||||||
|
profilesContent: string;
|
||||||
|
parsedProfiles: any | null;
|
||||||
|
profilesPath: string;
|
||||||
|
|
||||||
|
// status
|
||||||
|
isLoading: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
error: unknown;
|
||||||
|
isSaving: boolean;
|
||||||
|
|
||||||
|
// actions
|
||||||
|
refetch: () => void;
|
||||||
|
save: (content: string) => Promise<void>;
|
||||||
|
saveParsed: (obj: unknown) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useProfiles(): UseProfilesReturn {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { data, isLoading, isError, error, refetch } = useQuery({
|
||||||
|
queryKey: ['profiles'],
|
||||||
|
queryFn: () => profilesApi.load(),
|
||||||
|
staleTime: 1000 * 60, // 1 minute cache
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutateAsync: saveMutation, isPending: isSaving } = useMutation({
|
||||||
|
mutationFn: (content: string) => profilesApi.save(content),
|
||||||
|
onSuccess: (_, content) => {
|
||||||
|
// Optimistically update cache with new content
|
||||||
|
queryClient.setQueryData(['profiles'], (old: any) =>
|
||||||
|
old ? { ...old, content } : old
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const save = async (content: string): Promise<void> => {
|
||||||
|
await saveMutation(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsedProfiles = useMemo(() => {
|
||||||
|
if (!data?.content) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(data.content);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [data?.content]);
|
||||||
|
|
||||||
|
const saveParsed = async (obj: unknown) => {
|
||||||
|
await save(JSON.stringify(obj, null, 2));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
profilesContent: data?.content ?? '',
|
||||||
|
parsedProfiles,
|
||||||
|
profilesPath: data?.path ?? '',
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
isSaving,
|
||||||
|
refetch,
|
||||||
|
save,
|
||||||
|
saveParsed,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,862 +0,0 @@
|
|||||||
import { useCallback, useState, useEffect } from 'react';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from '@/components/ui/dropdown-menu';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { JSONEditor } from '@/components/ui/json-editor';
|
|
||||||
import { ChevronDown, Key, Loader2, Volume2 } from 'lucide-react';
|
|
||||||
import { ThemeMode, EditorType, SoundFile } from 'shared/types';
|
|
||||||
import type { BaseCodingAgent, ExecutorProfileId } from 'shared/types';
|
|
||||||
|
|
||||||
import { toPrettyCase } from '@/utils/string';
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { useUserSystem } from '@/components/config-provider';
|
|
||||||
import { GitHubLoginDialog } from '@/components/GitHubLoginDialog';
|
|
||||||
import { TaskTemplateManager } from '@/components/TaskTemplateManager';
|
|
||||||
import { profilesApi } from '@/lib/api';
|
|
||||||
|
|
||||||
export function Settings() {
|
|
||||||
const {
|
|
||||||
config,
|
|
||||||
updateConfig,
|
|
||||||
saveConfig,
|
|
||||||
loading,
|
|
||||||
updateAndSaveConfig,
|
|
||||||
profiles,
|
|
||||||
reloadSystem,
|
|
||||||
} = useUserSystem();
|
|
||||||
const [saving, setSaving] = useState(false);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [success, setSuccess] = useState(false);
|
|
||||||
const { setTheme } = useTheme();
|
|
||||||
const [showGitHubLogin, setShowGitHubLogin] = useState(false);
|
|
||||||
|
|
||||||
// Profiles editor state
|
|
||||||
const [profilesContent, setProfilesContent] = useState('');
|
|
||||||
const [profilesPath, setProfilesPath] = useState('');
|
|
||||||
const [profilesError, setProfilesError] = useState<string | null>(null);
|
|
||||||
const [profilesLoading, setProfilesLoading] = useState(false);
|
|
||||||
const [profilesSaving, setProfilesSaving] = useState(false);
|
|
||||||
const [profilesSuccess, setProfilesSuccess] = useState(false);
|
|
||||||
|
|
||||||
// Load profiles content on mount
|
|
||||||
useEffect(() => {
|
|
||||||
const loadProfiles = async () => {
|
|
||||||
setProfilesLoading(true);
|
|
||||||
try {
|
|
||||||
const result = await profilesApi.load();
|
|
||||||
setProfilesContent(result.content);
|
|
||||||
setProfilesPath(result.path);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to load profiles:', err);
|
|
||||||
setProfilesError('Failed to load profiles');
|
|
||||||
} finally {
|
|
||||||
setProfilesLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadProfiles();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const playSound = async (soundFile: SoundFile) => {
|
|
||||||
const audio = new Audio(`/api/sounds/${soundFile}`);
|
|
||||||
try {
|
|
||||||
await audio.play();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to play sound:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleProfilesChange = (value: string) => {
|
|
||||||
setProfilesContent(value);
|
|
||||||
setProfilesError(null);
|
|
||||||
|
|
||||||
// Validate JSON on change
|
|
||||||
if (value.trim()) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(value);
|
|
||||||
// Basic structure validation
|
|
||||||
if (!parsed.executors) {
|
|
||||||
setProfilesError('Invalid structure: must have a "executors" object');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof SyntaxError) {
|
|
||||||
setProfilesError('Invalid JSON format');
|
|
||||||
} else {
|
|
||||||
setProfilesError('Validation error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveProfiles = async () => {
|
|
||||||
setProfilesSaving(true);
|
|
||||||
setProfilesError(null);
|
|
||||||
setProfilesSuccess(false);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await profilesApi.save(profilesContent);
|
|
||||||
// Reload the system to get the updated profiles
|
|
||||||
await reloadSystem();
|
|
||||||
setProfilesSuccess(true);
|
|
||||||
setTimeout(() => setProfilesSuccess(false), 3000);
|
|
||||||
} catch (err: any) {
|
|
||||||
setProfilesError(err.message || 'Failed to save profiles');
|
|
||||||
} finally {
|
|
||||||
setProfilesSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
|
||||||
if (!config) return;
|
|
||||||
|
|
||||||
setSaving(true);
|
|
||||||
setError(null);
|
|
||||||
setSuccess(false);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Save the main configuration
|
|
||||||
const success = await saveConfig();
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
setSuccess(true);
|
|
||||||
// Update theme provider to reflect the saved theme
|
|
||||||
setTheme(config.theme);
|
|
||||||
|
|
||||||
setTimeout(() => setSuccess(false), 3000);
|
|
||||||
} else {
|
|
||||||
setError('Failed to save configuration');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setError('Failed to save configuration');
|
|
||||||
console.error('Error saving config:', err);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetDisclaimer = async () => {
|
|
||||||
if (!config) return;
|
|
||||||
|
|
||||||
updateConfig({ disclaimer_acknowledged: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetOnboarding = async () => {
|
|
||||||
if (!config) return;
|
|
||||||
|
|
||||||
updateConfig({ onboarding_acknowledged: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAuthenticated = !!(
|
|
||||||
config?.github?.username && config?.github?.oauth_token
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleLogout = useCallback(async () => {
|
|
||||||
if (!config) return;
|
|
||||||
updateAndSaveConfig({
|
|
||||||
github: {
|
|
||||||
...config.github,
|
|
||||||
oauth_token: null,
|
|
||||||
username: null,
|
|
||||||
primary_email: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [config, updateAndSaveConfig]);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<Loader2 className="h-8 w-8 animate-spin" />
|
|
||||||
<span className="ml-2">Loading settings...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
<Alert variant="destructive">
|
|
||||||
<AlertDescription>Failed to load settings. {error}</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold">Settings</h1>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Configure your preferences and application settings.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<Alert variant="destructive">
|
|
||||||
<AlertDescription>{error}</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{success && (
|
|
||||||
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200">
|
|
||||||
<AlertDescription className="font-medium">
|
|
||||||
✓ Settings saved successfully!
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid gap-6">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Appearance</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Customize how the application looks and feels.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="theme">Theme</Label>
|
|
||||||
<Select
|
|
||||||
value={config.theme}
|
|
||||||
onValueChange={(value: ThemeMode) => {
|
|
||||||
updateConfig({ theme: value });
|
|
||||||
setTheme(value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="theme">
|
|
||||||
<SelectValue placeholder="Select theme" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Object.values(ThemeMode).map((theme) => (
|
|
||||||
<SelectItem key={theme} value={theme}>
|
|
||||||
{toPrettyCase(theme)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Choose your preferred color scheme.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Task Execution</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Configure how tasks are executed and processed.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="executor">Default Executor Profile</Label>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<Select
|
|
||||||
value={config.executor_profile?.executor ?? ''}
|
|
||||||
onValueChange={(value: string) => {
|
|
||||||
const newProfile: ExecutorProfileId = {
|
|
||||||
executor: value as BaseCodingAgent,
|
|
||||||
variant: null,
|
|
||||||
};
|
|
||||||
updateConfig({
|
|
||||||
executor_profile: newProfile,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="executor">
|
|
||||||
<SelectValue placeholder="Select profile" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{profiles &&
|
|
||||||
Object.entries(profiles)
|
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
||||||
.map(([profileKey]) => (
|
|
||||||
<SelectItem key={profileKey} value={profileKey}>
|
|
||||||
{profileKey}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{/* Show variant selector if selected profile has variants */}
|
|
||||||
{(() => {
|
|
||||||
const currentProfileVariant = config.executor_profile;
|
|
||||||
const selectedProfile =
|
|
||||||
profiles?.[currentProfileVariant?.executor || ''];
|
|
||||||
const hasVariants =
|
|
||||||
selectedProfile &&
|
|
||||||
Object.keys(selectedProfile).length > 0;
|
|
||||||
|
|
||||||
if (hasVariants) {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full h-10 px-2 flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<span className="text-sm truncate flex-1 text-left">
|
|
||||||
{currentProfileVariant?.variant || 'DEFAULT'}
|
|
||||||
</span>
|
|
||||||
<ChevronDown className="h-4 w-4 ml-1 flex-shrink-0" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
{Object.entries(selectedProfile).map(
|
|
||||||
([variantLabel]) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={variantLabel}
|
|
||||||
onClick={() => {
|
|
||||||
const newProfile: ExecutorProfileId = {
|
|
||||||
executor:
|
|
||||||
currentProfileVariant?.executor || '',
|
|
||||||
variant: variantLabel,
|
|
||||||
};
|
|
||||||
updateConfig({
|
|
||||||
executor_profile: newProfile,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
currentProfileVariant?.variant ===
|
|
||||||
variantLabel
|
|
||||||
? 'bg-accent'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{variantLabel}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
} else if (selectedProfile) {
|
|
||||||
// Show disabled button when profile exists but has no variants
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full h-10 px-2 flex items-center justify-between"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
<span className="text-sm truncate flex-1 text-left">
|
|
||||||
Default
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Choose the default executor profile to use when creating a
|
|
||||||
task attempt.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Editor</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Configure which editor to open when viewing task attempts.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="editor">Preferred Editor</Label>
|
|
||||||
<Select
|
|
||||||
value={config.editor.editor_type}
|
|
||||||
onValueChange={(value: EditorType) =>
|
|
||||||
updateConfig({
|
|
||||||
editor: {
|
|
||||||
...config.editor,
|
|
||||||
editor_type: value,
|
|
||||||
custom_command:
|
|
||||||
value === EditorType.CUSTOM
|
|
||||||
? config.editor.custom_command
|
|
||||||
: null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="editor">
|
|
||||||
<SelectValue placeholder="Select editor" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Object.values(EditorType).map((type) => (
|
|
||||||
<SelectItem key={type} value={type}>
|
|
||||||
{toPrettyCase(type)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Choose your preferred code editor for opening task attempts.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{config.editor.editor_type === EditorType.CUSTOM && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="custom-command">Custom Command</Label>
|
|
||||||
<Input
|
|
||||||
id="custom-command"
|
|
||||||
placeholder="e.g., code, subl, vim"
|
|
||||||
value={config.editor.custom_command || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateConfig({
|
|
||||||
editor: {
|
|
||||||
...config.editor,
|
|
||||||
custom_command: e.target.value || null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Enter the command to run your custom editor. Use spaces for
|
|
||||||
arguments (e.g., "code --wait").
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Key className="h-5 w-5" />
|
|
||||||
GitHub Integration
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Configure GitHub settings for creating pull requests from task
|
|
||||||
attempts.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="github-token">Personal Access Token</Label>
|
|
||||||
<Input
|
|
||||||
id="github-token"
|
|
||||||
type="password"
|
|
||||||
placeholder="ghp_xxxxxxxxxxxxxxxxxxxx"
|
|
||||||
value={config.github.pat || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateConfig({
|
|
||||||
github: {
|
|
||||||
...config.github,
|
|
||||||
pat: e.target.value || null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
GitHub Personal Access Token with 'repo' permissions. Required
|
|
||||||
for creating pull requests.{' '}
|
|
||||||
<a
|
|
||||||
href="https://github.com/settings/tokens"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-600 hover:underline"
|
|
||||||
>
|
|
||||||
Create token here
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{config && isAuthenticated ? (
|
|
||||||
<div className="flex items-center justify-between gap-4">
|
|
||||||
<div>
|
|
||||||
<Label>Signed in as</Label>
|
|
||||||
<div className="text-lg font-mono">
|
|
||||||
{config.github.username}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" onClick={handleLogout}>
|
|
||||||
Log out
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Button onClick={() => setShowGitHubLogin(true)}>
|
|
||||||
Sign in with GitHub
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<GitHubLoginDialog
|
|
||||||
open={showGitHubLogin}
|
|
||||||
onOpenChange={setShowGitHubLogin}
|
|
||||||
/>
|
|
||||||
<div className="space-y-2 pt-4">
|
|
||||||
<Label htmlFor="default-pr-base">Default PR Base Branch</Label>
|
|
||||||
<Input
|
|
||||||
id="default-pr-base"
|
|
||||||
placeholder="main"
|
|
||||||
value={config.github.default_pr_base || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateConfig({
|
|
||||||
github: {
|
|
||||||
...config.github,
|
|
||||||
default_pr_base: e.target.value || null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Default base branch for pull requests. Defaults to 'main' if
|
|
||||||
not specified.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Notifications</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Configure how you receive notifications about task completion.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="sound-alerts"
|
|
||||||
checked={config.notifications.sound_enabled}
|
|
||||||
onCheckedChange={(checked: boolean) =>
|
|
||||||
updateConfig({
|
|
||||||
notifications: {
|
|
||||||
...config.notifications,
|
|
||||||
sound_enabled: checked,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label htmlFor="sound-alerts" className="cursor-pointer">
|
|
||||||
Sound Alerts
|
|
||||||
</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Play a sound when task attempts finish running.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{config.notifications.sound_enabled && (
|
|
||||||
<div className="space-y-2 ml-6">
|
|
||||||
<Label htmlFor="sound-file">Sound</Label>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Select
|
|
||||||
value={config.notifications.sound_file}
|
|
||||||
onValueChange={(value: SoundFile) =>
|
|
||||||
updateConfig({
|
|
||||||
notifications: {
|
|
||||||
...config.notifications,
|
|
||||||
sound_file: value,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="sound-file" className="flex-1">
|
|
||||||
<SelectValue placeholder="Select sound" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Object.values(SoundFile).map((soundFile) => (
|
|
||||||
<SelectItem key={soundFile} value={soundFile}>
|
|
||||||
{toPrettyCase(soundFile)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => playSound(config.notifications.sound_file)}
|
|
||||||
className="px-3"
|
|
||||||
>
|
|
||||||
<Volume2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Choose the sound to play when tasks complete. Click the
|
|
||||||
volume button to preview.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="push-notifications"
|
|
||||||
checked={config.notifications.push_enabled}
|
|
||||||
onCheckedChange={(checked: boolean) =>
|
|
||||||
updateConfig({
|
|
||||||
notifications: {
|
|
||||||
...config.notifications,
|
|
||||||
push_enabled: checked,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label
|
|
||||||
htmlFor="push-notifications"
|
|
||||||
className="cursor-pointer"
|
|
||||||
>
|
|
||||||
Push Notifications
|
|
||||||
</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Show system notifications when task attempts finish running.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Privacy</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Help improve Vibe-Kanban by sharing anonymous usage data.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="analytics-enabled"
|
|
||||||
checked={config.analytics_enabled ?? false}
|
|
||||||
onCheckedChange={(checked: boolean) =>
|
|
||||||
updateConfig({ analytics_enabled: checked })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label htmlFor="analytics-enabled" className="cursor-pointer">
|
|
||||||
Enable Telemetry
|
|
||||||
</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Enables anonymous usage events tracking to help improve the
|
|
||||||
application. No prompts or project information are
|
|
||||||
collected.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Task Templates</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Manage global task templates that can be used across all
|
|
||||||
projects.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<TaskTemplateManager isGlobal={true} />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
Agent Profiles
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Configure coding agent profiles with specific command-line
|
|
||||||
parameters.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
{profilesError && (
|
|
||||||
<Alert variant="destructive">
|
|
||||||
<AlertDescription>{profilesError}</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{profilesSuccess && (
|
|
||||||
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200">
|
|
||||||
<AlertDescription className="font-medium">
|
|
||||||
✓ Profiles saved successfully!
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="profiles-editor">
|
|
||||||
Profiles Configuration
|
|
||||||
</Label>
|
|
||||||
<JSONEditor
|
|
||||||
id="profiles-editor"
|
|
||||||
placeholder={
|
|
||||||
profilesLoading
|
|
||||||
? 'Loading profiles...'
|
|
||||||
: '{\n "profiles": [\n {\n "label": "my-custom-profile",\n "agent": "ClaudeCode",\n "command": {...}\n }\n ]\n}'
|
|
||||||
}
|
|
||||||
value={profilesLoading ? 'Loading...' : profilesContent}
|
|
||||||
onChange={handleProfilesChange}
|
|
||||||
disabled={profilesLoading}
|
|
||||||
minHeight={300}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{!profilesError && profilesPath && (
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
<span className="font-medium">Configuration file:</span>{' '}
|
|
||||||
<span className="font-mono text-xs">{profilesPath}</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Edit coding agent profiles. Each profile needs a unique
|
|
||||||
label, agent type, and command configuration.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end pt-2">
|
|
||||||
<Button
|
|
||||||
onClick={handleSaveProfiles}
|
|
||||||
disabled={
|
|
||||||
profilesSaving ||
|
|
||||||
profilesLoading ||
|
|
||||||
!!profilesError ||
|
|
||||||
profilesSuccess
|
|
||||||
}
|
|
||||||
className={
|
|
||||||
profilesSuccess ? 'bg-green-600 hover:bg-green-700' : ''
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{profilesSaving && (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
)}
|
|
||||||
{profilesSuccess && <span className="mr-2">✓</span>}
|
|
||||||
{profilesSuccess ? 'Profiles Saved!' : 'Save Profiles'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Safety & Disclaimers</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Manage safety warnings and acknowledgments.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>Disclaimer Status</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{config.disclaimer_acknowledged
|
|
||||||
? 'You have acknowledged the safety disclaimer.'
|
|
||||||
: 'The safety disclaimer has not been acknowledged.'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={resetDisclaimer}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
disabled={!config.disclaimer_acknowledged}
|
|
||||||
>
|
|
||||||
Reset Disclaimer
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Resetting the disclaimer will require you to acknowledge the
|
|
||||||
safety warning again.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>Onboarding Status</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{config.onboarding_acknowledged
|
|
||||||
? 'You have completed the onboarding process.'
|
|
||||||
: 'The onboarding process has not been completed.'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={resetOnboarding}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
disabled={!config.onboarding_acknowledged}
|
|
||||||
>
|
|
||||||
Reset Onboarding
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Resetting the onboarding will show the setup screen again.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>Telemetry Acknowledgment</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{config.telemetry_acknowledged
|
|
||||||
? 'You have acknowledged the telemetry notice.'
|
|
||||||
: 'The telemetry notice has not been acknowledged.'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
updateConfig({ telemetry_acknowledged: false })
|
|
||||||
}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
disabled={!config.telemetry_acknowledged}
|
|
||||||
>
|
|
||||||
Reset Acknowledgment
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Resetting the acknowledgment will require you to acknowledge
|
|
||||||
the telemetry notice again.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sticky save button */}
|
|
||||||
<div className="fixed bottom-0 left-0 right-0 bg-background/80 backdrop-blur-sm border-t p-4 z-10">
|
|
||||||
<div className="container mx-auto max-w-4xl flex justify-end">
|
|
||||||
<Button
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={saving || success}
|
|
||||||
className={success ? 'bg-green-600 hover:bg-green-700' : ''}
|
|
||||||
>
|
|
||||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
||||||
{success && <span className="mr-2">✓</span>}
|
|
||||||
{success ? 'Settings Saved!' : 'Save Settings'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Spacer to prevent content from being hidden behind sticky button */}
|
|
||||||
<div className="h-20"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
672
frontend/src/pages/settings/AgentSettings.tsx
Normal file
672
frontend/src/pages/settings/AgentSettings.tsx
Normal file
@@ -0,0 +1,672 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { JSONEditor } from '@/components/ui/json-editor';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
import { ExecutorConfigForm } from '@/components/ExecutorConfigForm';
|
||||||
|
import { useProfiles } from '@/hooks/useProfiles';
|
||||||
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
|
|
||||||
|
export function AgentSettings() {
|
||||||
|
// Use profiles hook for server state
|
||||||
|
const {
|
||||||
|
profilesContent: serverProfilesContent,
|
||||||
|
parsedProfiles: serverParsedProfiles,
|
||||||
|
profilesPath,
|
||||||
|
isLoading: profilesLoading,
|
||||||
|
isSaving: profilesSaving,
|
||||||
|
error: profilesError,
|
||||||
|
save: saveProfiles,
|
||||||
|
} = useProfiles();
|
||||||
|
|
||||||
|
const { reloadSystem } = useUserSystem();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
reloadSystem();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Local editor state (draft that may differ from server)
|
||||||
|
const [localProfilesContent, setLocalProfilesContent] = useState('');
|
||||||
|
const [profilesSuccess, setProfilesSuccess] = useState(false);
|
||||||
|
|
||||||
|
// Form-based editor state
|
||||||
|
const [useFormEditor, setUseFormEditor] = useState(true);
|
||||||
|
const [selectedExecutorType, setSelectedExecutorType] =
|
||||||
|
useState<string>('CLAUDE_CODE');
|
||||||
|
const [selectedConfiguration, setSelectedConfiguration] =
|
||||||
|
useState<string>('DEFAULT');
|
||||||
|
const [localParsedProfiles, setLocalParsedProfiles] = useState<any>(null);
|
||||||
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
|
||||||
|
// Create configuration dialog state
|
||||||
|
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||||
|
const [newConfigName, setNewConfigName] = useState('');
|
||||||
|
const [cloneFrom, setCloneFrom] = useState<string | null>(null);
|
||||||
|
const [dialogError, setDialogError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Delete configuration dialog state
|
||||||
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
|
const [configToDelete, setConfigToDelete] = useState<string | null>(null);
|
||||||
|
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Sync server state to local state when not dirty
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDirty && serverProfilesContent) {
|
||||||
|
setLocalProfilesContent(serverProfilesContent);
|
||||||
|
setLocalParsedProfiles(serverParsedProfiles);
|
||||||
|
}
|
||||||
|
}, [serverProfilesContent, serverParsedProfiles, isDirty]);
|
||||||
|
|
||||||
|
// Sync raw profiles with parsed profiles
|
||||||
|
const syncRawProfiles = (profiles: any) => {
|
||||||
|
setLocalProfilesContent(JSON.stringify(profiles, null, 2));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mark profiles as dirty
|
||||||
|
const markDirty = (nextProfiles: any) => {
|
||||||
|
setLocalParsedProfiles(nextProfiles);
|
||||||
|
syncRawProfiles(nextProfiles);
|
||||||
|
setIsDirty(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate configuration name
|
||||||
|
const validateConfigName = (name: string): string | null => {
|
||||||
|
const trimmedName = name.trim();
|
||||||
|
if (!trimmedName) return 'Configuration name cannot be empty';
|
||||||
|
if (trimmedName.length > 40)
|
||||||
|
return 'Configuration name must be 40 characters or less';
|
||||||
|
if (!/^[a-zA-Z0-9_-]+$/.test(trimmedName)) {
|
||||||
|
return 'Configuration name can only contain letters, numbers, underscores, and hyphens';
|
||||||
|
}
|
||||||
|
if (localParsedProfiles?.executors?.[selectedExecutorType]?.[trimmedName]) {
|
||||||
|
return 'A configuration with this name already exists';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open create dialog
|
||||||
|
const openCreateDialog = () => {
|
||||||
|
setNewConfigName('');
|
||||||
|
setCloneFrom(null);
|
||||||
|
setDialogError(null);
|
||||||
|
setShowCreateDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new configuration
|
||||||
|
const createConfiguration = (
|
||||||
|
executorType: string,
|
||||||
|
configName: string,
|
||||||
|
baseConfig?: string | null
|
||||||
|
) => {
|
||||||
|
if (!localParsedProfiles || !localParsedProfiles.executors) return;
|
||||||
|
|
||||||
|
const base =
|
||||||
|
baseConfig &&
|
||||||
|
localParsedProfiles.executors[executorType]?.[baseConfig]?.[executorType]
|
||||||
|
? localParsedProfiles.executors[executorType][baseConfig][executorType]
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const updatedProfiles = {
|
||||||
|
...localParsedProfiles,
|
||||||
|
executors: {
|
||||||
|
...localParsedProfiles.executors,
|
||||||
|
[executorType]: {
|
||||||
|
...localParsedProfiles.executors[executorType],
|
||||||
|
[configName]: {
|
||||||
|
[executorType]: base,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
markDirty(updatedProfiles);
|
||||||
|
setSelectedConfiguration(configName);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle create dialog submission
|
||||||
|
const handleCreateConfiguration = () => {
|
||||||
|
const validationError = validateConfigName(newConfigName);
|
||||||
|
if (validationError) {
|
||||||
|
setDialogError(validationError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createConfiguration(selectedExecutorType, newConfigName.trim(), cloneFrom);
|
||||||
|
setShowCreateDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open delete dialog
|
||||||
|
const openDeleteDialog = (configName: string) => {
|
||||||
|
setConfigToDelete(configName);
|
||||||
|
setDeleteError(null);
|
||||||
|
setShowDeleteDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle delete configuration
|
||||||
|
const handleDeleteConfiguration = async () => {
|
||||||
|
if (!localParsedProfiles || !configToDelete) {
|
||||||
|
setDeleteError('Invalid configuration data');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate that the configuration exists
|
||||||
|
if (
|
||||||
|
!localParsedProfiles.executors[selectedExecutorType]?.[configToDelete]
|
||||||
|
) {
|
||||||
|
setDeleteError(`Configuration "${configToDelete}" not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the last configuration
|
||||||
|
const currentConfigs = Object.keys(
|
||||||
|
localParsedProfiles.executors[selectedExecutorType] || {}
|
||||||
|
);
|
||||||
|
if (currentConfigs.length <= 1) {
|
||||||
|
setDeleteError('Cannot delete the last configuration');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the configuration from the executor
|
||||||
|
const remainingConfigs = {
|
||||||
|
...localParsedProfiles.executors[selectedExecutorType],
|
||||||
|
};
|
||||||
|
delete remainingConfigs[configToDelete];
|
||||||
|
|
||||||
|
const updatedProfiles = {
|
||||||
|
...localParsedProfiles,
|
||||||
|
executors: {
|
||||||
|
...localParsedProfiles.executors,
|
||||||
|
[selectedExecutorType]: remainingConfigs,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// If no configurations left, create a blank DEFAULT (should not happen due to check above)
|
||||||
|
if (Object.keys(remainingConfigs).length === 0) {
|
||||||
|
updatedProfiles.executors[selectedExecutorType] = {
|
||||||
|
DEFAULT: { [selectedExecutorType]: {} },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Save using hook
|
||||||
|
await saveProfiles(JSON.stringify(updatedProfiles, null, 2));
|
||||||
|
|
||||||
|
// Update local state and reset dirty flag
|
||||||
|
setLocalParsedProfiles(updatedProfiles);
|
||||||
|
setLocalProfilesContent(JSON.stringify(updatedProfiles, null, 2));
|
||||||
|
setIsDirty(false);
|
||||||
|
|
||||||
|
// Select the next available configuration
|
||||||
|
const nextConfigs = Object.keys(
|
||||||
|
updatedProfiles.executors[selectedExecutorType]
|
||||||
|
);
|
||||||
|
const nextSelected = nextConfigs[0] || 'DEFAULT';
|
||||||
|
setSelectedConfiguration(nextSelected);
|
||||||
|
|
||||||
|
// Show success and close dialog
|
||||||
|
setProfilesSuccess(true);
|
||||||
|
setTimeout(() => setProfilesSuccess(false), 3000);
|
||||||
|
setShowDeleteDialog(false);
|
||||||
|
} catch (saveError: any) {
|
||||||
|
console.error('Failed to save deletion to backend:', saveError);
|
||||||
|
setDeleteError(
|
||||||
|
saveError.message || 'Failed to save deletion. Please try again.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting configuration:', error);
|
||||||
|
setDeleteError('Failed to delete configuration. Please try again.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProfilesChange = (value: string) => {
|
||||||
|
setLocalProfilesContent(value);
|
||||||
|
setIsDirty(true);
|
||||||
|
|
||||||
|
// Validate JSON on change
|
||||||
|
if (value.trim()) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
setLocalParsedProfiles(parsed);
|
||||||
|
} catch (err) {
|
||||||
|
// Invalid JSON, keep local content but clear parsed
|
||||||
|
setLocalParsedProfiles(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveProfiles = async () => {
|
||||||
|
try {
|
||||||
|
const contentToSave =
|
||||||
|
useFormEditor && localParsedProfiles
|
||||||
|
? JSON.stringify(localParsedProfiles, null, 2)
|
||||||
|
: localProfilesContent;
|
||||||
|
|
||||||
|
await saveProfiles(contentToSave);
|
||||||
|
setProfilesSuccess(true);
|
||||||
|
setIsDirty(false);
|
||||||
|
setTimeout(() => setProfilesSuccess(false), 3000);
|
||||||
|
|
||||||
|
// Update the local content if using form editor
|
||||||
|
if (useFormEditor && localParsedProfiles) {
|
||||||
|
setLocalProfilesContent(contentToSave);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Failed to save profiles:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExecutorConfigChange = (
|
||||||
|
executorType: string,
|
||||||
|
configuration: string,
|
||||||
|
formData: any
|
||||||
|
) => {
|
||||||
|
if (!localParsedProfiles || !localParsedProfiles.executors) return;
|
||||||
|
|
||||||
|
// Update the parsed profiles with the new config
|
||||||
|
const updatedProfiles = {
|
||||||
|
...localParsedProfiles,
|
||||||
|
executors: {
|
||||||
|
...localParsedProfiles.executors,
|
||||||
|
[executorType]: {
|
||||||
|
...localParsedProfiles.executors[executorType],
|
||||||
|
[configuration]: {
|
||||||
|
[executorType]: formData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
markDirty(updatedProfiles);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExecutorConfigSave = async (formData: any) => {
|
||||||
|
if (!localParsedProfiles || !localParsedProfiles.executors) return;
|
||||||
|
|
||||||
|
// Update the parsed profiles with the saved config
|
||||||
|
const updatedProfiles = {
|
||||||
|
...localParsedProfiles,
|
||||||
|
executors: {
|
||||||
|
...localParsedProfiles.executors,
|
||||||
|
[selectedExecutorType]: {
|
||||||
|
...localParsedProfiles.executors[selectedExecutorType],
|
||||||
|
[selectedConfiguration]: {
|
||||||
|
[selectedExecutorType]: formData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
setLocalParsedProfiles(updatedProfiles);
|
||||||
|
|
||||||
|
// Save the updated profiles directly
|
||||||
|
try {
|
||||||
|
const contentToSave = JSON.stringify(updatedProfiles, null, 2);
|
||||||
|
|
||||||
|
await saveProfiles(contentToSave);
|
||||||
|
setProfilesSuccess(true);
|
||||||
|
setIsDirty(false);
|
||||||
|
setTimeout(() => setProfilesSuccess(false), 3000);
|
||||||
|
|
||||||
|
// Update the local content as well
|
||||||
|
setLocalProfilesContent(contentToSave);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Failed to save profiles:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (profilesLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center py-8">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin" />
|
||||||
|
<span className="ml-2">Loading agent configurations...</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{!!profilesError && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>
|
||||||
|
{profilesError instanceof Error
|
||||||
|
? profilesError.message
|
||||||
|
: String(profilesError)}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{profilesSuccess && (
|
||||||
|
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200">
|
||||||
|
<AlertDescription className="font-medium">
|
||||||
|
✓ Executor configurations saved successfully!
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Coding Agent Configurations</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Customize the behavior of coding agents with different
|
||||||
|
configurations.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{/* Editor type toggle */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="use-form-editor"
|
||||||
|
checked={!useFormEditor}
|
||||||
|
onCheckedChange={(checked) => setUseFormEditor(!checked)}
|
||||||
|
disabled={profilesLoading || !localParsedProfiles}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="use-form-editor">Edit JSON</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{useFormEditor &&
|
||||||
|
localParsedProfiles &&
|
||||||
|
localParsedProfiles.executors ? (
|
||||||
|
// Form-based editor
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="executor-type">Agent</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedExecutorType}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedExecutorType(value);
|
||||||
|
// Reset configuration selection when executor type changes
|
||||||
|
setSelectedConfiguration('DEFAULT');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="executor-type">
|
||||||
|
<SelectValue placeholder="Select executor type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.keys(localParsedProfiles.executors).map(
|
||||||
|
(type) => (
|
||||||
|
<SelectItem key={type} value={type}>
|
||||||
|
{type}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="configuration">Configuration</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Select
|
||||||
|
value={selectedConfiguration}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (value === '__create__') {
|
||||||
|
openCreateDialog();
|
||||||
|
} else {
|
||||||
|
setSelectedConfiguration(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
!localParsedProfiles.executors[selectedExecutorType]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="configuration">
|
||||||
|
<SelectValue placeholder="Select configuration" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.keys(
|
||||||
|
localParsedProfiles.executors[selectedExecutorType] ||
|
||||||
|
{}
|
||||||
|
).map((configuration) => (
|
||||||
|
<SelectItem key={configuration} value={configuration}>
|
||||||
|
{configuration}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value="__create__">
|
||||||
|
Create new...
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
className="h-10"
|
||||||
|
onClick={() => openDeleteDialog(selectedConfiguration)}
|
||||||
|
disabled={
|
||||||
|
profilesSaving ||
|
||||||
|
!localParsedProfiles.executors[selectedExecutorType] ||
|
||||||
|
Object.keys(
|
||||||
|
localParsedProfiles.executors[selectedExecutorType] ||
|
||||||
|
{}
|
||||||
|
).length <= 1
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
Object.keys(
|
||||||
|
localParsedProfiles.executors[selectedExecutorType] ||
|
||||||
|
{}
|
||||||
|
).length <= 1
|
||||||
|
? 'Cannot delete the last configuration'
|
||||||
|
: `Delete ${selectedConfiguration}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{localParsedProfiles.executors[selectedExecutorType]?.[
|
||||||
|
selectedConfiguration
|
||||||
|
]?.[selectedExecutorType] && (
|
||||||
|
<ExecutorConfigForm
|
||||||
|
executor={selectedExecutorType as any}
|
||||||
|
value={
|
||||||
|
localParsedProfiles.executors[selectedExecutorType][
|
||||||
|
selectedConfiguration
|
||||||
|
][selectedExecutorType] || {}
|
||||||
|
}
|
||||||
|
onChange={(formData) =>
|
||||||
|
handleExecutorConfigChange(
|
||||||
|
selectedExecutorType,
|
||||||
|
selectedConfiguration,
|
||||||
|
formData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onSave={handleExecutorConfigSave}
|
||||||
|
disabled={profilesSaving}
|
||||||
|
isSaving={profilesSaving}
|
||||||
|
isDirty={isDirty}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Raw JSON editor
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="profiles-editor">
|
||||||
|
Profiles Configuration (JSON)
|
||||||
|
</Label>
|
||||||
|
<JSONEditor
|
||||||
|
id="profiles-editor"
|
||||||
|
placeholder="Loading profiles..."
|
||||||
|
value={profilesLoading ? 'Loading...' : localProfilesContent}
|
||||||
|
onChange={handleProfilesChange}
|
||||||
|
disabled={profilesLoading}
|
||||||
|
minHeight={300}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!profilesError && profilesPath && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
<span className="font-medium">
|
||||||
|
Configuration file location:
|
||||||
|
</span>{' '}
|
||||||
|
<span className="font-mono text-xs">{profilesPath}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Save button for JSON editor mode */}
|
||||||
|
<div className="flex justify-end pt-4">
|
||||||
|
<Button
|
||||||
|
onClick={handleSaveProfiles}
|
||||||
|
disabled={!isDirty || profilesSaving || !!profilesError}
|
||||||
|
>
|
||||||
|
{profilesSaving && (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
)}
|
||||||
|
Save Executor Configurations
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Create Configuration Dialog */}
|
||||||
|
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New Configuration</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Add a new configuration for the {selectedExecutorType} executor.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config-name">Configuration Name</Label>
|
||||||
|
<Input
|
||||||
|
id="config-name"
|
||||||
|
value={newConfigName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNewConfigName(e.target.value);
|
||||||
|
setDialogError(null);
|
||||||
|
}}
|
||||||
|
placeholder="e.g., PRODUCTION, DEVELOPMENT"
|
||||||
|
maxLength={40}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="clone-from">Clone from (optional)</Label>
|
||||||
|
<Select
|
||||||
|
value={cloneFrom || '__blank__'}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setCloneFrom(value === '__blank__' ? null : value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="clone-from">
|
||||||
|
<SelectValue placeholder="Start blank or clone existing" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__blank__">Start blank</SelectItem>
|
||||||
|
{Object.keys(
|
||||||
|
localParsedProfiles?.executors?.[selectedExecutorType] || {}
|
||||||
|
).map((configuration) => (
|
||||||
|
<SelectItem key={configuration} value={configuration}>
|
||||||
|
Clone from {configuration}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{dialogError && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{dialogError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowCreateDialog(false)}
|
||||||
|
disabled={profilesSaving}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleCreateConfiguration}
|
||||||
|
disabled={!newConfigName.trim() || profilesSaving}
|
||||||
|
>
|
||||||
|
Create Configuration
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Delete Configuration Dialog */}
|
||||||
|
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Configuration?</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
This will permanently remove "{configToDelete}" from the{' '}
|
||||||
|
{selectedExecutorType} executor. You can't undo this action.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{deleteError && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{deleteError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowDeleteDialog(false)}
|
||||||
|
disabled={profilesSaving}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleDeleteConfiguration}
|
||||||
|
disabled={profilesSaving}
|
||||||
|
>
|
||||||
|
{profilesSaving && (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
)}
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
580
frontend/src/pages/settings/GeneralSettings.tsx
Normal file
580
frontend/src/pages/settings/GeneralSettings.tsx
Normal file
@@ -0,0 +1,580 @@
|
|||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { ChevronDown, Key, Loader2, Volume2 } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
ThemeMode,
|
||||||
|
EditorType,
|
||||||
|
SoundFile,
|
||||||
|
ExecutorProfileId,
|
||||||
|
BaseCodingAgent,
|
||||||
|
} from 'shared/types';
|
||||||
|
|
||||||
|
import { toPrettyCase } from '@/utils/string';
|
||||||
|
import { useTheme } from '@/components/theme-provider';
|
||||||
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
|
import { GitHubLoginDialog } from '@/components/GitHubLoginDialog';
|
||||||
|
import { TaskTemplateManager } from '@/components/TaskTemplateManager';
|
||||||
|
|
||||||
|
export function GeneralSettings() {
|
||||||
|
const {
|
||||||
|
config,
|
||||||
|
updateConfig,
|
||||||
|
saveConfig,
|
||||||
|
loading,
|
||||||
|
updateAndSaveConfig,
|
||||||
|
profiles,
|
||||||
|
} = useUserSystem();
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
const [showGitHubLogin, setShowGitHubLogin] = useState(false);
|
||||||
|
|
||||||
|
const playSound = async (soundFile: SoundFile) => {
|
||||||
|
const audio = new Audio(`/api/sounds/${soundFile}`);
|
||||||
|
try {
|
||||||
|
await audio.play();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to play sound:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await saveConfig();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
setSuccess(true);
|
||||||
|
setTheme(config.theme);
|
||||||
|
setTimeout(() => setSuccess(false), 3000);
|
||||||
|
} else {
|
||||||
|
setError('Failed to save configuration');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to save configuration');
|
||||||
|
console.error('Error saving config:', err);
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetDisclaimer = async () => {
|
||||||
|
if (!config) return;
|
||||||
|
updateConfig({ disclaimer_acknowledged: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetOnboarding = async () => {
|
||||||
|
if (!config) return;
|
||||||
|
updateConfig({ onboarding_acknowledged: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAuthenticated = !!(
|
||||||
|
config?.github?.username && config?.github?.oauth_token
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleLogout = useCallback(async () => {
|
||||||
|
if (!config) return;
|
||||||
|
updateAndSaveConfig({
|
||||||
|
github: {
|
||||||
|
...config.github,
|
||||||
|
oauth_token: null,
|
||||||
|
username: null,
|
||||||
|
primary_email: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [config, updateAndSaveConfig]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center py-8">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin" />
|
||||||
|
<span className="ml-2">Loading settings...</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return (
|
||||||
|
<div className="py-8">
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>Failed to load configuration.</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{success && (
|
||||||
|
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200">
|
||||||
|
<AlertDescription className="font-medium">
|
||||||
|
✓ Settings saved successfully!
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Appearance</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Customize how the application looks and feels.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="theme">Theme</Label>
|
||||||
|
<Select
|
||||||
|
value={config.theme}
|
||||||
|
onValueChange={(value: ThemeMode) =>
|
||||||
|
updateConfig({ theme: value })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="theme">
|
||||||
|
<SelectValue placeholder="Select theme" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.values(ThemeMode).map((theme) => (
|
||||||
|
<SelectItem key={theme} value={theme}>
|
||||||
|
{toPrettyCase(theme)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Choose your preferred color scheme.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Task Execution</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure how tasks are executed and processed.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="executor">Default Executor Profile</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<Select
|
||||||
|
value={config.executor_profile?.executor ?? ''}
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
const variants = profiles?.[value];
|
||||||
|
const keepCurrentVariant =
|
||||||
|
variants &&
|
||||||
|
config.executor_profile?.variant &&
|
||||||
|
variants[config.executor_profile.variant];
|
||||||
|
|
||||||
|
const newProfile: ExecutorProfileId = {
|
||||||
|
executor: value as BaseCodingAgent,
|
||||||
|
variant: keepCurrentVariant
|
||||||
|
? config.executor_profile!.variant
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
updateConfig({
|
||||||
|
executor_profile: newProfile,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!profiles}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="executor">
|
||||||
|
<SelectValue placeholder="Select profile" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{profiles &&
|
||||||
|
Object.entries(profiles)
|
||||||
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
|
.map(([profileKey]) => (
|
||||||
|
<SelectItem key={profileKey} value={profileKey}>
|
||||||
|
{profileKey}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{/* Show variant selector if selected profile has variants */}
|
||||||
|
{(() => {
|
||||||
|
const currentProfileVariant = config.executor_profile;
|
||||||
|
const selectedProfile =
|
||||||
|
profiles?.[currentProfileVariant?.executor || ''];
|
||||||
|
const hasVariants =
|
||||||
|
selectedProfile && Object.keys(selectedProfile).length > 0;
|
||||||
|
|
||||||
|
if (hasVariants) {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full h-10 px-2 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span className="text-sm truncate flex-1 text-left">
|
||||||
|
{currentProfileVariant?.variant || 'DEFAULT'}
|
||||||
|
</span>
|
||||||
|
<ChevronDown className="h-4 w-4 ml-1 flex-shrink-0" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
{Object.entries(selectedProfile).map(
|
||||||
|
([variantLabel]) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={variantLabel}
|
||||||
|
onClick={() => {
|
||||||
|
const newProfile: ExecutorProfileId = {
|
||||||
|
executor: currentProfileVariant!.executor,
|
||||||
|
variant: variantLabel,
|
||||||
|
};
|
||||||
|
updateConfig({
|
||||||
|
executor_profile: newProfile,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
currentProfileVariant?.variant === variantLabel
|
||||||
|
? 'bg-accent'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{variantLabel}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
} else if (selectedProfile) {
|
||||||
|
// Show disabled button when profile exists but has no variants
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full h-10 px-2 flex items-center justify-between"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<span className="text-sm truncate flex-1 text-left">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Choose the default executor profile to use when creating a task
|
||||||
|
attempt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Editor</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure your code editing experience.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="editor-type">Editor Type</Label>
|
||||||
|
<Select
|
||||||
|
value={config.editor.editor_type}
|
||||||
|
onValueChange={(value: EditorType) =>
|
||||||
|
updateConfig({
|
||||||
|
editor: { ...config.editor, editor_type: value },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="editor-type">
|
||||||
|
<SelectValue placeholder="Select editor" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.values(EditorType).map((editor) => (
|
||||||
|
<SelectItem key={editor} value={editor}>
|
||||||
|
{toPrettyCase(editor)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Choose your preferred code editor interface.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Key className="h-5 w-5" />
|
||||||
|
GitHub Integration
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Connect your GitHub account to enable advanced features.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{isAuthenticated ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between p-4 border rounded-lg">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">
|
||||||
|
Connected as {config.github.username}
|
||||||
|
</p>
|
||||||
|
{config.github.primary_email && (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{config.github.primary_email}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
Manage <ChevronDown className="ml-1 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={handleLogout}>
|
||||||
|
Disconnect
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Connect your GitHub account to access private repositories and
|
||||||
|
enable advanced Git operations.
|
||||||
|
</p>
|
||||||
|
<Button onClick={() => setShowGitHubLogin(true)}>
|
||||||
|
Connect GitHub Account
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notifications</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Control when and how you receive notifications.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="sound-enabled"
|
||||||
|
checked={config.notifications.sound_enabled}
|
||||||
|
onCheckedChange={(checked: boolean) =>
|
||||||
|
updateConfig({
|
||||||
|
notifications: {
|
||||||
|
...config.notifications,
|
||||||
|
sound_enabled: checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="sound-enabled" className="cursor-pointer">
|
||||||
|
Sound Notifications
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Play a sound when task attempts finish running.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{config.notifications.sound_enabled && (
|
||||||
|
<div className="ml-6 space-y-2">
|
||||||
|
<Label htmlFor="sound-file">Sound</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Select
|
||||||
|
value={config.notifications.sound_file}
|
||||||
|
onValueChange={(value: SoundFile) =>
|
||||||
|
updateConfig({
|
||||||
|
notifications: {
|
||||||
|
...config.notifications,
|
||||||
|
sound_file: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="sound-file" className="flex-1">
|
||||||
|
<SelectValue placeholder="Select sound" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.values(SoundFile).map((soundFile) => (
|
||||||
|
<SelectItem key={soundFile} value={soundFile}>
|
||||||
|
{toPrettyCase(soundFile)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => playSound(config.notifications.sound_file)}
|
||||||
|
className="px-3"
|
||||||
|
>
|
||||||
|
<Volume2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Choose the sound to play when tasks complete. Click the volume
|
||||||
|
button to preview.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="push-notifications"
|
||||||
|
checked={config.notifications.push_enabled}
|
||||||
|
onCheckedChange={(checked: boolean) =>
|
||||||
|
updateConfig({
|
||||||
|
notifications: {
|
||||||
|
...config.notifications,
|
||||||
|
push_enabled: checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="push-notifications" className="cursor-pointer">
|
||||||
|
Push Notifications
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Show system notifications when task attempts finish running.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Privacy</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Help improve Vibe-Kanban by sharing anonymous usage data.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="analytics-enabled"
|
||||||
|
checked={config.analytics_enabled ?? false}
|
||||||
|
onCheckedChange={(checked: boolean) =>
|
||||||
|
updateConfig({ analytics_enabled: checked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="analytics-enabled" className="cursor-pointer">
|
||||||
|
Enable Telemetry
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Enables anonymous usage events tracking to help improve the
|
||||||
|
application. No prompts or project information are collected.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Task Templates</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage global task templates that can be used across all projects.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<TaskTemplateManager isGlobal={true} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Safety & Disclaimers</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Reset acknowledgments for safety warnings and onboarding.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Disclaimer Acknowledgment</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Reset the safety disclaimer to show it again on next startup.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" onClick={resetDisclaimer}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Onboarding</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Reset the onboarding flow to show it again on next startup.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" onClick={resetOnboarding}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Sticky Save Button */}
|
||||||
|
<div className="sticky bottom-0 z-10 bg-background/80 backdrop-blur-sm border-t pt-4">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button onClick={handleSave} disabled={saving}>
|
||||||
|
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
Save Settings
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<GitHubLoginDialog
|
||||||
|
open={showGitHubLogin}
|
||||||
|
onOpenChange={(open) => setShowGitHubLogin(open)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,10 +21,10 @@ import { Loader2 } from 'lucide-react';
|
|||||||
import { McpConfig } from 'shared/types';
|
import { McpConfig } from 'shared/types';
|
||||||
import type { BaseCodingAgent, ExecutorConfig } from 'shared/types';
|
import type { BaseCodingAgent, ExecutorConfig } from 'shared/types';
|
||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import { mcpServersApi } from '../lib/api';
|
import { mcpServersApi } from '@/lib/api';
|
||||||
import { McpConfigStrategyGeneral } from '../lib/mcp-strategies';
|
import { McpConfigStrategyGeneral } from '@/lib/mcp-strategies';
|
||||||
|
|
||||||
export function McpServers() {
|
export function McpSettings() {
|
||||||
const { config, profiles } = useUserSystem();
|
const { config, profiles } = useUserSystem();
|
||||||
const [mcpServers, setMcpServers] = useState('{}');
|
const [mcpServers, setMcpServers] = useState('{}');
|
||||||
const [mcpConfig, setMcpConfig] = useState<McpConfig | null>(null);
|
const [mcpConfig, setMcpConfig] = useState<McpConfig | null>(null);
|
||||||
@@ -202,7 +202,7 @@ export function McpServers() {
|
|||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<div className="py-8">
|
||||||
<Alert variant="destructive">
|
<Alert variant="destructive">
|
||||||
<AlertDescription>Failed to load configuration.</AlertDescription>
|
<AlertDescription>Failed to load configuration.</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
@@ -211,161 +211,149 @@ export function McpServers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
<div className="space-y-6">
|
||||||
<div className="space-y-6">
|
{mcpError && (
|
||||||
<div>
|
<Alert variant="destructive">
|
||||||
<h1 className="text-3xl font-bold">MCP Servers</h1>
|
<AlertDescription>
|
||||||
<p className="text-muted-foreground">
|
MCP Configuration Error: {mcpError}
|
||||||
Configure MCP servers to extend coding agent capabilities.
|
</AlertDescription>
|
||||||
</p>
|
</Alert>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{mcpError && (
|
{success && (
|
||||||
<Alert variant="destructive">
|
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200">
|
||||||
<AlertDescription>
|
<AlertDescription className="font-medium">
|
||||||
MCP Configuration Error: {mcpError}
|
✓ MCP configuration saved successfully!
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{success && (
|
<Card>
|
||||||
<Alert className="border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200">
|
<CardHeader>
|
||||||
<AlertDescription className="font-medium">
|
<CardTitle>MCP Server Configuration</CardTitle>
|
||||||
✓ MCP configuration saved successfully!
|
<CardDescription>
|
||||||
</AlertDescription>
|
Configure Model Context Protocol servers to extend coding agent
|
||||||
</Alert>
|
capabilities with custom tools and resources.
|
||||||
)}
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="mcp-executor">Agent</Label>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
selectedProfile
|
||||||
|
? Object.keys(profiles || {}).find(
|
||||||
|
(key) => profiles![key] === selectedProfile
|
||||||
|
) || ''
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
const profile = profiles?.[value];
|
||||||
|
if (profile) setSelectedProfile(profile);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="mcp-executor">
|
||||||
|
<SelectValue placeholder="Select executor" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{profiles &&
|
||||||
|
Object.entries(profiles)
|
||||||
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
|
.map(([profileKey]) => (
|
||||||
|
<SelectItem key={profileKey} value={profileKey}>
|
||||||
|
{profileKey}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Choose which agent to configure MCP servers for.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card>
|
{mcpError && mcpError.includes('does not support MCP') ? (
|
||||||
<CardHeader>
|
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-950">
|
||||||
<CardTitle>Configuration</CardTitle>
|
<div className="flex">
|
||||||
<CardDescription>
|
<div className="ml-3">
|
||||||
Configure MCP servers for different coding agents to extend their
|
<h3 className="text-sm font-medium text-amber-800 dark:text-amber-200">
|
||||||
capabilities with custom tools and resources.
|
MCP Not Supported
|
||||||
</CardDescription>
|
</h3>
|
||||||
</CardHeader>
|
<div className="mt-2 text-sm text-amber-700 dark:text-amber-300">
|
||||||
<CardContent className="space-y-4">
|
<p>{mcpError}</p>
|
||||||
<div className="space-y-2">
|
<p className="mt-1">
|
||||||
<Label htmlFor="mcp-executor">Profile</Label>
|
To use MCP servers, please select a different executor
|
||||||
<Select
|
that supports MCP (Claude, Amp, Gemini, Codex, or
|
||||||
value={
|
Opencode) above.
|
||||||
selectedProfile
|
</p>
|
||||||
? Object.keys(profiles || {}).find(
|
|
||||||
(key) => profiles![key] === selectedProfile
|
|
||||||
) || ''
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
onValueChange={(value: string) => {
|
|
||||||
const profile = profiles?.[value];
|
|
||||||
if (profile) setSelectedProfile(profile);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="mcp-executor">
|
|
||||||
<SelectValue placeholder="Select executor" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{profiles &&
|
|
||||||
Object.entries(profiles)
|
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
||||||
.map(([profileKey]) => (
|
|
||||||
<SelectItem key={profileKey} value={profileKey}>
|
|
||||||
{profileKey}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Choose which profile to configure MCP servers for.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{mcpError && mcpError.includes('does not support MCP') ? (
|
|
||||||
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-950">
|
|
||||||
<div className="flex">
|
|
||||||
<div className="ml-3">
|
|
||||||
<h3 className="text-sm font-medium text-amber-800 dark:text-amber-200">
|
|
||||||
MCP Not Supported
|
|
||||||
</h3>
|
|
||||||
<div className="mt-2 text-sm text-amber-700 dark:text-amber-300">
|
|
||||||
<p>{mcpError}</p>
|
|
||||||
<p className="mt-1">
|
|
||||||
To use MCP servers, please select a different profile
|
|
||||||
that supports MCP (Claude, Amp, Gemini, Codex, or
|
|
||||||
Opencode) above.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<div className="space-y-2">
|
) : (
|
||||||
<Label htmlFor="mcp-servers">MCP Server Configuration</Label>
|
<div className="space-y-2">
|
||||||
<JSONEditor
|
<Label htmlFor="mcp-servers">Server Configuration (JSON)</Label>
|
||||||
id="mcp-servers"
|
<JSONEditor
|
||||||
placeholder={
|
id="mcp-servers"
|
||||||
mcpLoading
|
placeholder={
|
||||||
? 'Loading current configuration...'
|
mcpLoading
|
||||||
: '{\n "server-name": {\n "type": "stdio",\n "command": "your-command",\n "args": ["arg1", "arg2"]\n }\n}'
|
? 'Loading current configuration...'
|
||||||
}
|
: '{\n "server-name": {\n "type": "stdio",\n "command": "your-command",\n "args": ["arg1", "arg2"]\n }\n}'
|
||||||
value={mcpLoading ? 'Loading...' : mcpServers}
|
}
|
||||||
onChange={handleMcpServersChange}
|
value={mcpLoading ? 'Loading...' : mcpServers}
|
||||||
disabled={mcpLoading}
|
onChange={handleMcpServersChange}
|
||||||
minHeight={300}
|
disabled={mcpLoading}
|
||||||
/>
|
minHeight={300}
|
||||||
{mcpError && !mcpError.includes('does not support MCP') && (
|
/>
|
||||||
<p className="text-sm text-destructive dark:text-red-400">
|
{mcpError && !mcpError.includes('does not support MCP') && (
|
||||||
{mcpError}
|
<p className="text-sm text-destructive dark:text-red-400">
|
||||||
</p>
|
{mcpError}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{mcpLoading ? (
|
||||||
|
'Loading current MCP server configuration...'
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
Changes will be saved to:
|
||||||
|
{mcpConfigPath && (
|
||||||
|
<span className="ml-2 font-mono text-xs">
|
||||||
|
{mcpConfigPath}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{mcpLoading ? (
|
|
||||||
'Loading current MCP server configuration...'
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
Changes will be saved to:
|
|
||||||
{mcpConfigPath && (
|
|
||||||
<span className="ml-2 font-mono text-xs">
|
|
||||||
{mcpConfigPath}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-4">
|
|
||||||
<Button
|
|
||||||
onClick={handleConfigureVibeKanban}
|
|
||||||
disabled={mcpApplying || mcpLoading || !selectedProfile}
|
|
||||||
className="w-64"
|
|
||||||
>
|
|
||||||
Add Vibe-Kanban MCP
|
|
||||||
</Button>
|
|
||||||
<p className="text-sm text-muted-foreground mt-2">
|
|
||||||
Automatically adds the Vibe-Kanban MCP server.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Sticky save button */}
|
<div className="pt-4">
|
||||||
<div className="fixed bottom-0 left-0 right-0 bg-background/80 backdrop-blur-sm border-t p-4 z-10">
|
<Button
|
||||||
<div className="container mx-auto max-w-4xl flex justify-end">
|
onClick={handleConfigureVibeKanban}
|
||||||
<Button
|
disabled={mcpApplying || mcpLoading || !selectedProfile}
|
||||||
onClick={handleApplyMcpServers}
|
className="w-64"
|
||||||
disabled={mcpApplying || mcpLoading || !!mcpError || success}
|
>
|
||||||
className={success ? 'bg-green-600 hover:bg-green-700' : ''}
|
Add Vibe-Kanban MCP
|
||||||
>
|
</Button>
|
||||||
{mcpApplying && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
<p className="text-sm text-muted-foreground mt-2">
|
||||||
{success && <span className="mr-2">✓</span>}
|
Automatically adds the Vibe-Kanban MCP server configuration.
|
||||||
{success ? 'Settings Saved!' : 'Save Settings'}
|
</p>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Sticky Save Button */}
|
||||||
|
<div className="sticky bottom-0 z-10 bg-background/80 backdrop-blur-sm border-t pt-4">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleApplyMcpServers}
|
||||||
|
disabled={mcpApplying || mcpLoading || !!mcpError || success}
|
||||||
|
className={success ? 'bg-green-600 hover:bg-green-700' : ''}
|
||||||
|
>
|
||||||
|
{mcpApplying && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
{success && <span className="mr-2">✓</span>}
|
||||||
|
{success ? 'Settings Saved!' : 'Save MCP Configuration'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Spacer to prevent content from being hidden behind sticky button */}
|
|
||||||
<div className="h-20"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
71
frontend/src/pages/settings/SettingsLayout.tsx
Normal file
71
frontend/src/pages/settings/SettingsLayout.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { NavLink, Outlet } from 'react-router-dom';
|
||||||
|
import { Settings, Cpu, Server } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const settingsNavigation = [
|
||||||
|
{
|
||||||
|
path: 'general',
|
||||||
|
label: 'General',
|
||||||
|
icon: Settings,
|
||||||
|
description: 'Theme, notifications, and preferences',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'agents',
|
||||||
|
label: 'Agents',
|
||||||
|
icon: Cpu,
|
||||||
|
description: 'Coding agent configurations',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'mcp',
|
||||||
|
label: 'MCP Servers',
|
||||||
|
icon: Server,
|
||||||
|
description: 'Model Context Protocol servers',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SettingsLayout() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-8">
|
||||||
|
<div className="flex flex-col lg:flex-row gap-8">
|
||||||
|
{/* Sidebar Navigation */}
|
||||||
|
<aside className="w-full lg:w-64 lg:shrink-0 lg:sticky lg:top-8 lg:h-fit lg:max-h-[calc(100vh-4rem)] lg:overflow-y-auto">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h2 className="px-3 py-2 text-lg font-semibold">Settings</h2>
|
||||||
|
<nav className="space-y-1">
|
||||||
|
{settingsNavigation.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
key={item.path}
|
||||||
|
to={item.path}
|
||||||
|
end
|
||||||
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
|
'flex items-start gap-3 px-3 py-2 text-sm transition-colors',
|
||||||
|
'hover:text-accent-foreground',
|
||||||
|
isActive
|
||||||
|
? 'text-primary-foreground'
|
||||||
|
: 'text-secondary-foreground'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon className="h-4 w-4 mt-0.5 shrink-0" />
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="font-medium">{item.label}</div>
|
||||||
|
<div>{item.description}</div>
|
||||||
|
</div>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="flex-1 min-w-0">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
frontend/src/pages/settings/index.ts
Normal file
4
frontend/src/pages/settings/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { SettingsLayout } from './SettingsLayout';
|
||||||
|
export { GeneralSettings } from './GeneralSettings';
|
||||||
|
export { AgentSettings } from './AgentSettings';
|
||||||
|
export { McpSettings } from './McpSettings';
|
||||||
8
frontend/src/types/virtual-executor-schemas.d.ts
vendored
Normal file
8
frontend/src/types/virtual-executor-schemas.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
declare module 'virtual:executor-schemas' {
|
||||||
|
import type { RJSFSchema } from '@rjsf/utils';
|
||||||
|
import type { BaseCodingAgent } from '@/shared/types';
|
||||||
|
|
||||||
|
const schemas: Record<BaseCodingAgent, RJSFSchema>;
|
||||||
|
export { schemas };
|
||||||
|
export default schemas;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ module.exports = {
|
|||||||
'./components/**/*.{ts,tsx}',
|
'./components/**/*.{ts,tsx}',
|
||||||
'./app/**/*.{ts,tsx}',
|
'./app/**/*.{ts,tsx}',
|
||||||
'./src/**/*.{ts,tsx}',
|
'./src/**/*.{ts,tsx}',
|
||||||
|
"node_modules/@rjsf/shadcn/src/**/*.{js,ts,jsx,tsx,mdx}"
|
||||||
],
|
],
|
||||||
safelist: [
|
safelist: [
|
||||||
'xl:hidden',
|
'xl:hidden',
|
||||||
|
|||||||
@@ -1,32 +1,77 @@
|
|||||||
|
// vite.config.ts
|
||||||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig, Plugin } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
import path from 'path'
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
function executorSchemasPlugin(): Plugin {
|
||||||
|
const VIRTUAL_ID = "virtual:executor-schemas";
|
||||||
|
const RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ID;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "executor-schemas-plugin",
|
||||||
|
resolveId(id) {
|
||||||
|
if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID; // keep it virtual
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
load(id) {
|
||||||
|
if (id !== RESOLVED_VIRTUAL_ID) return null;
|
||||||
|
|
||||||
|
const schemasDir = path.resolve(__dirname, "../shared/schemas");
|
||||||
|
const files = fs.existsSync(schemasDir)
|
||||||
|
? fs.readdirSync(schemasDir).filter((f) => f.endsWith(".json"))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const imports: string[] = [];
|
||||||
|
const entries: string[] = [];
|
||||||
|
|
||||||
|
files.forEach((file, i) => {
|
||||||
|
const varName = `__schema_${i}`;
|
||||||
|
const importPath = `shared/schemas/${file}`; // uses your alias
|
||||||
|
const key = file.replace(/\.json$/, "").toUpperCase(); // claude_code -> CLAUDE_CODE
|
||||||
|
imports.push(`import ${varName} from "${importPath}";`);
|
||||||
|
entries.push(` "${key}": ${varName}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// IMPORTANT: pure JS (no TS types), and quote keys.
|
||||||
|
const code = `
|
||||||
|
${imports.join("\n")}
|
||||||
|
|
||||||
|
export const schemas = {
|
||||||
|
${entries.join(",\n")}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default schemas;
|
||||||
|
`;
|
||||||
|
return code;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), sentryVitePlugin({
|
plugins: [
|
||||||
org: "bloop-ai",
|
react(),
|
||||||
project: "vibe-kanban"
|
sentryVitePlugin({ org: "bloop-ai", project: "vibe-kanban" }),
|
||||||
})],
|
executorSchemasPlugin(),
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
"shared": path.resolve(__dirname, "../shared"),
|
shared: path.resolve(__dirname, "../shared"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
port: parseInt(process.env.FRONTEND_PORT || '3000'),
|
port: parseInt(process.env.FRONTEND_PORT || "3000"),
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
"/api": {
|
||||||
target: `http://localhost:${process.env.BACKEND_PORT || '3001'}`,
|
target: `http://localhost:${process.env.BACKEND_PORT || "3001"}`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fs: {
|
||||||
|
allow: [path.resolve(__dirname, "."), path.resolve(__dirname, "..")],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
build: { sourcemap: true },
|
||||||
build: {
|
});
|
||||||
sourcemap: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
585
pnpm-lock.yaml
generated
585
pnpm-lock.yaml
generated
@@ -71,6 +71,9 @@ importers:
|
|||||||
'@radix-ui/react-tooltip':
|
'@radix-ui/react-tooltip':
|
||||||
specifier: ^1.2.7
|
specifier: ^1.2.7
|
||||||
version: 1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@rjsf/shadcn':
|
||||||
|
specifier: 6.0.0-beta.10
|
||||||
|
version: 6.0.0-beta.10(@rjsf/core@6.0.0-beta.11(@rjsf/utils@6.0.0-beta.11(react@18.3.1))(react@18.3.1))(@rjsf/utils@6.0.0-beta.11(react@18.3.1))(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17)
|
||||||
'@sentry/react':
|
'@sentry/react':
|
||||||
specifier: ^9.34.0
|
specifier: ^9.34.0
|
||||||
version: 9.34.0(react@18.3.1)
|
version: 9.34.0(react@18.3.1)
|
||||||
@@ -147,6 +150,15 @@ importers:
|
|||||||
specifier: ^4.5.4
|
specifier: ^4.5.4
|
||||||
version: 4.5.7(@types/react@18.3.23)(react@18.3.1)
|
version: 4.5.7(@types/react@18.3.23)(react@18.3.1)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@rjsf/core':
|
||||||
|
specifier: 6.0.0-beta.11
|
||||||
|
version: 6.0.0-beta.11(@rjsf/utils@6.0.0-beta.11(react@18.3.1))(react@18.3.1)
|
||||||
|
'@rjsf/utils':
|
||||||
|
specifier: 6.0.0-beta.11
|
||||||
|
version: 6.0.0-beta.11(react@18.3.1)
|
||||||
|
'@rjsf/validator-ajv8':
|
||||||
|
specifier: 6.0.0-beta.11
|
||||||
|
version: 6.0.0-beta.11(@rjsf/utils@6.0.0-beta.11(react@18.3.1))
|
||||||
'@tailwindcss/container-queries':
|
'@tailwindcss/container-queries':
|
||||||
specifier: ^0.1.1
|
specifier: ^0.1.1
|
||||||
version: 0.1.1(tailwindcss@3.4.17)
|
version: 0.1.1(tailwindcss@3.4.17)
|
||||||
@@ -805,6 +817,9 @@ packages:
|
|||||||
'@radix-ui/primitive@1.1.2':
|
'@radix-ui/primitive@1.1.2':
|
||||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.3':
|
||||||
|
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||||
|
|
||||||
'@radix-ui/react-arrow@1.1.7':
|
'@radix-ui/react-arrow@1.1.7':
|
||||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -818,6 +833,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-checkbox@1.3.3':
|
||||||
|
resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-collection@1.1.7':
|
'@radix-ui/react-collection@1.1.7':
|
||||||
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -849,6 +877,19 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-dialog@1.1.15':
|
||||||
|
resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-direction@1.1.1':
|
'@radix-ui/react-direction@1.1.1':
|
||||||
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -871,6 +912,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.11':
|
||||||
|
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-dropdown-menu@2.1.15':
|
'@radix-ui/react-dropdown-menu@2.1.15':
|
||||||
resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==}
|
resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -893,6 +947,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.3':
|
||||||
|
resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-focus-scope@1.1.7':
|
'@radix-ui/react-focus-scope@1.1.7':
|
||||||
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -906,6 +969,11 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-icons@1.3.2':
|
||||||
|
resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
'@radix-ui/react-id@1.1.1':
|
'@radix-ui/react-id@1.1.1':
|
||||||
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -941,6 +1009,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-popover@1.1.15':
|
||||||
|
resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-popper@1.2.7':
|
'@radix-ui/react-popper@1.2.7':
|
||||||
resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==}
|
resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -954,6 +1035,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-popper@1.2.8':
|
||||||
|
resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.1.9':
|
'@radix-ui/react-portal@1.1.9':
|
||||||
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -980,6 +1074,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.5':
|
||||||
|
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.1.3':
|
'@radix-ui/react-primitive@2.1.3':
|
||||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -993,6 +1100,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-radio-group@1.3.8':
|
||||||
|
resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.10':
|
'@radix-ui/react-roving-focus@1.1.10':
|
||||||
resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==}
|
resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1006,6 +1126,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-roving-focus@1.1.11':
|
||||||
|
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-select@2.2.5':
|
'@radix-ui/react-select@2.2.5':
|
||||||
resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==}
|
resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1032,6 +1165,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-slider@1.3.6':
|
||||||
|
resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.2.3':
|
'@radix-ui/react-slot@1.2.3':
|
||||||
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1155,10 +1301,42 @@ packages:
|
|||||||
'@radix-ui/rect@1.1.1':
|
'@radix-ui/rect@1.1.1':
|
||||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||||
|
|
||||||
|
'@react-icons/all-files@4.1.0':
|
||||||
|
resolution: {integrity: sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
|
||||||
'@remix-run/router@1.23.0':
|
'@remix-run/router@1.23.0':
|
||||||
resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
|
resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
'@rjsf/core@6.0.0-beta.11':
|
||||||
|
resolution: {integrity: sha512-tadVN0B7CjAzl2p3HDsZKR3V8iCIiQMncblfCYSK0PAh9RqIjigFp8ovrZ3UqkVIRRcD+tQ6Y+psY5yEB4GWqA==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
peerDependencies:
|
||||||
|
'@rjsf/utils': ^6.0.0-beta
|
||||||
|
react: '>=18'
|
||||||
|
|
||||||
|
'@rjsf/shadcn@6.0.0-beta.10':
|
||||||
|
resolution: {integrity: sha512-BXgbCZF+Xb7lqLYBWYRcq4xamB2I41yOx0fT7dNs09CbnyWge4yziE6Ebm7R4N87qf2M320PLdJymcpUe5WtLA==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
peerDependencies:
|
||||||
|
'@rjsf/core': ^6.0.0-beta
|
||||||
|
'@rjsf/utils': ^6.0.0-beta
|
||||||
|
react: '>=18'
|
||||||
|
|
||||||
|
'@rjsf/utils@6.0.0-beta.11':
|
||||||
|
resolution: {integrity: sha512-tQeyacweXmquRjPcXCVS1pMFfhamFSoqpczNi0zEvMbx+mfu2b6CgoRKi2Hm8KgFEafdVBF+6HLqs+0FnYhRlQ==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18'
|
||||||
|
|
||||||
|
'@rjsf/validator-ajv8@6.0.0-beta.11':
|
||||||
|
resolution: {integrity: sha512-gR8pbCsR91LiayDKdIz7d7m7A2ozUv9J+7Tpl5/qUQ69Q/NmnHo+cAiVW04/yvqzf1cbRwMgkc5pZyu01Uu1/w==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
peerDependencies:
|
||||||
|
'@rjsf/utils': ^6.0.0-beta
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.11':
|
'@rolldown/pluginutils@1.0.0-beta.11':
|
||||||
resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
|
resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
|
||||||
|
|
||||||
@@ -1545,9 +1723,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||||
engines: {node: '>= 6.0.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
||||||
|
ajv-formats@2.1.1:
|
||||||
|
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||||
|
peerDependencies:
|
||||||
|
ajv: ^8.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
ajv:
|
||||||
|
optional: true
|
||||||
|
|
||||||
ajv@6.12.6:
|
ajv@6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
|
|
||||||
|
ajv@8.17.1:
|
||||||
|
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||||
|
|
||||||
ansi-regex@5.0.1:
|
ansi-regex@5.0.1:
|
||||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1674,6 +1863,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
cmdk@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
react-dom: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
|
||||||
codemirror@6.0.2:
|
codemirror@6.0.2:
|
||||||
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
||||||
|
|
||||||
@@ -1691,6 +1886,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
compute-gcd@1.2.1:
|
||||||
|
resolution: {integrity: sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==}
|
||||||
|
|
||||||
|
compute-lcm@1.1.2:
|
||||||
|
resolution: {integrity: sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==}
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
@@ -1918,6 +2119,9 @@ packages:
|
|||||||
fast-levenshtein@2.0.6:
|
fast-levenshtein@2.0.6:
|
||||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||||
|
|
||||||
|
fast-uri@3.1.0:
|
||||||
|
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||||
|
|
||||||
fastq@1.19.1:
|
fastq@1.19.1:
|
||||||
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
||||||
|
|
||||||
@@ -2144,9 +2348,19 @@ packages:
|
|||||||
json-parse-even-better-errors@2.3.1:
|
json-parse-even-better-errors@2.3.1:
|
||||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||||
|
|
||||||
|
json-schema-compare@0.2.2:
|
||||||
|
resolution: {integrity: sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==}
|
||||||
|
|
||||||
|
json-schema-merge-allof@0.8.1:
|
||||||
|
resolution: {integrity: sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
json-schema-traverse@0.4.1:
|
json-schema-traverse@0.4.1:
|
||||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||||
|
|
||||||
|
json-schema-traverse@1.0.0:
|
||||||
|
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1:
|
json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
|
|
||||||
@@ -2155,6 +2369,10 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
jsonpointer@5.0.1:
|
||||||
|
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
@@ -2173,6 +2391,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
lodash-es@4.17.21:
|
||||||
|
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||||
|
|
||||||
lodash.castarray@4.4.0:
|
lodash.castarray@4.4.0:
|
||||||
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
||||||
|
|
||||||
@@ -2201,6 +2422,11 @@ packages:
|
|||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
|
lucide-react@0.503.0:
|
||||||
|
resolution: {integrity: sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
lucide-react@0.539.0:
|
lucide-react@0.539.0:
|
||||||
resolution: {integrity: sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==}
|
resolution: {integrity: sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2210,6 +2436,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
|
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
markdown-to-jsx@7.7.13:
|
||||||
|
resolution: {integrity: sha512-DiueEq2bttFcSxUs85GJcQVrOr0+VVsPfj9AEUPqmExJ3f8P/iQNvZHltV4tm1XVhu1kl0vWBZWT3l99izRMaA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 0.14.0'
|
||||||
|
|
||||||
mdast-util-from-markdown@2.0.2:
|
mdast-util-from-markdown@2.0.2:
|
||||||
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
|
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
|
||||||
|
|
||||||
@@ -2345,6 +2577,11 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
nanoid@5.1.5:
|
||||||
|
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
|
||||||
|
engines: {node: ^18 || >=20}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
@@ -2543,6 +2780,9 @@ packages:
|
|||||||
react-is@16.13.1:
|
react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
|
react-is@18.3.1:
|
||||||
|
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||||
|
|
||||||
react-markdown@10.1.0:
|
react-markdown@10.1.0:
|
||||||
resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
|
resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2647,6 +2887,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
require-from-string@2.0.2:
|
||||||
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2784,6 +3028,9 @@ packages:
|
|||||||
tailwind-merge@2.6.0:
|
tailwind-merge@2.6.0:
|
||||||
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
|
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
|
||||||
|
|
||||||
|
tailwind-merge@3.3.1:
|
||||||
|
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
|
||||||
|
|
||||||
tailwindcss-animate@1.0.7:
|
tailwindcss-animate@1.0.7:
|
||||||
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2917,6 +3164,25 @@ packages:
|
|||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
|
uuid@11.1.0:
|
||||||
|
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
validate.io-array@1.0.6:
|
||||||
|
resolution: {integrity: sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==}
|
||||||
|
|
||||||
|
validate.io-function@1.0.2:
|
||||||
|
resolution: {integrity: sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==}
|
||||||
|
|
||||||
|
validate.io-integer-array@1.0.0:
|
||||||
|
resolution: {integrity: sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==}
|
||||||
|
|
||||||
|
validate.io-integer@1.0.5:
|
||||||
|
resolution: {integrity: sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==}
|
||||||
|
|
||||||
|
validate.io-number@1.0.3:
|
||||||
|
resolution: {integrity: sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==}
|
||||||
|
|
||||||
vfile-message@4.0.2:
|
vfile-message@4.0.2:
|
||||||
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
|
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
|
||||||
|
|
||||||
@@ -3656,6 +3922,8 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/primitive@1.1.2': {}
|
'@radix-ui/primitive@1.1.2': {}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.3': {}
|
||||||
|
|
||||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -3665,6 +3933,22 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
@@ -3689,6 +3973,28 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-remove-scroll: 2.7.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-direction@1.1.1(@types/react@18.3.23)(react@18.3.1)':
|
'@radix-ui/react-direction@1.1.1(@types/react@18.3.23)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -3708,6 +4014,19 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.2
|
'@radix-ui/primitive': 1.1.2
|
||||||
@@ -3729,6 +4048,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.23)(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
@@ -3740,6 +4065,10 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-icons@1.3.2(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
'@radix-ui/react-id@1.1.1(@types/react@18.3.23)(react@18.3.1)':
|
'@radix-ui/react-id@1.1.1(@types/react@18.3.23)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
@@ -3782,6 +4111,29 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-remove-scroll: 2.7.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/react-dom': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@floating-ui/react-dom': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -3800,6 +4152,24 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/react-dom': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -3820,6 +4190,16 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
||||||
@@ -3829,6 +4209,24 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-radio-group@1.3.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.2
|
'@radix-ui/primitive': 1.1.2
|
||||||
@@ -3846,6 +4244,23 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-select@2.2.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-select@2.2.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/number': 1.1.1
|
'@radix-ui/number': 1.1.1
|
||||||
@@ -3884,6 +4299,25 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-slider@1.3.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/number': 1.1.1
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.2.3(@types/react@18.3.23)(react@18.3.1)':
|
'@radix-ui/react-slot@1.2.3(@types/react@18.3.23)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
@@ -3992,8 +4426,72 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/rect@1.1.1': {}
|
'@radix-ui/rect@1.1.1': {}
|
||||||
|
|
||||||
|
'@react-icons/all-files@4.1.0(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
'@remix-run/router@1.23.0': {}
|
'@remix-run/router@1.23.0': {}
|
||||||
|
|
||||||
|
'@rjsf/core@6.0.0-beta.11(@rjsf/utils@6.0.0-beta.11(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@rjsf/utils': 6.0.0-beta.11(react@18.3.1)
|
||||||
|
lodash: 4.17.21
|
||||||
|
lodash-es: 4.17.21
|
||||||
|
markdown-to-jsx: 7.7.13(react@18.3.1)
|
||||||
|
nanoid: 5.1.5
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
|
'@rjsf/shadcn@6.0.0-beta.10(@rjsf/core@6.0.0-beta.11(@rjsf/utils@6.0.0-beta.11(react@18.3.1))(react@18.3.1))(@rjsf/utils@6.0.0-beta.11(react@18.3.1))(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-icons': 1.3.2(react@18.3.1)
|
||||||
|
'@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-popover': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-radio-group': 1.3.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-select': 2.2.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-slider': 1.3.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@react-icons/all-files': 4.1.0(react@18.3.1)
|
||||||
|
'@rjsf/core': 6.0.0-beta.11(@rjsf/utils@6.0.0-beta.11(react@18.3.1))(react@18.3.1)
|
||||||
|
'@rjsf/utils': 6.0.0-beta.11(react@18.3.1)
|
||||||
|
class-variance-authority: 0.7.1
|
||||||
|
clsx: 2.1.1
|
||||||
|
cmdk: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
lodash: 4.17.21
|
||||||
|
lodash-es: 4.17.21
|
||||||
|
lucide-react: 0.503.0(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
tailwind-merge: 3.3.1
|
||||||
|
tailwindcss-animate: 1.0.7(tailwindcss@3.4.17)
|
||||||
|
uuid: 11.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
- react-dom
|
||||||
|
- tailwindcss
|
||||||
|
|
||||||
|
'@rjsf/utils@6.0.0-beta.11(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
fast-uri: 3.1.0
|
||||||
|
json-schema-merge-allof: 0.8.1
|
||||||
|
jsonpointer: 5.0.1
|
||||||
|
lodash: 4.17.21
|
||||||
|
lodash-es: 4.17.21
|
||||||
|
nanoid: 5.1.5
|
||||||
|
react: 18.3.1
|
||||||
|
react-is: 18.3.1
|
||||||
|
|
||||||
|
'@rjsf/validator-ajv8@6.0.0-beta.11(@rjsf/utils@6.0.0-beta.11(react@18.3.1))':
|
||||||
|
dependencies:
|
||||||
|
'@rjsf/utils': 6.0.0-beta.11(react@18.3.1)
|
||||||
|
ajv: 8.17.1
|
||||||
|
ajv-formats: 2.1.1(ajv@8.17.1)
|
||||||
|
lodash: 4.17.21
|
||||||
|
lodash-es: 4.17.21
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.11': {}
|
'@rolldown/pluginutils@1.0.0-beta.11': {}
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.44.0':
|
'@rollup/rollup-android-arm-eabi@4.44.0':
|
||||||
@@ -4393,6 +4891,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
ajv-formats@2.1.1(ajv@8.17.1):
|
||||||
|
optionalDependencies:
|
||||||
|
ajv: 8.17.1
|
||||||
|
|
||||||
ajv@6.12.6:
|
ajv@6.12.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
@@ -4400,6 +4902,13 @@ snapshots:
|
|||||||
json-schema-traverse: 0.4.1
|
json-schema-traverse: 0.4.1
|
||||||
uri-js: 4.4.1
|
uri-js: 4.4.1
|
||||||
|
|
||||||
|
ajv@8.17.1:
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal: 3.1.3
|
||||||
|
fast-uri: 3.1.0
|
||||||
|
json-schema-traverse: 1.0.0
|
||||||
|
require-from-string: 2.0.2
|
||||||
|
|
||||||
ansi-regex@5.0.1: {}
|
ansi-regex@5.0.1: {}
|
||||||
|
|
||||||
ansi-regex@6.1.0: {}
|
ansi-regex@6.1.0: {}
|
||||||
@@ -4526,6 +5035,18 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
|
cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
|
||||||
codemirror@6.0.2:
|
codemirror@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/autocomplete': 6.18.6
|
'@codemirror/autocomplete': 6.18.6
|
||||||
@@ -4546,6 +5067,19 @@ snapshots:
|
|||||||
|
|
||||||
commander@4.1.1: {}
|
commander@4.1.1: {}
|
||||||
|
|
||||||
|
compute-gcd@1.2.1:
|
||||||
|
dependencies:
|
||||||
|
validate.io-array: 1.0.6
|
||||||
|
validate.io-function: 1.0.2
|
||||||
|
validate.io-integer-array: 1.0.0
|
||||||
|
|
||||||
|
compute-lcm@1.1.2:
|
||||||
|
dependencies:
|
||||||
|
compute-gcd: 1.2.1
|
||||||
|
validate.io-array: 1.0.6
|
||||||
|
validate.io-function: 1.0.2
|
||||||
|
validate.io-integer-array: 1.0.0
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
concurrently@8.2.2:
|
concurrently@8.2.2:
|
||||||
@@ -4817,6 +5351,8 @@ snapshots:
|
|||||||
|
|
||||||
fast-levenshtein@2.0.6: {}
|
fast-levenshtein@2.0.6: {}
|
||||||
|
|
||||||
|
fast-uri@3.1.0: {}
|
||||||
|
|
||||||
fastq@1.19.1:
|
fastq@1.19.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.1.0
|
reusify: 1.1.0
|
||||||
@@ -5040,12 +5576,26 @@ snapshots:
|
|||||||
|
|
||||||
json-parse-even-better-errors@2.3.1: {}
|
json-parse-even-better-errors@2.3.1: {}
|
||||||
|
|
||||||
|
json-schema-compare@0.2.2:
|
||||||
|
dependencies:
|
||||||
|
lodash: 4.17.21
|
||||||
|
|
||||||
|
json-schema-merge-allof@0.8.1:
|
||||||
|
dependencies:
|
||||||
|
compute-lcm: 1.1.2
|
||||||
|
json-schema-compare: 0.2.2
|
||||||
|
lodash: 4.17.21
|
||||||
|
|
||||||
json-schema-traverse@0.4.1: {}
|
json-schema-traverse@0.4.1: {}
|
||||||
|
|
||||||
|
json-schema-traverse@1.0.0: {}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||||
|
|
||||||
json5@2.2.3: {}
|
json5@2.2.3: {}
|
||||||
|
|
||||||
|
jsonpointer@5.0.1: {}
|
||||||
|
|
||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
@@ -5063,6 +5613,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
|
lodash-es@4.17.21: {}
|
||||||
|
|
||||||
lodash.castarray@4.4.0: {}
|
lodash.castarray@4.4.0: {}
|
||||||
|
|
||||||
lodash.isplainobject@4.0.6: {}
|
lodash.isplainobject@4.0.6: {}
|
||||||
@@ -5089,6 +5641,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
||||||
|
lucide-react@0.503.0(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
lucide-react@0.539.0(react@18.3.1):
|
lucide-react@0.539.0(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -5097,6 +5653,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
markdown-to-jsx@7.7.13(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
mdast-util-from-markdown@2.0.2:
|
mdast-util-from-markdown@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
@@ -5360,6 +5920,8 @@ snapshots:
|
|||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
|
nanoid@5.1.5: {}
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
node-fetch@2.7.0:
|
node-fetch@2.7.0:
|
||||||
@@ -5533,6 +6095,8 @@ snapshots:
|
|||||||
|
|
||||||
react-is@16.13.1: {}
|
react-is@16.13.1: {}
|
||||||
|
|
||||||
|
react-is@18.3.1: {}
|
||||||
|
|
||||||
react-markdown@10.1.0(@types/react@18.3.23)(react@18.3.1):
|
react-markdown@10.1.0(@types/react@18.3.23)(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
@@ -5650,6 +6214,8 @@ snapshots:
|
|||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
resolve@1.22.10:
|
resolve@1.22.10:
|
||||||
@@ -5793,6 +6359,8 @@ snapshots:
|
|||||||
|
|
||||||
tailwind-merge@2.6.0: {}
|
tailwind-merge@2.6.0: {}
|
||||||
|
|
||||||
|
tailwind-merge@3.3.1: {}
|
||||||
|
|
||||||
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
|
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
|
||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 3.4.17
|
tailwindcss: 3.4.17
|
||||||
@@ -5944,6 +6512,23 @@ snapshots:
|
|||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
|
uuid@11.1.0: {}
|
||||||
|
|
||||||
|
validate.io-array@1.0.6: {}
|
||||||
|
|
||||||
|
validate.io-function@1.0.2: {}
|
||||||
|
|
||||||
|
validate.io-integer-array@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
validate.io-array: 1.0.6
|
||||||
|
validate.io-integer: 1.0.5
|
||||||
|
|
||||||
|
validate.io-integer@1.0.5:
|
||||||
|
dependencies:
|
||||||
|
validate.io-number: 1.0.3
|
||||||
|
|
||||||
|
validate.io-number@1.0.3: {}
|
||||||
|
|
||||||
vfile-message@4.0.2:
|
vfile-message@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs).
|
|
||||||
// Do not edit this file manually.
|
|
||||||
// Auto-generated from Rust backend types using ts-rs
|
|
||||||
|
|
||||||
export const MCP_SUPPORTED_EXECUTORS: string[] = [
|
|
||||||
"claude",
|
|
||||||
"amp",
|
|
||||||
"gemini",
|
|
||||||
"sst-opencode",
|
|
||||||
"charm-opencode",
|
|
||||||
"claude-code-router"
|
|
||||||
];
|
|
||||||
43
shared/schemas/amp.json
Normal file
43
shared/schemas/amp.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"properties": {
|
||||||
|
"append_prompt": {
|
||||||
|
"title": "Append Prompt",
|
||||||
|
"description": "Extra text appended to the prompt",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "textarea",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"dangerously_allow_all": {
|
||||||
|
"title": "Dangerously Allow All",
|
||||||
|
"description": "Allow all commands to be executed, even if they are not safe.",
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_command_override": {
|
||||||
|
"title": "Base Command Override",
|
||||||
|
"description": "Override the base command with a custom command",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additional_params": {
|
||||||
|
"title": "Additional Parameters",
|
||||||
|
"description": "Additional parameters to append to the base command",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
53
shared/schemas/claude_code.json
Normal file
53
shared/schemas/claude_code.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"properties": {
|
||||||
|
"append_prompt": {
|
||||||
|
"title": "Append Prompt",
|
||||||
|
"description": "Extra text appended to the prompt",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "textarea",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"claude_code_router": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"plan": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dangerously_skip_permissions": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_command_override": {
|
||||||
|
"title": "Base Command Override",
|
||||||
|
"description": "Override the base command with a custom command",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additional_params": {
|
||||||
|
"title": "Additional Parameters",
|
||||||
|
"description": "Additional parameters to append to the base command",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
74
shared/schemas/codex.json
Normal file
74
shared/schemas/codex.json
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"properties": {
|
||||||
|
"append_prompt": {
|
||||||
|
"title": "Append Prompt",
|
||||||
|
"description": "Extra text appended to the prompt",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "textarea",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"sandbox": {
|
||||||
|
"description": "Sandbox policy modes for Codex",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"enum": [
|
||||||
|
"read-only",
|
||||||
|
"workspace-write",
|
||||||
|
"danger-full-access",
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"approval": {
|
||||||
|
"description": "Approval policy for Codex",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"enum": [
|
||||||
|
"untrusted",
|
||||||
|
"on-failure",
|
||||||
|
"on-request",
|
||||||
|
"never",
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"oss": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_command_override": {
|
||||||
|
"title": "Base Command Override",
|
||||||
|
"description": "Override the base command with a custom command",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additional_params": {
|
||||||
|
"title": "Additional Parameters",
|
||||||
|
"description": "Additional parameters to append to the base command",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
47
shared/schemas/cursor.json
Normal file
47
shared/schemas/cursor.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"properties": {
|
||||||
|
"append_prompt": {
|
||||||
|
"title": "Append Prompt",
|
||||||
|
"description": "Extra text appended to the prompt",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "textarea",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"force": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_command_override": {
|
||||||
|
"title": "Base Command Override",
|
||||||
|
"description": "Override the base command with a custom command",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additional_params": {
|
||||||
|
"title": "Additional Parameters",
|
||||||
|
"description": "Additional parameters to append to the base command",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
51
shared/schemas/gemini.json
Normal file
51
shared/schemas/gemini.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"append_prompt": {
|
||||||
|
"title": "Append Prompt",
|
||||||
|
"description": "Extra text appended to the prompt",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "textarea",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"flash"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"yolo": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_command_override": {
|
||||||
|
"title": "Base Command Override",
|
||||||
|
"description": "Override the base command with a custom command",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additional_params": {
|
||||||
|
"title": "Additional Parameters",
|
||||||
|
"description": "Additional parameters to append to the base command",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
shared/schemas/opencode.json
Normal file
47
shared/schemas/opencode.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"properties": {
|
||||||
|
"append_prompt": {
|
||||||
|
"title": "Append Prompt",
|
||||||
|
"description": "Extra text appended to the prompt",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "textarea",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"agent": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_command_override": {
|
||||||
|
"title": "Base Command Override",
|
||||||
|
"description": "Override the base command with a custom command",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additional_params": {
|
||||||
|
"title": "Additional Parameters",
|
||||||
|
"description": "Additional parameters to append to the base command",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
41
shared/schemas/qwen_code.json
Normal file
41
shared/schemas/qwen_code.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"properties": {
|
||||||
|
"append_prompt": {
|
||||||
|
"title": "Append Prompt",
|
||||||
|
"description": "Extra text appended to the prompt",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "textarea",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"yolo": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_command_override": {
|
||||||
|
"title": "Base Command Override",
|
||||||
|
"description": "Override the base command with a custom command",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additional_params": {
|
||||||
|
"title": "Additional Parameters",
|
||||||
|
"description": "Additional parameters to append to the base command",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
@@ -134,25 +134,27 @@ export type ExecutorConfig = { [key in string]?: { "CLAUDE_CODE": ClaudeCode } |
|
|||||||
|
|
||||||
export type BaseAgentCapability = "RESTORE_CHECKPOINT";
|
export type BaseAgentCapability = "RESTORE_CHECKPOINT";
|
||||||
|
|
||||||
export type ClaudeCode = { claude_code_router?: boolean | null, append_prompt?: string | null, plan?: boolean | null, dangerously_skip_permissions?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
export type ClaudeCode = { append_prompt: AppendPrompt, claude_code_router?: boolean | null, plan?: boolean | null, dangerously_skip_permissions?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||||
|
|
||||||
export type Gemini = { model: GeminiModel, append_prompt?: string | null, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
export type Gemini = { append_prompt: AppendPrompt, model: GeminiModel, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||||
|
|
||||||
export type GeminiModel = "default" | "flash";
|
export type GeminiModel = "default" | "flash";
|
||||||
|
|
||||||
export type Amp = { append_prompt?: string | null, dangerously_allow_all?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
export type Amp = { append_prompt: AppendPrompt, dangerously_allow_all?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||||
|
|
||||||
export type Codex = { append_prompt?: string | null, sandbox?: SandboxMode | null, approval?: ApprovalPolicy | null, oss?: boolean | null, model?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
export type Codex = { append_prompt: AppendPrompt, sandbox?: SandboxMode | null, approval?: ApprovalPolicy | null, oss?: boolean | null, model?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||||
|
|
||||||
export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
||||||
|
|
||||||
export type ApprovalPolicy = "untrusted" | "on-failure" | "on-request" | "never";
|
export type ApprovalPolicy = "untrusted" | "on-failure" | "on-request" | "never";
|
||||||
|
|
||||||
export type Cursor = { append_prompt?: string | null, force?: boolean | null, model?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
export type Cursor = { append_prompt: AppendPrompt, force?: boolean | null, model?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||||
|
|
||||||
export type Opencode = { append_prompt?: string | null, model?: string | null, agent?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
export type Opencode = { append_prompt: AppendPrompt, model?: string | null, agent?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||||
|
|
||||||
export type QwenCode = { append_prompt?: string | null, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
export type QwenCode = { append_prompt: AppendPrompt, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||||
|
|
||||||
|
export type AppendPrompt = string | null;
|
||||||
|
|
||||||
export type CodingAgentInitialRequest = { prompt: string,
|
export type CodingAgentInitialRequest = { prompt: string,
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user