feat: add JSON import functionality for CC agents

This commit is contained in:
Vivek R
2025-06-23 15:35:14 +05:30
parent ad035036e8
commit bec8cea1e7
4 changed files with 193 additions and 12 deletions

View File

@@ -169,6 +169,28 @@ pub struct AgentRunWithMetrics {
pub output: Option<String>, // Real-time JSONL content
}
/// Agent export format
#[derive(Debug, Serialize, Deserialize)]
pub struct AgentExport {
pub version: u32,
pub exported_at: String,
pub agent: AgentData,
}
/// Agent data within export
#[derive(Debug, Serialize, Deserialize)]
pub struct AgentData {
pub name: String,
pub icon: String,
pub system_prompt: String,
pub default_task: Option<String>,
pub model: String,
pub sandbox_enabled: bool,
pub enable_file_read: bool,
pub enable_file_write: bool,
pub enable_network: bool,
}
/// Database connection state
pub struct AgentDb(pub Mutex<Connection>);
@@ -1904,3 +1926,91 @@ fn create_command_with_env(program: &str) -> Command {
cmd
}
/// Import an agent from JSON data
#[tauri::command]
pub async fn import_agent(db: State<'_, AgentDb>, json_data: String) -> Result<Agent, String> {
// Parse the JSON data
let export_data: AgentExport = serde_json::from_str(&json_data)
.map_err(|e| format!("Invalid JSON format: {}", e))?;
// Validate version
if export_data.version != 1 {
return Err(format!("Unsupported export version: {}. This version of the app only supports version 1.", export_data.version));
}
let agent_data = export_data.agent;
let conn = db.0.lock().map_err(|e| e.to_string())?;
// Check if an agent with the same name already exists
let existing_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM agents WHERE name = ?1",
params![agent_data.name],
|row| row.get(0),
)
.map_err(|e| e.to_string())?;
// If agent with same name exists, append a suffix
let final_name = if existing_count > 0 {
format!("{} (Imported)", agent_data.name)
} else {
agent_data.name
};
// Create the agent
conn.execute(
"INSERT INTO agents (name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
params![
final_name,
agent_data.icon,
agent_data.system_prompt,
agent_data.default_task,
agent_data.model,
agent_data.sandbox_enabled,
agent_data.enable_file_read,
agent_data.enable_file_write,
agent_data.enable_network
],
)
.map_err(|e| format!("Failed to create agent: {}", e))?;
let id = conn.last_insert_rowid();
// Fetch the created agent
let agent = conn
.query_row(
"SELECT id, name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
params![id],
|row| {
Ok(Agent {
id: Some(row.get(0)?),
name: row.get(1)?,
icon: row.get(2)?,
system_prompt: row.get(3)?,
default_task: row.get(4)?,
model: row.get(5)?,
sandbox_enabled: row.get(6)?,
enable_file_read: row.get(7)?,
enable_file_write: row.get(8)?,
enable_network: row.get(9)?,
created_at: row.get(10)?,
updated_at: row.get(11)?,
})
},
)
.map_err(|e| format!("Failed to fetch created agent: {}", e))?;
Ok(agent)
}
/// Import agent from file
#[tauri::command]
pub async fn import_agent_from_file(db: State<'_, AgentDb>, file_path: String) -> Result<Agent, String> {
// Read the file
let json_data = std::fs::read_to_string(&file_path)
.map_err(|e| format!("Failed to read file: {}", e))?;
// Import the agent
import_agent(db, json_data).await
}

View File

@@ -26,7 +26,8 @@ use commands::agents::{
migrate_agent_runs_to_session_ids, list_running_sessions, kill_agent_session,
get_session_status, cleanup_finished_processes, get_session_output,
get_live_session_output, stream_session_output, get_claude_binary_path,
set_claude_binary_path, export_agent, export_agent_to_file, AgentDb
set_claude_binary_path, export_agent, export_agent_to_file, import_agent,
import_agent_from_file, AgentDb
};
use commands::sandbox::{
list_sandbox_profiles, create_sandbox_profile, update_sandbox_profile, delete_sandbox_profile,
@@ -139,6 +140,8 @@ fn main() {
get_agent,
export_agent,
export_agent_to_file,
import_agent,
import_agent_from_file,
execute_agent,
list_agent_runs,
get_agent_run,

View File

@@ -16,12 +16,13 @@ import {
Terminal,
ArrowLeft,
History,
Download
Download,
Upload
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { api, type Agent, type AgentRunWithMetrics } from "@/lib/api";
import { save } from "@tauri-apps/plugin-dialog";
import { save, open } from "@tauri-apps/plugin-dialog";
import { invoke } from "@tauri-apps/api/core";
import { cn } from "@/lib/utils";
import { Toast, ToastContainer } from "@/components/ui/toast";
@@ -187,6 +188,34 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
}
};
const handleImportAgent = async () => {
try {
// Show native open dialog
const filePath = await open({
multiple: false,
filters: [{
name: 'Claudia Agent',
extensions: ['claudia.json', 'json']
}]
});
if (!filePath) {
// User cancelled the dialog
return;
}
// Import the agent from the selected file
await api.importAgentFromFile(filePath as string);
setToast({ message: "Agent imported successfully", type: "success" });
await loadAgents();
} catch (err) {
console.error("Failed to import agent:", err);
const errorMessage = err instanceof Error ? err.message : "Failed to import agent";
setToast({ message: errorMessage, type: "error" });
}
};
// Pagination calculations
const totalPages = Math.ceil(agents.length / AGENTS_PER_PAGE);
const startIndex = (currentPage - 1) * AGENTS_PER_PAGE;
@@ -264,14 +293,25 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
</p>
</div>
</div>
<Button
onClick={() => setView("create")}
size="default"
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Create CC Agent
</Button>
<div className="flex items-center gap-2">
<Button
onClick={handleImportAgent}
size="default"
variant="outline"
className="flex items-center gap-2"
>
<Download className="h-4 w-4" />
Import
</Button>
<Button
onClick={() => setView("create")}
size="default"
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Create CC Agent
</Button>
</div>
</div>
</motion.div>
@@ -402,7 +442,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
className="flex items-center gap-1"
title="Export agent to .claudia.json"
>
<Download className="h-3 w-3" />
<Upload className="h-3 w-3" />
Export
</Button>
<Button

View File

@@ -747,6 +747,34 @@ export const api = {
}
},
/**
* Imports an agent from JSON data
* @param jsonData - The JSON string containing the agent export
* @returns Promise resolving to the imported agent
*/
async importAgent(jsonData: string): Promise<Agent> {
try {
return await invoke<Agent>('import_agent', { jsonData });
} catch (error) {
console.error("Failed to import agent:", error);
throw error;
}
},
/**
* Imports an agent from a file
* @param filePath - The path to the JSON file
* @returns Promise resolving to the imported agent
*/
async importAgentFromFile(filePath: string): Promise<Agent> {
try {
return await invoke<Agent>('import_agent_from_file', { filePath });
} catch (error) {
console.error("Failed to import agent from file:", error);
throw error;
}
},
/**
* Executes an agent
* @param agentId - The agent ID to execute