setShowCustomColorPanel(false)}
/>
Customize Theme
setShowCustomColorPanel(false)}
className="p-1 rounded-lg hover:bg-[var(--component-hover)] text-[var(--text-secondary)] transition-colors"
>
{[
{ label: 'Background', key: 'background' },
{ label: 'Text Primary', key: 'foreground' },
{ label: 'Text Secondary', key: 'muted' },
{ label: 'Border Color', key: 'border' },
{ label: 'Panel Background', key: 'componentBg' },
{ label: 'Header Background', key: 'headerBg' },
{ label: 'User Bubble', key: 'userBubble' },
{ label: 'User Text', key: 'userText' },
{ label: 'Agent Bubble', key: 'agentBubble' },
{ label: 'Agent Text', key: 'agentText' },
{ label: 'Input Background', key: 'inputBg' },
{ label: 'Accent Color', key: 'accent' },
{ label: 'Accent Text', key: 'accentText' },
].map((item) => (
))}
handleThemeSync(currentTheme)}
className="w-full py-3 rounded-xl font-bold transition-all shadow-lg active:scale-95"
style={{ background: 'var(--accent)', color: 'var(--accent-text)' }}
>
Apply Changes
)}
);
};
export default ChatPage;
================================================
FILE: packages/agents/src/CreatePage.jsx
================================================
"use client";
import CreateAgent from "./components/CreateAgent";
import { Toaster } from "react-hot-toast";
const CreateAgentPage = ({ useUser, usedIn = "muapiapp" }) => {
return (
);
};
export default CreateAgentPage;
================================================
FILE: packages/agents/src/EditPage.jsx
================================================
"use client";
import EditAgent from "./components/EditAgent";
import { Toaster } from "react-hot-toast";
const EditAgentPage = ({ useUser, usedIn = "muapiapp" }) => {
return (
);
};
export default EditAgentPage;
================================================
FILE: packages/agents/src/components/AgentThemeProvider.jsx
================================================
'use client'
import React from 'react'
import { ThemeProvider } from 'next-themes'
export const AgentThemeProvider = ({ children }) => {
return (
{children}
)
}
export default AgentThemeProvider
================================================
FILE: packages/agents/src/components/CreateAgent.jsx
================================================
import React, { useEffect, useState } from "react";
import Link from "next/link";
import axios from "axios";
import { BiLoaderAlt } from "react-icons/bi";
import { RiRobot2Fill } from "react-icons/ri";
import { IoArrowBackOutline } from "react-icons/io5";
import { useRouter } from "next/navigation";
const BASE_URL = "/api/agents";
const CreateAgent = ({ useUser, usedIn }) => {
const userContext = useUser ? useUser() : {};
let user = null;
if (usedIn === "vadoo") {
const { serverDetails } = userContext;
user = serverDetails?.user_details
? { email: serverDetails.user_details.email, name: serverDetails.user_details.name }
: null;
} else {
// muapiapp
user = userContext.user || null;
}
const router = useRouter();
const [prompt, setPrompt] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleArchitectAgent = async (e) => {
e.preventDefault();
if (!prompt.trim()) return;
try {
setLoading(true);
setError(null);
const suggestResponse = await axios.post(`${BASE_URL}/suggest`, {
prompt,
});
const suggestion = suggestResponse.data;
const createPayload = {
name: suggestion.name || "Unnamed Agent",
description: suggestion.description || "",
system_prompt: suggestion.system_prompt || "",
skill_ids: suggestion.recommended_skill_ids || [],
welcome_message: suggestion.welcome_message || "",
initial_suggestions: suggestion.initial_suggestions || [],
is_published: false,
is_template: false,
};
const createResponse = await axios.post(`${BASE_URL}`, createPayload);
if (createResponse.status === 200 || createResponse.status === 201) {
const createdAgent = createResponse.data;
router.push(`/agents/edit/${createdAgent.agent_id}`);
}
} catch (err) {
console.error("Agent creation failed:", err);
setError(
err.response?.data?.message ||
err.response?.data?.detail ||
err.message ||
"Failed to architect agent. Please try again.",
);
} finally {
setLoading(false);
}
};
return (
Prompt Any Assistant
Use this to prompt up an assistant to help you with any topic!
)
};
export default CreateAgent;
================================================
FILE: packages/agents/src/components/EditAgent.jsx
================================================
import React, { useState, useEffect, useRef } from "react";
import { useParams, useRouter } from "next/navigation";
import Link from "next/link";
import axios from "axios";
import { IoChevronBack, IoPencilOutline, IoShareOutline, IoTrashOutline, IoCloseOutline, IoImageOutline, IoSparklesOutline, IoChatbubblesOutline } from "react-icons/io5";
import { BiLoaderAlt } from "react-icons/bi";
import { RiRobot2Fill } from "react-icons/ri";
import toast from "react-hot-toast";
import { FaRegTrashCan } from "react-icons/fa6";
import { MdClose } from "react-icons/md";
import { themes } from "./themes";
const BASE_URL = "/api/agents";
const EditAgent = ({ useUser, usedIn }) => {
// Project-specific user detail extraction
const userContext = useUser ? useUser() : {};
let user = null;
if (usedIn === "vadoo") {
const { serverDetails } = userContext;
user = serverDetails?.user_details
? { email: serverDetails.user_details.email, name: serverDetails.user_details.name }
: null;
} else {
// muapiapp
user = userContext.user || null;
}
const { id } = useParams();
const router = useRouter();
const fileInputRef = useRef(null);
const [formData, setFormData] = useState({
name: "",
description: "",
system_prompt: "",
icon_url: "",
skill_ids: [],
theme: "cosmic",
is_published: false,
is_template: false,
});
const [availableSkills, setAvailableSkills] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [searchTerm, setSearchTerm] = useState("");
const [error, setError] = useState(null);
const [success, setSuccess] = useState(false);
const [initialSkills, setInitialSkills] = useState([]);
const [realignedPrompt, setRealignedPrompt] = useState("");
const [isRealigning, setIsRealigning] = useState(false);
const [showRealignModal, setShowRealignModal] = useState(false);
const [generatingIcon, setGeneratingIcon] = useState(false);
const [showIconPromptModal, setShowIconPromptModal] = useState(false);
const [showIconSelectionModal, setShowIconSelectionModal] = useState(false);
const [iconPrompt, setIconPrompt] = useState("");
useEffect(() => {
if (id) {
fetchData();
}
}, [id]);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const [agentRes, skillsRes] = await Promise.all([
axios.get(`${BASE_URL}/by-slug/${id}`),
axios.get(`${BASE_URL}/skills`)
]);
const agent = agentRes.data;
if (!agent.is_owner) {
setError("You are not authorized to edit this agent.");
setLoading(false);
return;
}
setFormData({
name: agent.name,
description: agent.description || "",
system_prompt: agent.system_prompt,
icon_url: agent.icon_url || "",
skill_ids: agent.skills.map(s => s.id),
theme: agent.theme || "cosmic",
is_published: agent.is_published || false,
is_template: agent.is_template || false,
});
setInitialSkills(agent.skills.map(s => s.id));
setAvailableSkills(skillsRes.data);
} catch (err) {
console.error("Error fetching data:", err);
setError(
err.response?.data?.message ||
err.response?.data?.detail ||
"Failed to load agent details."
);
} finally {
setLoading(false);
}
};
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSkillToggle = (skillId) => {
setFormData(prev => {
const isSelected = prev.skill_ids.includes(skillId);
if (isSelected) {
return { ...prev, skill_ids: prev.skill_ids.filter(id => id !== skillId) };
} else {
return { ...prev, skill_ids: [...prev.skill_ids, skillId] };
}
});
};
const handleDelete = async () => {
if (!window.confirm("Are you sure you want to delete this agent? This action cannot be undone.")) {
return;
}
try {
setSaving(true);
await axios.delete(`${BASE_URL}/by-slug/${id}`);
toast.success("Agent deleted successfully");
router.push("/agents");
} catch (err) {
console.error("Delete error:", err);
toast.error("Failed to delete agent");
setError(err.response?.data?.detail || "Delete failed");
} finally {
setSaving(false);
}
};
const handleShare = () => {
const url = `${window.location.origin}/agents/${id}`;
navigator.clipboard.writeText(url);
toast.success("Chat link copied to clipboard!");
};
const handleFileUpload = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.type.startsWith("image/")) {
toast.error("Please upload an image file");
return;
}
try {
setUploading(true);
setUploadProgress(0);
const { data: uploadParams } = await axios.get("/api/app/get_file_upload_url", {
params: { filename: file.name }
});
const { url, fields } = uploadParams;
const uploadData = new FormData();
Object.entries(fields).forEach(([key, value]) => {
uploadData.append(key, value);
});
uploadData.append("file", file);
await axios.post(url, uploadData, {
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
setUploadProgress(percent);
}
});
const prefix = usedIn === "vadoo" ? "https://d3adwkbyhxyrtq.cloudfront.net/": "https://cdn.muapi.ai/";
const uploadedUrl = `${prefix}${fields.key}`;
setFormData(prev => ({ ...prev, icon_url: uploadedUrl }));
toast.success("Profile image updated");
} catch (err) {
console.error("Upload failed:", err);
toast.error("Failed to upload image");
} finally {
setUploading(false);
setUploadProgress(0);
}
};
const handleGenerateIcon = async (customPrompt) => {
if (!formData.name && !customPrompt) {
toast.error("Please enter an agent name first");
return;
}
try {
setGeneratingIcon(true);
const prompt = customPrompt || `A professional, clean profile icon for an AI agent named "${formData.name}". Description: ${formData.description || "An AI assistant"}. Minimalist, high-quality, circular composition.`;
const response = await axios.post("/api/api/v1/flux-schnell-image", {
prompt,
width: 1024,
height: 1024,
num_images: 1,
sync: true
});
if (response.data && response.data.outputs && response.data.outputs.length > 0) {
const generatedUrl = response.data.outputs[0];
setFormData(prev => ({ ...prev, icon_url: generatedUrl }));
setShowIconPromptModal(false);
toast.success("AI icon generated!");
} else {
throw new Error("No image generated");
}
} catch (err) {
console.error("Icon generation failed:", err);
toast.error(err.response?.data?.detail || "Failed to generate AI icon");
} finally {
setGeneratingIcon(false);
}
};
const handleRealign = async () => {
try {
setIsRealigning(true);
const res = await axios.post(`${BASE_URL}/by-slug/${id}/preview-realign`, {
current_prompt: formData.system_prompt,
new_skill_ids: formData.skill_ids
});
setRealignedPrompt(res.data.proposed_prompt);
setShowRealignModal(true);
toast.success("Prompt realigned! Please review.");
} catch (err) {
console.error("Realign failed:", err);
toast.error("Failed to realign prompt");
} finally {
setIsRealigning(false);
}
};
const applyRealignedPrompt = () => {
setFormData(prev => ({ ...prev, system_prompt: realignedPrompt }));
setShowRealignModal(false);
toast.success("New instructions applied!");
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
setSaving(true);
setError(null);
setSuccess(false);
await axios.put(`${BASE_URL}/by-slug/${id}`, formData);
setSuccess(true);
toast.success("Agent profile updated successfully!");
setTimeout(() => {
router.push("/agents");
}, 1500);
} catch (err) {
console.error("Error updating agent:", err);
setError(
err.response?.data?.message ||
err.response?.data?.detail ||
"Failed to update agent."
);
toast.error("Failed to save changes");
} finally {
setSaving(false);
}
};
if (loading) {
return (
);
}
if (error) {
return (
Access Denied
{error}
Return to My Agents
);
}
return (
setShowIconSelectionModal(true)}
className="w-28 h-28 rounded-full bg-gray-100 dark:bg-secondary-bg overflow-hidden ring-4 ring-white dark:ring-primary-bg shadow-sm border border-gray-100 dark:border-divider cursor-pointer group transition-all hover:ring-blue-500/30"
>
{formData.icon_url ? (
) : (
)}
{uploading && (
)}
{saving ? "Saving..." : "Save Changes"}
setFormData(prev => ({ ...prev, is_published: !prev.is_published }))}
className={`flex items-center gap-2 px-4 py-2.5 rounded-xl cursor-pointer transition-all duration-300 ${
formData.is_published
? "bg-white dark:bg-primary-bg shadow-sm text-blue-600 dark:text-primary"
: "text-gray-400 hover:text-gray-600 dark:text-secondary-text dark:hover:text-primary-text"
}`}
>
Publish
Behavior & Identity
Shape how your agent thinks, responds, and describes itself
Instructions
{isRealigning ? : "✨ Realign with Skills"}
Define how your agent thinks and communicates. Start with "You are..." and include specific examples.
Description
This will be visible to users when they discover your agent.
Theme & Appearance
Customize how your agent looks in the chat interface
{/* Theme Selection */}
Select Theme
{Object.values(themes || {}).map((theme) => (
setFormData(prev => ({ ...prev, theme: theme.id }))}
className={`group relative flex flex-col items-center gap-2 p-3 rounded-2xl border-2 transition-all ${
formData.theme === theme.id
? "border-black dark:border-primary bg-gray-50 dark:bg-primary-bg shadow-md scale-[1.02]"
: "border-gray-100 dark:border-divider hover:border-gray-200 dark:hover:border-primary bg-white dark:bg-primary-bg/50"
}`}
>
{theme.name}
{formData.theme === theme.id && (
)}
))}
Chat Preview
{formData.icon_url ? (
) : (
)}
{formData.name || "Agent Name"}
Online
Hi! How can you help me today?
I can help you with tasks, answer questions, and much more using {formData.skill_ids.length} configured skills!
This theme will be automatically applied to the chat interface for all users.
Capabilities
setSearchTerm(e.target.value)}
className="w-full bg-white dark:bg-primary-bg border border-gray-100 dark:border-divider rounded-xl px-5 py-3.5 text-sm dark:text-white focus:ring-4 focus:ring-black/5 dark:focus:ring-primary/5 focus:border-black dark:focus:border-primary transition-all outline-none shadow-sm"
/>
Active Agent Skills ({formData.skill_ids.length})
{formData.skill_ids.length > 0 ? (
formData.skill_ids.map((id) => {
const skill = availableSkills.find(s => s.id === id);
if (!skill) return null;
return (
handleSkillToggle(skill.id)}
className="relative p-4 flex items-center justify-between rounded-2xl bg-white dark:bg-primary-bg border border-gray-100 dark:border-divider shadow-sm transition-all hover:border-black dark:hover:border-primary group"
>
{skill.name}
{skill.description}
);
})
) : (
)}
Available in Registry
{availableSkills
.filter(skill =>
!formData.skill_ids.includes(skill.id) &&
(skill.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
skill.id.toLowerCase().includes(searchTerm.toLowerCase()))
)
.map((skill) => (
{
handleSkillToggle(skill.id);
setSearchTerm("");
}}
className="p-4 flex items-center justify-between rounded-2xl border border-gray-100 dark:border-divider bg-white dark:bg-primary-bg hover:border-black dark:hover:border-primary transition-all shadow-sm hover:shadow-md group"
>
{skill.name}
{skill.description}
+
))
}
Manage tools and skills your agent can use to perform tasks
{error && (
)}
{showRealignModal && (
Review Brain Realignment
The AI has refactored your instructions to match your new skills.
setShowRealignModal(false)}
className="p-2 hover:bg-white dark:hover:bg-primary-bg rounded-full transition-colors text-gray-400 dark:text-secondary-text hover:text-gray-900 dark:hover:text-white"
>
Current Instructions
{formData.system_prompt}
Proposed Instructions
setRealignedPrompt(e.target.value)}
className="flex-1 p-5 bg-violet-50/30 dark:bg-violet-900/10 border-2 border-violet-100 dark:border-violet-800/50 rounded-2xl text-sm text-gray-800 dark:text-primary-text font-medium leading-relaxed focus:ring-4 focus:ring-violet-500/10 focus:border-violet-500 outline-none transition-all resize-none min-h-[400px]"
/>
setShowRealignModal(false)}
className="px-6 py-2.5 text-sm font-bold text-gray-600 dark:text-secondary-text hover:text-gray-900 dark:hover:text-white transition-colors"
>
Discard Changes
Accept & Apply
)}
{showIconPromptModal && (
✨ Customize AI Icon Prompt
setShowIconPromptModal(false)}
className="p-2 hover:bg-white dark:hover:bg-secondary-bg rounded-full transition-colors text-gray-400 hover:text-gray-600 dark:hover:text-primary-text"
>
Tell the AI what kind of icon you want. You can describe style, colors, and specific elements.
setIconPrompt(e.target.value)}
placeholder="Describe your agent's icon..."
className="w-full h-40 p-5 bg-gray-50 dark:bg-primary-bg border border-gray-200 dark:border-divider rounded-2xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all resize-none dark:text-white placeholder:text-gray-400"
/>
setShowIconPromptModal(false)}
className="flex-1 px-6 py-4 border border-gray-200 dark:border-divider rounded-2xl text-sm font-bold text-gray-600 dark:text-primary-text hover:bg-gray-50 dark:hover:bg-primary-bg transition-all active:scale-[0.98]"
>
Cancel
handleGenerateIcon(iconPrompt)}
disabled={generatingIcon || !iconPrompt.trim()}
className="flex-[2] px-6 py-4 bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white font-bold rounded-2xl transition-all shadow-lg shadow-blue-500/20 active:scale-[0.98] flex items-center justify-center gap-2"
>
{generatingIcon ? (
<>
Generating...
>
) : (
<>
✨
Generate Icon
>
)}
)}
{showIconSelectionModal && (
Profile Icon
Choose how to update your agent's look
setShowIconSelectionModal(false)}
className="w-10 h-10 flex items-center justify-center bg-gray-50 dark:bg-primary-bg rounded-full text-gray-400 hover:text-black dark:hover:text-white transition-colors"
>
{
setShowIconSelectionModal(false);
fileInputRef.current?.click();
}}
className="group flex flex-col items-center gap-4 p-8 bg-gray-50 dark:bg-primary-bg rounded-[2rem] border border-gray-100 dark:border-divider hover:border-blue-500/50 hover:bg-white dark:hover:bg-secondary-bg transition-all duration-300 hover:shadow-xl hover:shadow-blue-500/5 active:scale-[0.98]"
>
Upload Photo
Pick a file from your device
{
setShowIconSelectionModal(false);
setIconPrompt(`A professional, clean profile icon for an AI agent named "${formData.name}". Description: ${formData.description || "An AI assistant"}. Minimalist, high-quality, circular composition.`);
setShowIconPromptModal(true);
}}
className="group flex flex-col items-center gap-4 p-8 bg-blue-50/30 dark:bg-blue-500/5 rounded-[2rem] border border-blue-100/50 dark:border-blue-500/20 hover:border-blue-500 hover:bg-white dark:hover:bg-secondary-bg transition-all duration-300 hover:shadow-xl hover:shadow-blue-500/10 active:scale-[0.98]"
>
Generate with AI
Create unique icon from prompt
)}
);
}
export default EditAgent
================================================
FILE: packages/agents/src/components/ProfileAgent.jsx
================================================
"use client";
import React, { useState, useEffect, useCallback } from "react";
import Image from "next/image";
import Link from "next/link";
import axios from "axios";
import { RiRobot2Fill } from "react-icons/ri";
import { BiLoaderAlt } from "react-icons/bi";
import {
IoChatbubbleEllipsesSharp,
IoShareOutline,
IoHeartOutline,
IoHeart,
} from "react-icons/io5";
import { FiClock, FiZap } from "react-icons/fi";
import { MdOutlineVerified } from "react-icons/md";
import { HiPlus } from "react-icons/hi2";
import { useParams } from "next/navigation";
const BASE_URL = "/api/agents";
function timeAgo(dateStr) {
if (!dateStr) return "";
const utcStr =
dateStr.endsWith("Z") || dateStr.includes("+") ? dateStr : dateStr + "Z";
const now = new Date();
const d = new Date(utcStr);
const diff = Math.floor((now - d) / 1000);
if (diff < 60) return "just now";
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`;
const months = Math.floor(diff / 2592000);
if (months < 12) return `${months} mo. ago`;
return `${Math.floor(months / 12)} yr. ago`;
}
function formatCount(n) {
if (!n && n !== 0) return "–";
if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
if (n >= 1000) return (n / 1000).toFixed(1) + "K";
return n.toLocaleString();
}
/**
* ProfileAgent — Agent profile content component.
* Supports light (muapiapp default) and dark (vadoo / dark-mode) themes via
* Tailwind's `dark:` prefix + CSS variables set by the host app.
*
* Props:
* useUser {function} — hook to get the current logged-in user
* usedIn {string} — "muapiapp" | "vadoo"
*/
export default function ProfileAgent({ useUser, usedIn = "muapiapp" }) {
const { agent_id } = useParams();
const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [liked, setLiked] = useState(false);
const [copied, setCopied] = useState(false);
const fetchProfile = useCallback(async () => {
try {
setLoading(true);
const res = await axios.get(`${BASE_URL}/${agent_id}/profile`);
setProfile(res.data);
if (res.data?.agent) {
setLiked(res.data.agent.has_liked || false);
}
setError(null);
} catch (err) {
setError(
err.response?.data?.detail || err.message || "Failed to load agent profile"
);
} finally {
setLoading(false);
}
}, [agent_id]);
useEffect(() => {
if (agent_id) fetchProfile();
}, [agent_id, fetchProfile]);
const handleShare = () => {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
};
const handleLike = async () => {
const newLiked = !liked;
setLiked(newLiked);
try {
const res = await axios.post(`/api/agents/by-slug/${agent.agent_id || agent.id}/like?is_like=${newLiked}`);
// Update the local state properly to trigger re-render
if (profile) {
setProfile({
...profile,
agent: {
...profile.agent,
like_count: res.data.like_count,
has_liked: res.data.has_liked
}
});
// Also ensure the 'liked' state is in sync with the real source of truth
setLiked(res.data.has_liked);
}
} catch (err) {
console.error("Failed to sync like:", err);
// Rollback on error
setLiked(!newLiked);
}
};
if (loading) {
return (
);
}
if (error || !profile) {
return (
);
}
const { agent, total_messages, total_chats, recent_chats } = profile;
const chatUrl = agent.agent_id
? `/agents/${agent.agent_id}`
: `/agents/${agent.id}`;
return (
{agent.icon_url ? (
) : (
)}
{agent.name}
{agent.is_published && (
Public
)}
{agent.description && (
{agent.description}
)}
{ (agent.owner_username || agent.owner_email) && (
by{" "}
{agent.owner_username || agent.owner_email.split("@")[0]}
)}
{liked ? (
) : (
)}
{agent.like_count || 0}
{copied && Copied! }
Chat
{agent.skills && agent.skills.length > 0 && (
Workflows
{agent.skills.map((skill) => (
{skill.name}
))}
)}
{agent.description && (
About {agent.name}
{agent.description}
)}
{agent.welcome_message && (
Greeting
"{agent.welcome_message}"
)}
Details
{agent.skills && agent.skills.length > 0 && (
)}
{recent_chats && recent_chats.length > 0 && (
Recent chats with this agent
{recent_chats.map((chat) => (
{chat.title || "New Chat"}
{chat.message_count} msg{chat.message_count !== 1 ? "s" : ""} · {timeAgo(chat.updated_at)}
))}
New chat
)}
{agent.initial_suggestions && agent.initial_suggestions.length > 0 && (
Try asking
{agent.initial_suggestions.slice(0, 4).map((s, i) => (
{s.label || s.prompt}
))}
)}
);
}
function DetailRow({ label, value }) {
return (
{label}
{value}
);
}
================================================
FILE: packages/agents/src/components/themes.jsx
================================================
export const themes = {
cosmic: {
id: 'cosmic',
name: 'Cosmic',
colors: {
background: 'radial-gradient(circle at 50% -20%, #1e293b 0%, #0b0f1a 80%)',
foreground: '#ffffff',
muted: '#94a3b8',
border: 'rgba(255, 255, 255, 0.1)',
componentBg: 'rgba(255, 255, 255, 0.05)',
componentHover: 'rgba(255, 255, 255, 0.1)',
headerBg: 'rgba(19, 24, 38, 0.8)',
userBubble: 'linear-gradient(135deg, #2563eb 0%, #4338ca 100%)',
userText: '#ffffff',
agentBubble: 'rgba(30, 41, 59, 0.6)',
agentText: '#cbd5e1',
inputBg: 'rgba(30, 41, 59, 0.5)',
accent: '#3b82f6',
accentText: '#ffffff',
}
},
midnight: {
id: 'midnight',
name: 'Midnight',
colors: {
background: '#000000',
foreground: '#ededed',
muted: '#737373',
border: '#262626',
componentBg: '#171717',
componentHover: '#262626',
headerBg: 'rgba(0, 0, 0, 0.8)',
userBubble: '#262626',
userText: '#ffffff',
agentBubble: '#0a0a0a',
agentText: '#d4d4d4',
inputBg: '#0a0a0a',
accent: '#ffffff',
accentText: '#000000',
}
},
light: {
id: 'light',
name: 'Clean Light',
colors: {
background: 'linear-gradient(to bottom, #f8fafc, #ffffff)',
foreground: '#1e293b',
muted: '#64748b',
border: '#e2e8f0',
componentBg: '#f1f5f9',
componentHover: '#e2e8f0',
headerBg: 'rgba(255, 255, 255, 0.8)',
userBubble: '#1e293b',
userText: '#ffffff',
agentBubble: '#ffffff',
agentText: '#334155',
inputBg: '#ffffff',
accent: '#475569',
accentText: '#ffffff',
}
},
cyberpunk: {
id: 'cyberpunk',
name: 'Cyberpunk',
colors: {
background: 'linear-gradient(45deg, #050505 0%, #12031c 100%)',
foreground: '#00ff41',
muted: '#d300c5',
border: '#00ff41',
componentBg: 'rgba(255, 0, 187, 0.1)',
componentHover: 'rgba(0, 255, 65, 0.1)',
headerBg: 'rgba(5, 5, 5, 0.9)',
userBubble: 'linear-gradient(90deg, #ff00ea, #5500ff)',
userText: '#ffffff',
agentBubble: '#000000',
agentText: '#00ff41',
inputBg: '#050505',
accent: '#d300c5',
accentText: '#ffffff',
}
},
glossy: {
id: 'glossy',
name: 'Glossy',
colors: {
background: 'linear-gradient(120deg, #e0c3fc 0%, #8ec5fc 100%)',
foreground: '#2d3748',
muted: '#64748b',
border: 'rgba(255, 255, 255, 0.4)',
componentBg: 'rgba(255, 255, 255, 0.3)',
componentHover: 'rgba(255, 255, 255, 0.5)',
headerBg: 'rgba(255, 255, 255, 0.2)',
userBubble: 'rgba(255, 255, 255, 0.8)',
userText: '#2d3748',
agentBubble: 'rgba(255, 255, 255, 0.4)',
agentText: '#2d3748',
inputBg: 'rgba(255, 255, 255, 0.5)',
accent: '#63b3ed',
accentText: '#ffffff',
}
},
ocean: {
id: 'ocean',
name: 'Ocean Depth',
colors: {
background: 'linear-gradient(to bottom, #0f172a, #082f49)',
foreground: '#e0f2fe',
muted: '#7dd3fc',
border: '#0c4a6e',
componentBg: '#0c4a6e',
componentHover: '#075985',
headerBg: 'rgba(12, 74, 110, 0.8)',
userBubble: '#0ea5e9',
userText: '#ffffff',
agentBubble: '#164e63',
agentText: '#e0f2fe',
inputBg: '#082f49',
accent: '#38bdf8',
accentText: '#082f49',
}
},
forest: {
id: 'forest',
name: 'Dark Forest',
colors: {
background: '#052e16',
foreground: '#dcfce7',
muted: '#86efac',
border: '#14532d',
componentBg: '#14532d',
componentHover: '#166534',
headerBg: 'rgba(5, 46, 22, 0.9)',
userBubble: '#22c55e',
userText: '#064e3b',
agentBubble: '#064e3b',
agentText: '#dcfce7',
inputBg: '#064e3b',
accent: '#4ade80',
accentText: '#064e3b',
}
},
sunset: {
id: 'sunset',
name: 'Sunset',
colors: {
background: 'linear-gradient(to top right, #4a1d1d, #7c2d12)',
foreground: '#ffedd5',
muted: '#fdba74',
border: '#9a3412',
componentBg: '#7c2d12',
componentHover: '#9a3412',
headerBg: 'rgba(124, 45, 18, 0.8)',
userBubble: 'linear-gradient(90deg, #f97316, #ea580c)',
userText: '#ffffff',
agentBubble: '#431407',
agentText: '#ffedd5',
inputBg: '#431407',
accent: '#fb923c',
accentText: '#431407',
}
},
dracula: {
id: 'dracula',
name: 'Vampire',
colors: {
background: '#282a36',
foreground: '#f8f8f2',
muted: '#6272a4',
border: '#44475a',
componentBg: '#44475a',
componentHover: '#6272a4',
headerBg: '#282a36',
userBubble: '#ff79c6',
userText: '#282a36',
agentBubble: '#44475a',
agentText: '#f8f8f2',
inputBg: '#44475a',
accent: '#bd93f9',
accentText: '#282a36',
}
},
coffee: {
id: 'coffee',
name: 'Coffee Shop',
colors: {
background: '#2e2622',
foreground: '#d6ccc2',
muted: '#b0a695',
border: '#4a3b32',
componentBg: '#3d322b',
componentHover: '#4a3b32',
headerBg: 'rgba(46, 38, 34, 0.9)',
userBubble: '#c3a185',
userText: '#2e2622',
agentBubble: '#1f1a17',
agentText: '#d6ccc2',
inputBg: '#1f1a17',
accent: '#d4a373',
accentText: '#2e2622',
}
},
terminal: {
id: 'terminal',
name: 'Hacker Terminal',
colors: {
background: '#0d1117',
foreground: '#00ff00',
muted: '#2ea043', // Lighter green for better visibility
border: '#30363d', // Subtle border
componentBg: '#161b22',
componentHover: '#21262d',
headerBg: '#0d1117',
userBubble: '#238636', // GitHub-like green button
userText: '#ffffff',
agentBubble: '#161b22',
agentText: '#00ff00',
inputBg: '#0d1117',
accent: '#2f81f7', // Blue accent for variety or keep green #2ea043
accentText: '#ffffff',
}
},
royal: {
id: 'royal',
name: 'Royal',
colors: {
background: '#1a0b2e',
foreground: '#e9d5ff',
muted: '#a855f7',
border: '#4c1d95',
componentBg: '#2e1065',
componentHover: '#4c1d95',
headerBg: 'rgba(26, 11, 46, 0.9)',
userBubble: 'linear-gradient(to right, #7e22ce, #6b21a8)',
userText: '#ffffff',
agentBubble: '#3b0764',
agentText: '#e9d5ff',
inputBg: '#3b0764',
accent: '#d8b4fe',
accentText: '#3b0764',
}
},
custom: {
id: 'custom',
name: 'Custom Theme',
colors: {
background: '#ffffff',
foreground: '#1e293b',
muted: '#64748b',
border: '#e2e8f0',
componentBg: '#f1f5f9',
componentHover: '#e2e8f0',
headerBg: 'rgba(255, 255, 255, 0.8)',
userBubble: '#1e293b',
userText: '#ffffff',
agentBubble: '#ffffff',
agentText: '#334155',
inputBg: '#ffffff',
accent: '#475569',
accentText: '#ffffff',
}
}
};
================================================
FILE: packages/agents/src/index.js
================================================
export { default as AiAgent } from "./AiAgent";
export { getAgentDetails } from "./utils/server";
export { themes } from "./components/themes";
export { default as CreateAgentPage } from "./CreatePage";
export { default as EditAgentPage } from "./EditPage";
export { default as AgentThemeProvider } from "./components/AgentThemeProvider";
export { default as AgentProfile } from "./AgentProfile";
================================================
FILE: packages/agents/src/tailwind.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.premium-bg {
@apply bg-white dark:bg-[#09090b];
background-image: radial-gradient(circle at 0% 0%, rgba(79, 70, 229, 0.03) 0%, transparent 25%),
radial-gradient(circle at 100% 100%, rgba(59, 130, 246, 0.03) 0%, transparent 25%);
}
.premium-sidebar-blur {
@apply backdrop-blur-xl bg-white/80 dark:bg-[#09090b]/80;
}
.premium-glass {
@apply backdrop-blur-md bg-white/70 dark:bg-zinc-900/60 border border-black/5 dark:border-white/10;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
}
.skeleton {
background: linear-gradient(
90deg,
#2a2a2a 25%,
#3a3a3a 50%,
#2a2a2a 75%
);
background-size: 200% 100%;
animation: wave 1.5s ease-in-out infinite;
}
@keyframes wave {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* Basic Markdown Spacing Fixes */
.prose p {
margin-bottom: 1rem;
}
.prose p:last-child {
margin-bottom: 0;
}
.prose ul {
list-style-type: disc;
margin-left: 1.5rem;
margin-bottom: 1rem;
}
.prose ol {
list-style-type: decimal;
margin-left: 1.5rem;
margin-bottom: 1rem;
}
.prose li {
margin-bottom: 0.25rem;
}
.prose h1, .prose h2, .prose h3, .prose h4 {
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
color: white;
}
.prose h1 { font-size: 1.5rem; }
.prose h2 { font-size: 1.25rem; }
.prose h3 { font-size: 1.125rem; }
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
.custom-scrollbar:hover::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
}
================================================
FILE: packages/agents/src/utils/server.js
================================================
export const getAgentDetails = async (agentId, options = {}) => {
const {
baseUrl = "http://127.0.0.1:8000/agents", // Default relative URL for internal API, or provide full URL
fetchOptions = {}
} = options;
if (!agentId) {
throw new Error("Agent ID is required");
}
const url = `${baseUrl}/${agentId}`;
try {
const response = await fetch(url, {
...fetchOptions,
cache: fetchOptions.cache || 'no-store',
});
if (!response.ok) {
throw new Error(`Failed to fetch agent details: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error("Error fetching agent details:", error);
throw error;
}
};
================================================
FILE: packages/agents/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: ["./src/**/*.{js,jsx}"],
theme: {
extend: {
colors: {
'primary': '#3898ec',
'primary-bg': '#121212',
'secondary-bg': '#1E1E1E',
'primary-text': '#E0E0E0',
'secondary-text': '#B0B0B0',
'divider': '#333333',
},
},
},
plugins: [],
}
================================================
FILE: server/.gitignore
================================================
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
.venv
*.egg-info/
dist/
build/
.pytest_cache/
.coverage
htmlcov/
================================================
FILE: server/app/main.py
================================================
import os
from dotenv import load_dotenv
env_path = os.path.join(os.path.dirname(__file__), '..', '.env')
load_dotenv(env_path)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .routers import agent_proxy
app = FastAPI(title="Vibe-Agents API", version="1.0.0")
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include routers
app.include_router(agent_proxy.router, prefix="/api", tags=["proxy"])
@app.get("/")
async def root():
return {"message": "Welcome to Vibe-Agents API"}
@app.get("/api/health")
async def health_check():
return {"status": "healthy"}
================================================
FILE: server/app/routers/__init__.py
================================================
from . import agent_proxy
================================================
FILE: server/app/routers/agent_proxy.py
================================================
from fastapi import APIRouter, Request, HTTPException
from app.utils.agent_helper import proxy_request
from typing import Optional
import os
router = APIRouter()
MUAPI_BASE_URL = os.getenv("MUAPI_BASE_URL", "https://api.muapi.ai")
# --- Agent Library Endpoints ---
@router.get("/agents/user/agents")
async def get_user_agents():
return await proxy_request("GET", "/agents/user/agents")
@router.get("/agents/templates/agents")
async def get_template_agents():
return await proxy_request("GET", "/agents/templates/agents")
@router.get("/agents/featured/agents")
async def get_featured_agents():
return await proxy_request("GET", "/agents/featured/agents")
@router.post("/agents/suggest")
async def get_suggested_agents(request: Request):
payload = await request.json()
return await proxy_request("POST", "/agents/suggest", payload=payload)
@router.post("/agents")
async def create_agent(request: Request):
payload = await request.json()
return await proxy_request("POST", "/agents", payload=payload)
# --- Agent Detail & Chat Endpoints ---
@router.get("/agents/skills")
async def get_agent_skills():
return await proxy_request("GET", f"/agents/skills")
@router.get("/agents/by-slug/{slug}")
async def get_agent_by_slug(slug: str):
return await proxy_request("GET", f"/agents/by-slug/{slug}")
@router.get("/agents/{slug}/profile")
async def get_agent_profile(slug: str):
return await proxy_request("GET", f"/agents/{slug}/profile")
@router.put("/agents/by-slug/{slug}")
async def update_agent_by_slug(slug: str, request: Request):
payload = await request.json()
return await proxy_request("PUT", f"/agents/by-slug/{slug}", payload=payload)
@router.post("/agents/by-slug/{slug}/chat")
async def agent_chat(slug: str, request: Request):
payload = await request.json()
return await proxy_request("POST", f"/agents/by-slug/{slug}/chat", payload=payload)
@router.post("/agents/by-slug/{slug}/like")
async def like_agent(slug: str, request: Request):
params = dict(request.query_params)
return await proxy_request("POST", f"/agents/by-slug/{slug}/like", params=params)
@router.get("/agents/by-slug/{slug}/{conv_id}")
async def get_conversation_history(slug: str, conv_id: str):
return await proxy_request("GET", f"/agents/by-slug/{slug}/{conv_id}")
@router.post("/agents/by-slug/{slug}/preview-realign")
async def get_agent_preview(slug: str, request: Request):
payload = await request.json()
return await proxy_request("POST", f"/agents/by-slug/{slug}/preview-realign", payload=payload)
# --- Prediction & Image Gen Endpoints ---
@router.get("/api/v1/predictions/{request_id}/result")
async def get_prediction_result(request_id: str):
return await proxy_request("GET", f"/api/v1/predictions/{request_id}/result")
@router.post("/api/v1/flux-schnell-image")
async def generate_flux_image(request: Request):
payload = await request.json()
return await proxy_request("POST", "/api/v1/flux-schnell-image", payload=payload)
# --- App & Workflow Utilities ---
@router.get("/app/get_file_upload_url")
async def get_upload_url(request: Request):
params = dict(request.query_params)
return await proxy_request("GET", "/app/get_file_upload_url", params=params)
@router.post("/workflow/cloudfront-signed-url")
async def get_signed_url(request: Request):
payload = await request.json()
return await proxy_request("POST", "/workflow/cloudfront-signed-url", payload=payload)
================================================
FILE: server/app/utils/agent_helper.py
================================================
import os
import httpx
import logging
from fastapi import HTTPException
from typing import Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
MUAPI_BASE_URL = os.getenv("MUAPI_BASE_URL", "https://api.muapi.ai")
async def get_api_key():
api_key = os.getenv("MU_API_KEY")
if not api_key:
raise HTTPException(status_code=400, detail="Setup MU_API_KEY in .env to be able to use the agent library")
return api_key
async def proxy_request(method: str, path: str, payload: Optional[dict] = None, params: Optional[dict] = None):
api_key = await get_api_key()
url = f"{MUAPI_BASE_URL}/{path.lstrip('/')}"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
async with httpx.AsyncClient() as client:
try:
response = await client.request(
method=method,
url=url,
json=payload,
params=params,
headers=headers,
timeout=60.0
)
# For JSON responses, return the parsed data
if "application/json" in response.headers.get("content-type", ""):
return response.json()
else:
# Fallback for other content types
return response.content
except httpx.RequestError as e:
logger.error(f"Request error: {e}")
raise HTTPException(status_code=500, detail=f"Error contacting MuAPI: {e}")
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise HTTPException(status_code=500, detail=str(e))
================================================
FILE: server/requirements.txt
================================================
fastapi
uvicorn
sqlalchemy
asyncpg
httpx
python-dotenv
ruff