Full Code of Anil-matcha/Chat-With-Excel for AI

main cb12973823b1 cached
45 files
181.3 KB
44.0k tokens
42 symbols
1 requests
Download .txt
Repository: Anil-matcha/Chat-With-Excel
Branch: main
Commit: cb12973823b1
Files: 45
Total size: 181.3 KB

Directory structure:
gitextract_79z3ryl8/

├── .gitignore
├── README.md
├── client/
│   ├── .gitignore
│   ├── app/
│   │   ├── agents/
│   │   │   ├── [agent_id]/
│   │   │   │   ├── [conversation_id]/
│   │   │   │   │   └── page.js
│   │   │   │   ├── page.js
│   │   │   │   └── profile/
│   │   │   │       └── page.js
│   │   │   ├── create/
│   │   │   │   └── page.js
│   │   │   ├── edit/
│   │   │   │   └── [id]/
│   │   │   │       └── page.js
│   │   │   ├── loading.js
│   │   │   └── page.js
│   │   ├── globals.css
│   │   ├── layout.js
│   │   └── page.js
│   ├── components/
│   │   └── AgentClientWrapper.js
│   ├── context/
│   │   └── fetchAgentData.js
│   ├── jsconfig.json
│   ├── next.config.mjs
│   ├── package.json
│   └── postcss.config.mjs
├── package.json
├── packages/
│   └── agents/
│       ├── .gitignore
│       ├── README.md
│       ├── THEME_SETUP.md
│       ├── babel.config.json
│       ├── package.json
│       ├── postcss.config.js
│       ├── src/
│       │   ├── AgentProfile.jsx
│       │   ├── AiAgent.jsx
│       │   ├── CreatePage.jsx
│       │   ├── EditPage.jsx
│       │   ├── components/
│       │   │   ├── AgentThemeProvider.jsx
│       │   │   ├── CreateAgent.jsx
│       │   │   ├── EditAgent.jsx
│       │   │   ├── ProfileAgent.jsx
│       │   │   └── themes.jsx
│       │   ├── index.js
│       │   ├── tailwind.css
│       │   └── utils/
│       │       └── server.js
│       └── tailwind.config.js
└── server/
    ├── .gitignore
    ├── app/
    │   ├── main.py
    │   ├── routers/
    │   │   ├── __init__.py
    │   │   └── agent_proxy.py
    │   └── utils/
    │       └── agent_helper.py
    └── requirements.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules


================================================
FILE: README.md
================================================
# Open-Poe-AI

Open-source, self-hosted alternative to [Poe AI](https://poe.com) — chat with multiple large language models from a single interface, on your own infrastructure.

Poe (by Quora) is a hosted aggregator that puts GPT, Claude, Gemini, Grok, DeepSeek, Llama, Mistral and image/video models behind one chat UI. **Open-Poe-AI** is the self-hosted version: bring your own API keys, run it on your own server, and keep full control of prompts, conversations, and data.

## Features

- **Multi-model chat** — unified interface for OpenAI, Anthropic, Google, Mistral, DeepSeek, xAI, Meta Llama, and any OpenAI-compatible endpoint (including local models via Ollama / vLLM / LM Studio).
- **Multi-bot conversations** — query several models in the same thread and compare answers side by side.
- **Custom bots** — build and share bots with their own system prompts, tools, and knowledge bases.
- **Group chat** — multiple users and multiple AI models in one shared conversation.
- **Multimodal** — text, image generation, vision, and audio; pluggable adapters for image/video model providers.
- **Bring your own keys** — no subscription, no rate caps beyond the providers you use.
- **Self-hosted** — Docker Compose for one-command deploy; works on a laptop, VPS, or Kubernetes.
- **Open protocol** — bot server API so anyone can host their own bot and plug it in.

## Status

Early work in progress. Contributions welcome.

## Quick start

```bash
git clone https://github.com/Anil-matcha/Open-Poe-AI.git
cd Open-Poe-AI
cp .env.example .env   # add your provider API keys
docker compose up -d
```

Then open http://localhost:3000.

## License

MIT


================================================
FILE: client/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
package-lock.json



================================================
FILE: client/app/agents/[agent_id]/[conversation_id]/page.js
================================================
import { cookies } from "next/headers"; 
import { fetchAgentData, fetchHistoryData } from "@/context/fetchAgentData";
import AgentClientWrapper from "@/components/AgentClientWrapper";

export default async function Page({ params }) {
  const { agent_id, conversation_id } = await params;
  const cookieStore = await cookies();
  const cookieStr = cookieStore.toString();

  // Fetch agent and history in parallel
  const [agentDetails, initialHistory] = await Promise.all([
    fetchAgentData(agent_id, cookieStr, true),
    fetchHistoryData(agent_id, conversation_id, cookieStr)
  ]);

  return (
    <AgentClientWrapper initialAgentDetails={agentDetails} initialHistory={initialHistory} />
  );
}


================================================
FILE: client/app/agents/[agent_id]/page.js
================================================
import { cookies } from "next/headers";
import { fetchAgentData } from "@/context/fetchAgentData";
import AgentClientWrapper from "@/components/AgentClientWrapper";

export default async function Page({ params }) {
  const { agent_id } = await params;
  const cookieStore = await cookies();

  // Fetch agent by slug
  const agentDetails = await fetchAgentData(agent_id, cookieStore.toString(), true);
  return (
    <AgentClientWrapper initialAgentDetails={agentDetails} />
  );
}


================================================
FILE: client/app/agents/[agent_id]/profile/page.js
================================================
"use client";

import { AgentProfile } from "ai-agent";
import "ai-agent/dist/tailwind.css";
import { useParams } from "next/navigation";

// Mock user context to pass into components
const mockUseUser = () => ({
  user: {
    name: "Tester",
    username: "dev_user",
    profile_photo: "",
  }
});

export default function AgentProfileRoute() {
  const params = useParams();
  const agent_id = params.agent_id;

  return (
    <div className="w-full h-dvh bg-white">
      <AgentProfile agent_id={agent_id} useUser={mockUseUser} usedIn="muapiapp" />
    </div>
  );
}


================================================
FILE: client/app/agents/create/page.js
================================================
"use client";

import { CreateAgentPage } from "ai-agent";
import "ai-agent/dist/tailwind.css";

// Mock user context to pass into components
const mockUseUser = () => ({
  user: {
    name: "Tester",
    username: "dev_user",
    profile_photo: "",
  }
});

export default function CreateAgentRoute() {
  return (
    <div className="w-full h-dvh bg-white">
      <CreateAgentPage useUser={mockUseUser} usedIn="muapiapp" />
    </div>
  );
}


================================================
FILE: client/app/agents/edit/[id]/page.js
================================================
"use client";

import { EditAgentPage } from "ai-agent";
import "ai-agent/dist/tailwind.css";
import { useParams } from "next/navigation";

// Mock user context to pass into components
const mockUseUser = () => ({
  user: {
    name: "Tester",
    username: "dev_user",
    profile_photo: "",
  }
});

export default function EditAgentRoute() {
  const params = useParams();
  const id = params.id;

  return (
    <div className="h-dvh bg-white">
      <EditAgentPage id={id} useUser={mockUseUser} usedIn="muapiapp" />
    </div>
  );
}


================================================
FILE: client/app/agents/loading.js
================================================
export default function Loading() {
  return (
    <div className="flex flex-col items-center justify-center min-h-[60vh] gap-4">
      <div className="w-10 h-10 border-2 border-slate-200 border-t-blue-500 rounded-full animate-spin" />
      <p className="text-slate-400 text-sm font-bold uppercase tracking-widest animate-pulse">Syncing Library...</p>
    </div>
  );
}


================================================
FILE: client/app/agents/page.js
================================================
"use client";

import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import axios from "axios";
import { 
  RiRobot2Fill, 
  RiUser3Line, 
  RiLayoutGridLine, 
  RiStarLine, 
  RiSearchLine, 
  RiArrowRightUpLine, 
  RiAddLine,
  RiInformationLine
} from "react-icons/ri";

const AgentCard = ({ agent, category }) => (
  <Link
    href={`/agents/${agent.agent_id}`}
    className="group flex flex-col bg-white border border-slate-200 rounded-2xl p-2 hover:bg-slate-50 hover:border-slate-300 transition-all duration-200 shadow-sm hover:shadow-md"
  >
    {/* Large Image Top (muapiapp style) */}
    <div className="relative aspect-square w-full rounded-xl overflow-hidden bg-slate-100 border border-slate-200 flex items-center justify-center mb-4">
      {agent.icon_url ? (
        <img
          src={agent.icon_url}
          alt={agent.name}
          className="object-cover w-full h-full transition-transform duration-500 group-hover:scale-105"
        />
      ) : (
        <RiRobot2Fill className="w-12 h-12 text-slate-300" />
      )}
      <div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity">
        <div className="p-1.5 bg-white/80 backdrop-blur-md rounded-lg border border-slate-200 shadow-sm">
          <RiArrowRightUpLine className="w-3.5 h-3.5 text-slate-600" />
        </div>
      </div>
    </div>

    {/* Content Bottom */}
    <div className="px-2 pb-2">
      <div className="flex items-center justify-between gap-2 mb-1">
        <h3 className="text-sm font-bold text-slate-900 truncate group-hover:text-blue-600 transition-colors">
          {agent.name}
        </h3>
        <span className="text-[9px] font-black text-blue-500 uppercase tracking-widest shrink-0">
           {category}
        </span>
      </div>

      <p className="text-[11px] text-slate-500 line-clamp-2 leading-relaxed h-8 mb-4 font-medium">
        {agent.description || "Specialized AI Intelligence Unit for complex workflows."}
      </p>

      <div className="flex items-center justify-between mt-auto pt-3 border-t border-slate-100">
        <div className="flex items-center gap-1.5">
          <span className="flex h-1 w-1 rounded-full bg-emerald-500 shadow-[0_0_4px_rgba(16,185,129,0.5)]" />
          <span className="text-[9px] font-bold text-slate-400 uppercase tracking-wider">Ready</span>
        </div>
        <div className="text-[9px] font-bold text-slate-200 uppercase tracking-tighter">
          ACTIVE HUB
        </div>
      </div>
    </div>
  </Link>
);

const CategorySection = ({ title, icon: Icon, agents, categoryLabel }) => {
  const items = Array.isArray(agents) ? agents : [];
  if (items.length === 0) return null;
  
  return (
    <section className="animate-fade-in-up">
      <div className="flex items-center gap-2 mb-6 border-l-2 border-blue-500/30 pl-3">
        <Icon className="w-4 h-4 text-slate-400" />
        <h2 className="text-lg font-bold text-slate-900 tracking-tight">{title}</h2>
        <span className="text-[10px] font-black text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded-md ml-1">{items.length}</span>
      </div>
      <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
        {items.map(a => (
          <AgentCard key={a.id || a.agent_id} agent={a} category={categoryLabel} />
        ))}
      </div>
    </section>
  );
};

export default function AgentsLibrary() {
  const [agents, setAgents] = useState([]);
  const [templates, setTemplates] = useState([]);
  const [featured, setFeatured] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [searchTerm, setSearchTerm] = useState("");
  const [activeTab, setActiveTab] = useState("workforce"); // Removed "all"

  const fetchAllData = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      
      const BASE_URL = "/api/agents";

      const [userRes, templatesRes, featuredRes] = await Promise.all([
        axios.get(`${BASE_URL}/user/agents`),
        axios.get(`${BASE_URL}/templates/agents`),
        axios.get(`${BASE_URL}/featured/agents`)
      ]);

      setAgents(Array.isArray(userRes.data) ? userRes.data : []);
      setTemplates(Array.isArray(templatesRes.data) ? templatesRes.data : []);
      setFeatured(Array.isArray(featuredRes.data) ? featuredRes.data : []);
    } catch (err) {
      console.error(err);
      setError("Synchronizing failed. Check connectivity.");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetchAllData();
  }, [fetchAllData]);

  const filterAgents = (list) => {
    const arr = Array.isArray(list) ? list : [];
    if (!searchTerm) return arr;
    return arr.filter(a => 
      (a.name && a.name.toLowerCase().includes(searchTerm.toLowerCase())) || 
      (a.description && a.description.toLowerCase().includes(searchTerm.toLowerCase()))
    );
  };

  const filteredAgents = filterAgents(agents);
  const filteredTemplates = filterAgents(templates);
  const filteredFeatured = filterAgents(featured);

  const TABS = [
    { id: "workforce", label: "My Workforce", icon: RiUser3Line },
    { id: "templates", label: "Templates", icon: RiLayoutGridLine },
    { id: "featured", label: "Featured", icon: RiStarLine },
  ];

  return (
    <div className="min-h-screen bg-[#FDFDFD] text-slate-900 selection:bg-blue-500/10 font-sans pb-20">
      <div className="fixed top-0 right-0 w-1/3 h-1/3 bg-blue-500/[0.03] rounded-full blur-[100px] pointer-events-none" />
      <div className="fixed inset-0 bg-[#00000003] backdrop-noise pointer-events-none" />

      <div className="relative z-10 max-w-[1400px] mx-auto px-6 py-12">
        
        <header className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-12 animate-fade-in-up">
          <div className="space-y-1">
            <h1 className="text-3xl font-bold tracking-tight text-slate-900 flex items-center gap-2">
              <RiRobot2Fill className="text-blue-600 w-8 h-8" />
              Agent Library
            </h1>
            <p className="text-slate-400 text-sm font-medium uppercase tracking-widest text-[10px]">Manage Intelligence Workflow</p>
          </div>

          <div className="flex flex-col sm:flex-row items-center gap-3">
             <div className="relative min-w-[280px]">
                <RiSearchLine className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
                <input 
                  type="text"
                  placeholder="Filter intelligence..."
                  value={searchTerm}
                  onChange={(e) => setSearchTerm(e.target.value)}
                  className="w-full bg-white border border-slate-200 rounded-xl pl-9 pr-4 py-2 text-sm outline-none focus:border-blue-500/40 focus:ring-4 focus:ring-blue-500/5 transition-all font-medium text-slate-900监测:text-slate-400"
                />
             </div>
             <Link 
               href="/agents/create"
               className="flex items-center justify-center gap-1.5 px-5 py-2 bg-slate-900 text-white text-sm font-bold rounded-xl hover:bg-slate-800 transition-all shrink-0 shadow-sm"
             >
               <RiAddLine className="w-4 h-4" />
               Create
             </Link>
          </div>
        </header>

        {/* Tab Strip */}
        <nav className="flex items-center gap-1 bg-slate-100/50 border border-slate-200/60 p-1 rounded-2xl mb-12 w-fit animate-fade-in-up shadow-sm" style={{ animationDelay: '0.1s' }}>
          {TABS.map(tab => (
            <button
              key={tab.id}
              onClick={() => setActiveTab(tab.id)}
              className={`flex items-center gap-2 px-5 py-2.5 text-[12px] font-bold rounded-xl transition-all ${
                activeTab === tab.id 
                  ? "bg-white text-slate-900 shadow-sm border border-slate-200/50" 
                  : "text-slate-500 hover:text-slate-900 hover:bg-white/50"
              }`}
            >
              <tab.icon className={`w-3.5 h-3.5 ${activeTab === tab.id ? "text-blue-600" : "text-slate-400"}`} />
              {tab.label}
            </button>
          ))}
        </nav>

        {loading ? (
          <div className="flex flex-col items-center justify-center py-32 gap-4">
            <div className="w-8 h-8 border-2 border-slate-200 border-t-blue-500 rounded-full animate-spin" />
            <p className="text-slate-400 text-[10px] font-bold uppercase tracking-widest animate-pulse">Syncing Library...</p>
          </div>
        ) : error ? (
          <div className="flex flex-col items-center justify-center py-20 p-8 border border-slate-200 bg-white rounded-3xl text-center max-w-md mx-auto shadow-sm">
             <RiInformationLine className="w-8 h-8 text-red-500/50 mb-4" />
             <h2 className="text-lg font-bold mb-1 text-slate-900">Connection Error</h2>
             <p className="text-slate-500 text-xs mb-6 leading-relaxed">{error}</p>
             <button 
               onClick={fetchAllData}
               className="px-6 py-2 bg-slate-900 text-white rounded-xl text-xs font-bold hover:bg-slate-800 transition-all shadow-sm"
             >
               Reconnect
             </button>
          </div>
        ) : (
          <div className="space-y-4">
            
            {activeTab === "workforce" && (
              <CategorySection 
                title="My Workforce"
                icon={RiUser3Line}
                agents={filteredAgents}
                categoryLabel="Personal"
              />
            )}

            {activeTab === "templates" && (
              <CategorySection 
                title="Templates"
                icon={RiLayoutGridLine}
                agents={filteredTemplates}
                categoryLabel="Blueprint"
              />
            )}

            {activeTab === "featured" && (
              <CategorySection 
                title="Featured"
                icon={RiStarLine}
                agents={filteredFeatured}
                categoryLabel="Verified"
              />
            )}

            {(activeTab === "workforce" ? filteredAgents : activeTab === "templates" ? filteredTemplates : filteredFeatured).length === 0 && (
              <div className="py-32 text-center bg-slate-50/50 rounded-3xl border border-dashed border-slate-200">
                <RiSearchLine className="w-10 h-10 text-slate-200 mx-auto mb-4" />
                <h3 className="text-lg font-bold text-slate-400 mb-1">No units found</h3>
                <p className="text-slate-400 text-[10px] font-medium uppercase tracking-widest">Adjust filters or architect a new unit</p>
              </div>
            )}

          </div>
        )}
      </div>

      <style jsx global>{`
        @keyframes fade-in-up {
          from { opacity: 0; transform: translateY(10px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .animate-fade-in-up {
          animation: fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
          opacity: 0;
        }
        .backdrop-noise {
          background-image: url('https://grainy-gradients.vercel.app/noise.svg');
          opacity: 0.01;
          filter: contrast(120%) brightness(120%);
        }
      `}</style>
    </div>
  );
}


================================================
FILE: client/app/globals.css
================================================
@import "tailwindcss";

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  background: var(--background);
  color: var(--foreground);
  font-family: Arial, Helvetica, sans-serif;
}


================================================
FILE: client/app/layout.js
================================================
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata = {
  title: "Vibe-Agents Client",
  description: "Testing Vibe-Agents proxy and frontend package",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}


================================================
FILE: client/app/page.js
================================================
"use client";

import React from "react";
import Link from "next/link";
import { RiRobot2Fill, RiTeamLine, RiFlashlightLine, RiNodeTree } from "react-icons/ri";

export default function WelcomePage() {
  return (
    <div className="relative min-h-screen w-full bg-white text-slate-900 overflow-hidden selection:bg-blue-500/10 font-sans">
      {/* Premium Background Effects */}
      <div className="fixed top-[-10%] right-[-5%] w-[50%] h-[50%] bg-blue-500/[0.03] rounded-full blur-[120px] pointer-events-none" />
      <div className="fixed bottom-[-10%] left-[-5%] w-[50%] h-[50%] bg-purple-500/[0.03] rounded-full blur-[120px] pointer-events-none" style={{ animationDelay: '2s' }} />
      <div className="fixed inset-0 bg-[linear-gradient(to_right,#00000005_1px,transparent_1px),linear-gradient(to_bottom,#00000005_1px,transparent_1px)] bg-[size:40px_40px] pointer-events-none" />
      
      {/* Subtle Noise Texture */}
      <div className="fixed inset-0 opacity-[0.01] pointer-events-none bg-[url('https://grainy-gradients.vercel.app/noise.svg')] filter contrast(120%) brightness(120%)" />

      <div className="relative z-10 flex flex-col items-center justify-center px-6 pt-20 pb-32 max-w-7xl mx-auto min-h-screen">
        {/* Badge */}
        <div className="mb-8 inline-flex items-center gap-2 px-3 py-1 bg-blue-50 border border-blue-100 rounded-full animate-fade-in-up">
          <span className="flex h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
          <span className="text-xs font-bold tracking-wide text-blue-600 uppercase">Next Gen AI Engine</span>
        </div>

        {/* Hero Title */}
        <h1 className="text-6xl md:text-8xl font-black text-center tracking-tighter leading-[0.9] mb-8 bg-gradient-to-b from-slate-900 via-slate-800 to-slate-500 bg-clip-text text-transparent animate-fade-in-up" style={{ animationDelay: '0.1s' }}>
          Vibe-Agents <br />
          <span className="text-blue-600">Intelligent Workforce</span>
        </h1>

        {/* Description */}
        <p className="max-w-2xl text-center text-lg md:text-xl text-slate-500 font-medium leading-relaxed mb-12 animate-fade-in-up" style={{ animationDelay: '0.2s' }}>
          Unleash autonomous AI agents that think, collaborate, and execute. 
          The ultimate platform for architecting specialized intelligence into your workflows.
        </p>

        {/* CTA Section */}
        <div className="flex flex-col sm:flex-row gap-4 items-center animate-fade-in-up" style={{ animationDelay: '0.3s' }}>
          <Link 
            href="/agents"
            className="group relative px-8 py-4 bg-slate-900 text-white font-bold rounded-2xl transition-all hover:scale-[1.02] active:scale-[0.98] shadow-lg shadow-slate-200 hover:shadow-xl hover:shadow-slate-300 overflow-hidden"
          >
            <div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-500 opacity-0 group-hover:opacity-100 transition-opacity" />
            <span className="relative flex items-center gap-2">
              Browse Agent Library
              <RiRobot2Fill className="w-5 h-5 group-hover:rotate-12 transition-transform" />
            </span>
          </Link>
          <Link 
            href="/agents/create"
            className="px-8 py-4 bg-white border border-slate-200 text-slate-900 font-bold rounded-2xl hover:bg-slate-50 transition-all active:scale-[0.98] shadow-sm"
          >
            Architect New Agent
          </Link>
        </div>

        {/* Features Grid */}
        <div className="mt-32 grid grid-cols-1 md:grid-cols-3 gap-6 w-full animate-fade-in-up" style={{ animationDelay: '0.4s' }}>
          <div className="p-8 rounded-3xl bg-white border border-slate-100 shadow-sm group hover:border-blue-500/30 transition-all">
            <div className="w-12 h-12 rounded-2xl bg-blue-50 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
              <RiTeamLine className="w-6 h-6 text-blue-600" />
            </div>
            <h3 className="text-xl font-bold mb-3 text-slate-900">Multi-Agent Teams</h3>
            <p className="text-slate-500 leading-relaxed font-medium">Build swarms of specialized agents that collaborate on complex tasks through natural language.</p>
          </div>

          <div className="p-8 rounded-3xl bg-white border border-slate-100 shadow-sm group hover:border-purple-500/30 transition-all">
            <div className="w-12 h-12 rounded-2xl bg-purple-50 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
              <RiFlashlightLine className="w-6 h-6 text-purple-600" />
            </div>
            <h3 className="text-xl font-bold mb-3 text-slate-900">Instant Execution</h3>
            <p className="text-slate-500 leading-relaxed font-medium">From code generation to visual design, agents execute with unprecedented speed and accuracy.</p>
          </div>

          <div className="p-8 rounded-3xl bg-white border border-slate-100 shadow-sm group hover:border-slate-300 transition-all">
            <div className="w-12 h-12 rounded-2xl bg-slate-100 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
              <RiNodeTree className="w-6 h-6 text-slate-600" />
            </div>
            <h3 className="text-xl font-bold mb-3 text-slate-900">Modular Logic</h3>
            <p className="text-slate-500 leading-relaxed font-medium">Deeply integrated with Vibe-Workflow for seamless bridging between human intent and automated logic.</p>
          </div>
        </div>
      </div>

      <style jsx global>{`
        @keyframes fade-in-up {
          from { opacity: 0; transform: translateY(20px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .animate-fade-in-up {
          animation: fade-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
          opacity: 0;
        }
      `}</style>
    </div>
  );
}


================================================
FILE: client/components/AgentClientWrapper.js
================================================
"use client";

import { AiAgent } from "ai-agent";
import "ai-agent/dist/tailwind.css";

// Mock user context to pass into AiAgent
const mockUseUser = () => ({
  user: {
    name: "Tester",
    username: "dev_user",
    profile_photo: "",
  }
});

export default function AgentClientWrapper({ initialAgentDetails, initialHistory = null }) {
  return (
    <div className="h-dvh w-full">
      <AiAgent 
        initialAgentDetails={initialAgentDetails} 
        initialHistory={initialHistory} 
        useUser={mockUseUser}
        usedIn="muapiapp"
      />
    </div>
  );
}


================================================
FILE: client/context/fetchAgentData.js
================================================
export async function fetchAgentData(id, cookieHeader, byName = false) {
  const endpoint = `http://127.0.0.1:8000/api/agents/by-slug/${id}`;
  const res = await fetch(endpoint, {
    cache: 'no-store',
    headers: {
      'Cookie': cookieHeader || '',
    },
  });

  if (!res.ok) return null;

  return await res.json();
}

export async function fetchHistoryData(agentSlug, conversationId, cookieHeader) {
  const endpoint = `http://127.0.0.1:8000/api/agents/by-slug/${agentSlug}/${conversationId}`;
  const res = await fetch(endpoint, {
    cache: 'no-store',
    headers: {
      'Cookie': cookieHeader || '',
    },
  });

  if (!res.ok) return null;
  return await res.json();
}


================================================
FILE: client/jsconfig.json
================================================
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./*"
      ]
    }
  }
}

================================================
FILE: client/next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ['ai-agent'],
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.muapi.ai',
        port: '',
        pathname: '/**',
      },
    ],
  },
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:8000/api/:path*',
      },
    ];
  },
};

export default nextConfig;


================================================
FILE: client/package.json
================================================
{
  "name": "vibe-agents-client",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "axios": "^1.13.2",
    "next": "16.1.1",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "react-icons": "^5.5.0",
    "react-hot-toast": "^2.4.1",
    "reactflow": "^11.11.4",
    "ai-agent": "*"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "eslint": "^9",
    "eslint-config-next": "16.1.1",
    "tailwindcss": "^4"
  }
}


================================================
FILE: client/postcss.config.mjs
================================================
const config = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

export default config;


================================================
FILE: package.json
================================================
{
    "name": "vibe-agents-monorepo",
    "version": "1.0.0",
    "private": true,
    "workspaces": [
        "packages/*",
        "client",
        "server"
    ],
    "scripts": {
        "dev:app": "npm run dev -w client",
        "build:app": "npm run build -w client",
        "build:lib": "npm run build -w packages/agents",
        "install:all": "npm install"
    }
}


================================================
FILE: packages/agents/.gitignore
================================================
node_modules
package-lock.json
dist

================================================
FILE: packages/agents/README.md
================================================
# AI Agent

A premium React component for AI interactions.

## Installation

To use this component in another project, run:

```bash
npm install git+https://github.com/jaiprasad04/ai-agent.git
```

## Usage

### 1. Import the Component

```javascript
import { AiAgent } from 'ai-agent';
```

### 2. Import the Styles

You must import the CSS file for the component to look correct:

```javascript
import 'ai-agent/dist/tailwind.css';
```

### 3. Example

```jsx
import React from 'react';
import { AiAgent } from 'ai-agent';
import 'ai-agent/dist/tailwind.css';

function App() {
  return (
    <div className="App">
      <AiAgent />
    </div>
  );
}

export default App;
```

## Development

If you want to modify the component:

1. Clone the repo
2. Run `npm install`
3. Run `npm run build` to update the `dist/` folder


================================================
FILE: packages/agents/THEME_SETUP.md
================================================
# Theme Setup Guide

This library uses `next-themes` to manage light and dark modes. For the theme switching buttons to work correctly, you must wrap your application with the provided `AgentThemeProvider`.

## 1. Wrap your Application
In your root layout or `App` component, import and use `AgentThemeProvider`.

### Next.js (App Router)
In your `layout.js`:

```jsx
import { AgentThemeProvider } from 'ai-agent';

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <AgentThemeProvider>
          {children}
        </AgentThemeProvider>
      </body>
    </html>
  )
}
```
> [!IMPORTANT]
> Adding `suppressHydrationWarning` to the `<html>` tag is required by `next-themes` to avoid hydration mismatch errors.

### Other React Apps
Wrap your main component:
```jsx
import { AgentThemeProvider } from 'ai-agent';

ReactDOM.render(
  <AgentThemeProvider>
    <App />
  </AgentThemeProvider>,
  document.getElementById('root')
);
```

## 2. Tailwind Configuration
Ensure your `tailwind.config.js` in the **consuming project** includes `darkMode: 'class'`:
```javascript
module.exports = {
  darkMode: 'class',
  // ... rest of your config
}
```

## 3. Usage
Once the provider is in place, the theme switching logic inside `HomepageNavbar` (and other components) will work automatically, toggling the `.dark` class on the `<html>` element.


================================================
FILE: packages/agents/babel.config.json
================================================
{
  "presets": [
    "@babel/preset-env",
    ["@babel/preset-react", { "runtime": "automatic" }]
  ]
}


================================================
FILE: packages/agents/package.json
================================================
{
  "name": "ai-agent",
  "version": "1.0.0",
  "description": "AI Agent",
  "main": "dist/index.js",
  "module": "dist/index.js",
  "files": [
    "dist"
  ],
  "scripts": {
    "build:css": "tailwindcss -i ./src/tailwind.css -o ./dist/tailwind.css --minify",
    "build": "npm run build:css && babel src --out-dir dist --extensions .js,.jsx"
  },
  "license": "MIT",
  "dependencies": {
    "axios": "^1.7.0",
    "next-themes": "^0.4.6",
    "react-hot-toast": "^2.5.2",
    "react-icons": "^5.0.1",
    "react-markdown": "^9.0.0",
    "react-toastify": "^11.0.5",
    "reactflow": "^11.11.4",
    "remark-gfm": "^4.0.0"
  },
  "peerDependencies": {
    "react": ">=18.0.0",
    "react-dom": ">=18.0.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.28.3",
    "@babel/preset-env": "^7.28.5",
    "@babel/preset-react": "^7.28.5",
    "autoprefixer": "^10.4.14",
    "postcss": "^8.4.24",
    "tailwindcss": "^3.3.3"
  }
}


================================================
FILE: packages/agents/postcss.config.js
================================================
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}


================================================
FILE: packages/agents/src/AgentProfile.jsx
================================================
"use client";

import { Toaster } from "react-hot-toast";
import ProfileAgent from "./components/ProfileAgent";

const AgentProfile = ({ useUser, usedIn = "muapiapp" }) => {
  return (
    <div className="h-screen w-full flex flex-col bg-blue-50/50 transition-all duration-300 ease-in-out">
      <Toaster position="top-center" reverseOrder={false} />
      <main className="flex flex-col items-center gap-2 w-full h-full overflow-y-auto pt-8">
        <ProfileAgent useUser={useUser} usedIn={usedIn} />
      </main>
    </div>
  );
};

export default AgentProfile;


================================================
FILE: packages/agents/src/AiAgent.jsx
================================================
"use client";

import React, { useState, useEffect, useRef } from "react";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import axios from "axios";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { IoSend, IoChevronBack, IoColorPalette, IoAdd, IoHeart, IoHeartOutline, IoChatbubbleEllipsesSharp } from "react-icons/io5";
import { HiLightBulb } from "react-icons/hi2";
import { MdTerminal, MdPerson, MdClose, MdEdit, MdContentCopy, MdCheck, MdFullscreen, MdFileDownload, MdImage } from "react-icons/md";
import { RiRobot2Fill } from "react-icons/ri";
import { HiOutlinePencilAlt } from "react-icons/hi";
import { BiLoaderAlt } from "react-icons/bi";
import { VscDebugAlt } from "react-icons/vsc";
import { themes } from "./components/themes";
import { FaAngleRight } from "react-icons/fa6";

const BASE_URL = "/api/agents"; // "https://api.muapi.ai/agents";

const formatMessageTime = (date) => {
  if (!date) return "";
  return new Intl.DateTimeFormat("en-US", {
    hour: "numeric",
    minute: "2-digit",
    hour12: true,
  }).format(new Date(date));
};

const getDateHeader = (date) => {
  const d = new Date(date);
  const now = new Date();
  const yesterday = new Date(now);
  yesterday.setDate(now.getDate() - 1);

  if (d.toDateString() === now.toDateString()) return "Today";
  if (d.toDateString() === yesterday.toDateString()) return "Yesterday";

  return d.toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
  });
};

const parseMessageContent = (text) => {
  if (!text) return [];
  const urlRegex = /(https?:\/\/[^\s]+)/g;
  const parts = [];
  let lastIndex = 0;
  let match;

  while ((match = urlRegex.exec(text)) !== null) {
    const start = match.index;
    const end = start + match[0].length;
    const url = match[0];

    if (start > lastIndex) {
      parts.push({ type: "text", content: text.substring(lastIndex, start) });
    }

    const cleanUrl = url.split("?")[0].toLowerCase();
    const isImage = /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(cleanUrl);
    const isVideo = /\.(mp4|webm|mov|ogg)$/i.test(cleanUrl);
    const isAudio = /\.(mp3|wav|mpeg)$/i.test(cleanUrl);

    if (isImage) {
      parts.push({ type: "image", url });
    } else if (isVideo) {
      parts.push({ type: "video", url });
    } else if (isAudio) {
      parts.push({ type: "audio", url });
    } else {
      parts.push({ type: "text", content: url });
    }

    lastIndex = end;
  }

  if (lastIndex < text.length) {
    parts.push({ type: "text", content: text.substring(lastIndex) });
  }

  return parts;
};

const CopyButton = ({ text }) => {
  const [copied, setCopied] = useState(false);

  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch (err) {
      console.error("Failed to copy text: ", err);
    }
  };

  return (
    <button
      onClick={handleCopy}
      className="p-1.5 rounded-lg border transition-all group relative border-[var(--border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--component-hover)]"
      title="Copy to clipboard"
      type="button"
    >
      {copied ? (
        <MdCheck className="w-3.5 h-3.5 text-green-400" />
      ) : (
        <MdContentCopy className="w-3.5 h-3.5" />
      )}
      <span
        className={`absolute -top-8 left-1/2 -translate-x-1/2 px-2 py-1 bg-slate-800 text-white text-[10px] rounded pointer-events-none transition-opacity duration-200 ${copied ? "opacity-100" : "opacity-0"
          }`}
      >
        Copied!
      </span>
    </button>
  );
};

const ChatPage = ({ 
  initialAgentDetails, 
  useUser, 
  usedIn = "muapiapp",
  useSidebar,
  searchQuery = "",
  setSearchQuery = () => {},
  getSearchItems = () => {},
  initialHistory = null,
}) => {
  const { id: routeAgentId, agent_id, agent_name, conversation_id: routeConversationId } = useParams();
  const effectiveAgentId = agent_id || agent_name || routeAgentId;
  const lowerAgentSlug = effectiveAgentId?.toLowerCase();
  
  const effectiveConversationId = routeConversationId;
  const router = useRouter();
  
  const userContext = useUser ? useUser() : {};
  let userName = "User";
  let userProfile = null;

  if (usedIn === "vadoo") {
    const { serverDetails } = userContext;
    userName = serverDetails?.user_details?.name || "User";
    userProfile = serverDetails?.user_details?.profile;
  } else if (usedIn === "muapiapp") {
    // muapiapp
    const { user } = userContext;
    userName = user?.username || user?.name || "User";
    userProfile = user?.profile_photo;
  }

  const [messages, setMessages] = useState(() => {
    if (initialHistory && initialHistory.history) {
      return initialHistory.history.map((msg, i) => {
        let ts = msg.timestamp || initialHistory.created_at || new Date();
        if (typeof ts === 'string' && ts.includes('T') && !ts.endsWith('Z') && !ts.includes('+')) {
          ts += 'Z';
        }
        return {
          ...msg,
          id: msg.id || `${msg.role}_${Date.now()}_${i}`,
          timestamp: ts
        };
      });
    }
    return [];
  });
  const [input, setInput] = useState("");
  const [isStreaming, setIsStreaming] = useState(() => {
    if (typeof window !== 'undefined' && effectiveConversationId) {
      return !!sessionStorage.getItem('pending_first_msg');
    }
    return false;
  });
  const [agentDetails, setAgentDetails] = useState(initialAgentDetails || null);
  const [error, setError] = useState(null);
  const [debugLogs, setDebugLogs] = useState([]);
  const [showDebug, setShowDebug] = useState(false);
  const conversationIdRef = useRef(null);
  const [showDropdown, setShowDropdown] = useState(false);
  const [showThemeDropdown, setShowThemeDropdown] = useState(false);
  const [selectedMedia, setSelectedMedia] = useState(null);
  const [downloadingUrl, setDownloadingUrl] = useState(null);
  const [currentTheme, setCurrentTheme] = useState(() => {
    const themeData = initialAgentDetails?.theme;
    if (typeof themeData === 'string' && themes[themeData]) {
      return themes[themeData];
    }
    if (themeData && typeof themeData === 'object' && themeData.colors) {
      return themeData;
    }
    return themes.cosmic;
  });
  const textareaRef = useRef(null);
  const scrollRef = useRef(null);
  const [attachments, setAttachments] = useState([]);
  const [isUploading, setIsUploading] = useState(false);
  const [uploadProgress, setUploadProgress] = useState(0);
  const [isDragging, setIsDragging] = useState(false);
  const fileInputRef = useRef(null);
  const currentAssistantMsgRef = useRef({
    content: "",
    thoughts: "",
    status: [],
    suggestions: [],
  });
  const [showCustomColorPanel, setShowCustomColorPanel] = useState(false);
  const [isMounted, setIsMounted] = useState(false);
  const [liked, setLiked] = useState(agentDetails ? agentDetails?.has_liked : false);
  const [likeCount, setLikeCount] = useState(agentDetails ? agentDetails?.like_count : 0);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  useEffect(() => {
    const fetchHistory = async () => {
      if (messages.length > 0) {
        conversationIdRef.current = effectiveConversationId;
        return;
      }

      if (effectiveConversationId && lowerAgentSlug) {
        const pending = sessionStorage.getItem('pending_first_msg');
        if (pending) {
          try {
            const { convId } = JSON.parse(pending);
            if (convId === effectiveConversationId) {
              return;
            }
          } catch (e) {}
        }

        try {
          let endpoint = `${BASE_URL}/by-slug/${lowerAgentSlug}/${effectiveConversationId}`;
          const res = await axios.get(endpoint);
          if (res.data && res.data.history) {
            const hydratedMessages = res.data.history.map((msg, i) => {
              let ts = msg.timestamp || res.data.created_at || new Date();
              if (typeof ts === 'string' && ts.includes('T') && !ts.endsWith('Z') && !ts.includes('+')) {
                ts += 'Z';
              }
              return {
                ...msg,
                id: msg.id || `${msg.role}_${Date.now()}_${i}`,
                timestamp: ts
              };
            });

            if (hydratedMessages.length > 0) {
              setMessages(hydratedMessages);
            }
            conversationIdRef.current = effectiveConversationId;
          }
        } catch (err) {
          console.error("Failed to fetch conversation history:", err);
        }
      }
    };
    fetchHistory();
  }, [effectiveConversationId, lowerAgentSlug]);

  const handleCustomColorChange = (part, color) => {
    const updatedTheme = {
      ...currentTheme,
      id: 'custom',
      name: 'Custom Theme',
      colors: {
        ...currentTheme.colors,
        [part]: color
      }
    };
    setCurrentTheme(updatedTheme);
  };

  const handleThemeSync = async (theme) => {
    try {
      await axios.put(`${BASE_URL}/by-slug/${lowerAgentSlug}`, { theme: theme });
    } catch (err) {
      console.error("Failed to save theme:", err);
    }
    setShowCustomColorPanel(false);
  };

  const generateCssVariables = (theme) => {
    const c = theme?.colors || themes.cosmic.colors;
    return {
      "--bg-primary": c.background,
      "--text-primary": c.foreground,
      "--text-secondary": c.muted,
      "--border-color": c.border,
      "--component-bg": c.componentBg,
      "--component-hover": c.componentHover,
      "--header-bg": c.headerBg,
      "--user-bubble": c.userBubble,
      "--user-text": c.userText,
      "--agent-bubble": c.agentBubble,
      "--agent-text": c.agentText,
      "--input-bg": c.inputBg,
      "--accent": c.accent,
      "--accent-text": c.accentText,
      "--font-family": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
    };
  };

  const handleDownloadFile = async (file_url, filename = "download") => {
    if (!file_url) {
      toast.error("File URL not found");
      return;
    }

    setDownloadingUrl(file_url);
    try {
      const response = await axios.post("/api/workflow/cloudfront-signed-url",
        {
          url: file_url
        }
      );

      const signed_url = response.data.signed_url;
      const fetchResponse = await fetch(signed_url, { mode: "cors" });
      const blob = await fetchResponse.blob();
      const url = window.URL.createObjectURL(blob);

      const link = document.createElement("a");
      link.href = url;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);

      window.URL.revokeObjectURL(url);
    } catch (err) {
      console.error("Download failed:", err);
      toast.error(`Download failed: ${err.message}`);
    } finally {
      setDownloadingUrl(null);
    }
  };

  useEffect(() => {
    if (agentDetails?.theme && themes[agentDetails.theme]) {
      setCurrentTheme(themes[agentDetails.theme]);
    }
  }, [agentDetails]);

  useEffect(() => {
    if (initialAgentDetails) {
      setAgentDetails(initialAgentDetails);
    } else {
      // fetchAgentDetails();
    }
  }, [lowerAgentSlug, initialAgentDetails]);

  useEffect(() => {
    const checkPendingMessage = async () => {
      if (effectiveConversationId) {
        const pending = sessionStorage.getItem('pending_first_msg');
        if (pending) {
          try {
            const { convId, text, attachments: pendingAttachments } = JSON.parse(pending);
            if (convId === effectiveConversationId) {
              sessionStorage.removeItem('pending_first_msg');
              setTimeout(() => {
                handleSendMessage(null, text, pendingAttachments);
              }, 100);
            }
          } catch (e) {
            console.error("Failed to parse pending message", e);
          }
        }
      }
    };
    checkPendingMessage();
  }, [effectiveConversationId]);

  useEffect(() => {
    if (textareaRef.current) {
      textareaRef.current.style.height = 'auto';
      textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
    }
  }, [input]);

  useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTo({
        top: scrollRef.current.scrollHeight,
        behavior: "smooth",
      });
    }
  }, [messages]);

  const fetchAgentDetails = async () => {
    try {
      const endpoint = `${BASE_URL}/by-slug/${lowerAgentSlug}`;
      const response = await axios.get(endpoint);
      setAgentDetails(response.data);
    } catch (err) {
      setAgentDetails({
        name: "Autonomous Agent",
        description: "MuAPI Powered Intelligence.",
      });
    }
  };

  const uploadFile = async (file) => {
    if (!file) return;

    if (file.size > 10 * 1024 * 1024) {
      setError("File size too large (max 10MB)");
      return;
    }

    try {
      setUploadProgress(0);
      setIsUploading(true);

      const response = await axios.get("/api/app/get_file_upload_url", {
        params: { filename: file.name }
      });
      const { url, fields } = response.data;

      const formData = new FormData();
      Object.entries(fields).forEach(([key, value]) => {
        formData.append(key, value);
      });
      formData.append("file", file);

      await axios.post(url, formData, {
        headers: { "Content-Type": "multipart/form-data" },
        onUploadProgress: (progressEvent) => {
          const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
          setUploadProgress(percent);
        }
      });
      const prefix = "https://cdn.muapi.ai/";
      const uploadedUrl = prefix + fields.key;
      setAttachments(prev => [...prev, uploadedUrl]);
    } catch (err) {
      console.error("Upload failed", err);
      setError("Failed to upload image.");
    } finally {
      setIsUploading(false);
      setUploadProgress(0);
      if (fileInputRef.current) fileInputRef.current.value = "";
    }
  };

  const handleFileUpload = (e) => {
    const file = e.target.files[0];
    uploadFile(file);
  };

  const handleDragOver = (e) => {
    e.preventDefault();
    setIsDragging(true);
  };

  const handleDragLeave = (e) => {
    e.preventDefault();
    setIsDragging(false);
  };

  const handleDrop = (e) => {
    e.preventDefault();
    setIsDragging(false);
    
    const file = e.dataTransfer.files[0];
    if (file && file.type.startsWith("image/")) {
      uploadFile(file);
    } else if (file) {
      setError("Please only upload image files.");
    }
  };

  const removeAttachment = (url) => {
    setAttachments(prev => prev.filter(item => item !== url));
  };

  const handleThemeChange = async (theme) => {
    setCurrentTheme(theme);
    handleThemeSync(theme);
  };

  const handleLike = async () => {
    const newLiked = !liked;
    const prevLikeCount = likeCount;
    
    // Optimistic update
    setLiked(newLiked);
    setLikeCount(prev => newLiked ? prev + 1 : prev - 1);

    try {
      const res = await axios.post(`/api/agents/by-slug/${lowerAgentSlug}/like?is_like=${newLiked}`);
      setLiked(res.data.has_liked);
      setLikeCount(res.data.like_count);
    } catch (err) {
      console.error("Failed to sync like:", err);
      // Rollback
      setLiked(!newLiked);
      setLikeCount(prevLikeCount);
    }
  };

  const handleNewChat = () => {
    if (lowerAgentSlug) {
      router.push(`/agents/${lowerAgentSlug}`);
    }
  };

  const handleSendMessage = async (e, overrideText = null, overrideAttachments = null) => {
    if (e) e.preventDefault();
    
    const userText = overrideText || input;
    const currentAttachments = overrideAttachments || (overrideText ? [] : attachments);

    if (!userText.trim()) return;
    if (isStreaming && !overrideText) return;
    
    if (overrideText) setIsStreaming(false);

    const userMessage = {
      role: "user",
      content: userText,
      attachments: [...currentAttachments],
      timestamp: new Date(),
    };
    setMessages((prev) => [...prev, userMessage]);
    
    if (!overrideText) {
      setAttachments([]);
      setInput("");
    }
    
    setIsStreaming(true);
    setError(null);
    setDebugLogs([]);

    const assistantMsgId = `asst_${Date.now()}`;
    currentAssistantMsgRef.current = {
      id: assistantMsgId,
      role: "assistant",
      content: "",
      thoughts: "",
      status: [],
      suggestions: [],
      timestamp: new Date(),
    };

    setMessages((prev) => [...prev, { ...currentAssistantMsgRef.current }]);

    try {
      let currentConvId = conversationIdRef.current || effectiveConversationId;
      
      if (!currentConvId && !overrideText) {
        const newConvId = crypto.randomUUID();
        conversationIdRef.current = newConvId;
        
        sessionStorage.setItem('pending_first_msg', JSON.stringify({
          convId: newConvId,
          text: userText,
          attachments: currentAttachments,
          timestamp: new Date().toISOString()
        }));

        if (lowerAgentSlug) {
           router.replace(`/agents/${lowerAgentSlug}/${newConvId}`);
        }
        
        return;
      }

      const initialRes = await axios.post(
        `${BASE_URL}/by-slug/${lowerAgentSlug}/chat`,
        {
          message: userText,
          stream: false,
          conversation_id: currentConvId,
          attachments: userMessage.attachments,
        }
      );

      const { request_id } = initialRes.data;
      if (!request_id) throw new Error("No Request ID returned from agent");

      const pollInterval = 1000;
      let isComplete = false;
      let errors = 0;

      while (!isComplete && errors < 5) {
        try {
          const pollRes = await axios.get(`/api/api/v1/predictions/${request_id}/result`);
          const data = pollRes.data;

          // data format from backend execute_agent_chat_background:
          // { 
          //   conversation_id, 
          //   messages: [{role, content...}, {type:'pulse'...}],
          //   status_text, 
          //   is_complete, 
          //   suggestions,
          //   error
          // }

          if (data.conversation_id) conversationIdRef.current = data.conversation_id;

          const incomingMessages = data.messages || [];

          let newContent = "";
          let newThoughts = "";
          let newStatus = [];

          incomingMessages.forEach(msg => {
            if (msg.role === "assistant" && msg.content) {
              newContent = msg.content;
            }
            if (msg.type === "pulse" && msg.content) {
              newStatus.push(msg.content);
            }
            if (msg.role === "assistant" && msg.thoughts) {
              newThoughts = msg.thoughts;
            }
          });

          currentAssistantMsgRef.current.content = newContent;
          currentAssistantMsgRef.current.status = newStatus;
          currentAssistantMsgRef.current.suggestions = data.suggestions || [];
          setMessages((prev) => {
            const index = prev.findIndex((m) => m.id === assistantMsgId);
            if (index !== -1) {
              const newMessages = [...prev];
              newMessages[index] = {
                ...newMessages[index],
                content: newContent,
                status: newStatus,
                suggestions: data.suggestions || [],
              };
              return newMessages;
            }
            return prev;
          });

          if (data.status === "failed") {
            throw new Error(data.error || "Agent execution failed");
          }

          if (data.status === "completed" || data.status === "succeeded" || data.is_complete) {
            isComplete = true;
          } else {
            await new Promise(r => setTimeout(r, pollInterval));
          }

        } catch (pollErr) {
          console.error("Polling error", pollErr);
          errors++;
          await new Promise(r => setTimeout(r, 2000));
        }
      }

      if (errors >= 5) throw new Error("Lost connection to agent process");

    } catch (err) {
      console.log("Agent error:", err);
      let errorMessage = err.message || "Something went wrong. Check browser console";
      if (err.response) {
        const { status, data } = err.response;
        errorMessage = data?.error || "Not enough credits";
      } else {
        errorMessage = err.message;
      }
      setError(errorMessage);
      if (!currentAssistantMsgRef.current.content) {
        setMessages((prev) => prev.filter((m) => m.id !== assistantMsgId));
      }
    } finally {
      setIsStreaming(false);
    }
  };

  return (
    <main
      className="h-dvh flex flex-col selection:bg-blue-500/30 relative"
      style={{
        ...generateCssVariables(currentTheme),
        background: "var(--bg-primary)",
        color: "var(--text-primary)",
        fontFamily: "var(--font-family)",
      }}
    >
      {isMounted && (
        <style dangerouslySetInnerHTML={{ __html: `
          @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
          
          main {
            font-family: var(--font-family) !important;
          }
          
          .prose, .prose p, .prose h1, .prose h2, .prose h3, .prose h4, .prose li {
            font-family: var(--font-family) !important;
          }
        ` }} />
      )}
      <header className="flex-shrink-0 border-b backdrop-blur-2xl px-6 py-4 flex items-center justify-center z-10 shadow-lg transition-colors duration-300 bg-[var(--header-bg)] border-[var(--border-color)]">
        <div className="flex items-center justify-between gap-4 w-full lg:max-w-[80%]">
          <div className="flex items-center gap-4">
            <button
              onClick={() => window.history.back()}
              className="flex items-center justify-center transition-all group"
            >
              <IoChevronBack className="w-5 h-5 text-[var(--text-secondary)] group-hover:text-[var(--text-primary)] transition-colors" />
            </button>
            <div className="flex items-center gap-3">
              {agentDetails?.icon_url ? (
                <img
                  src={agentDetails.icon_url}
                  alt={agentDetails.name}
                  className="w-9 h-9 rounded-lg object-cover border border-[var(--border-color)]"
                />
              ) : (
                <div className="w-9 h-9 rounded-lg flex items-center justify-center" style={{ background: 'var(--accent)', color: 'var(--accent-text)' }}>
                  <RiRobot2Fill className="w-5 h-5" />
                </div>
              )}
              <div className="relative">
                <button
                  onClick={() => setShowDropdown(!showDropdown)}
                  className="flex items-center gap-2 px-2 py-1 rounded-lg transition-all hover:bg-[var(--component-hover)]"
                >
                  <div className="flex flex-col items-start leading-tight">
                    <h1 className="text-base font-semibold text-[var(--text-primary)] truncate">
                      {agentDetails?.name || "Loading..."}
                    </h1>
                    {agentDetails && !agentDetails.is_owner && (agentDetails.owner_username || agentDetails.owner_email) && (
                      <span className="text-[10px] text-[var(--text-secondary)] font-medium">
                        by {agentDetails.owner_username || agentDetails.owner_email?.split('@')[0]}
                      </span>
                    )}
                  </div>
                  <IoChevronBack
                    className={`w-4 h-4 text-[var(--text-secondary)] transition-transform ${showDropdown ? "rotate-90" : "-rotate-180"
                      }`}
                  />
                </button>
                {showDropdown && (
                  <div className="absolute top-10 left-0 border rounded-lg shadow-xl z-50 animate-in fade-in slide-in-from-top-2 duration-200 min-w-[200px] bg-[var(--header-bg)] border-[var(--border-color)]">
                    <button
                      onClick={() => {
                        setShowDropdown(false);
                        router.push(`/agents/${lowerAgentSlug}/profile`);
                      }}
                      type="button"
                      className="w-full flex items-center gap-3 px-3 py-2 transition-all hover:bg-[var(--component-hover)] rounded-t-lg"
                    >
                      <RiRobot2Fill size={16} className="text-[var(--text-secondary)]" />
                      <span className="text-sm text-[var(--text-primary)]">View Profile</span>
                    </button>
                    {agentDetails?.is_owner && (
                      <>
                        <button
                          onClick={() => {
                            setShowDropdown(false);
                            router.push(`/agents/edit/${agent_id}`);
                          }}
                          type="button"
                          className="w-full flex items-center gap-3 px-3 py-2 transition-all hover:bg-[var(--component-hover)] border-t border-[var(--border-color)]"
                        >
                          <MdEdit size={16} className="text-[var(--text-secondary)]" />
                          <span className="text-sm text-[var(--text-primary)]">Edit agent</span>
                        </button>
                        <div className="relative group/submenu">
                          <button
                            onMouseEnter={() => setShowThemeDropdown(true)}
                            onClick={() => setShowThemeDropdown(!showThemeDropdown)}
                            type="button"
                            className={`w-full flex items-center gap-3 px-3 py-2 transition-all hover:bg-[var(--component-hover)] border-t border-[var(--border-color)] rounded-b-lg ${showThemeDropdown ? 'bg-[var(--component-hover)]' : ''}`}
                          >
                            <IoColorPalette size={16} className="text-[var(--text-secondary)]" />
                            <span className="text-sm text-[var(--text-primary)]">Themes</span>
                            <FaAngleRight size={14} className="ml-auto text-[var(--text-secondary)]" />
                          </button>
                          {showThemeDropdown && (
                            <div
                              className="md:absolute relative md:left-full left-0 md:top-0 top-0 md:ml-1 ml-0 md:border border-none md:rounded-xl rounded-none md:shadow-2xl shadow-none overflow-hidden z-[60] animate-in fade-in md:slide-in-from-left-2 slide-in-from-top-2 duration-200 min-w-[200px] bg-[var(--header-bg)] md:border-[var(--border-color)] p-2"
                              onMouseEnter={() => setShowThemeDropdown(true)}
                              onMouseLeave={() => setShowThemeDropdown(false)}
                            >
                              <div className="text-[10px] font-bold text-[var(--text-secondary)] mb-2 px-2 uppercase tracking-[0.2em]">Select Theme</div>
                              <div className="space-y-1 max-h-80 overflow-y-auto custom-scrollbar pr-1">
                                {Object.values(themes).map((theme) => (
                                  <button
                                    key={theme.id}
                                    onClick={() => {
                                      handleThemeChange(theme);
                                      setShowThemeDropdown(false);
                                      setShowDropdown(false);
                                    }}
                                    type="button"
                                    className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-all group/theme ${currentTheme.id === theme.id
                                        ? "bg-[var(--accent)] text-[var(--accent-text)] shadow-md"
                                        : "text-[var(--text-secondary)] hover:bg-[var(--component-hover)]"
                                      }`}
                                  >
                                    <div
                                      className="w-4 h-4 rounded-full border border-white/20 shadow-inner flex-shrink-0"
                                      style={{ background: theme.colors.background }}
                                    ></div>
                                    <span className="font-medium">{theme.name}</span>
                                    {currentTheme.id === theme.id && (
                                      <MdCheck className="ml-auto w-4 h-4" />
                                    )}
                                  </button>
                                ))}
                                <button
                                  onClick={() => {
                                    setShowCustomColorPanel(true);
                                    setShowThemeDropdown(false);
                                    setShowDropdown(false);
                                  }}
                                  type="button"
                                  className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-[var(--text-secondary)] hover:bg-[var(--component-hover)] border-t border-[var(--border-color)] mt-1"
                                >
                                  <MdEdit className="w-4 h-4" />
                                  <span className="font-medium">Customize Colors</span>
                                </button>
                              </div>
                            </div>
                          )}
                        </div>
                      </>
                    )}
                  </div>
                )}
              </div>
            </div>
          </div>
          <div className="flex items-center gap-2">
            <button
              type="button"
              onClick={handleLike}
              className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg transition-all border border-[var(--border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--component-hover)]"
              title={liked ? "Unlike agent" : "Like agent"}
            >
              {liked ? (
                <IoHeart className="w-4 h-4 text-red-500" />
              ) : (
                <IoHeartOutline className="w-4 h-4" />
              )}
              <span className="text-xs font-semibold">{likeCount || 0}</span>
            </button>

            {effectiveConversationId && (
              <button
                type="button"
                onClick={handleNewChat}
                className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg transition-all border border-[var(--border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--component-hover)]"
                title="Start new chat"
              >
                <HiOutlinePencilAlt className="w-4 h-4" />
                <span className="text-xs hidden md:flex font-semibold">New Chat</span>
              </button>
            )}
          </div>
        </div>
      </header>
      <div className="flex-1 flex overflow-y-auto">
        <div
          ref={scrollRef}
          className="flex-1 overflow-y-auto px-4 py-8 custom-scrollbar"
        >
          <div className="max-w-3xl mx-auto space-y-6">
            {messages.length === 0 && agentDetails && (
              <div className="space-y-6">
                <div className="flex justify-center">
                  <div className="px-4 py-1.5 rounded-full border text-[10px] uppercase tracking-widest font-bold bg-[var(--component-bg)] border-[var(--border-color)] text-[var(--text-secondary)]">
                    Today
                  </div>
                </div>
                <div className="flex flex-col items-start animate-in fade-in slide-in-from-bottom-2 duration-300">
                  <div className="flex items-center gap-2 mb-1 ml-11">
                    <div className="text-xs font-bold text-[var(--text-primary)]">
                      {agentDetails?.name}
                    </div>
                  </div>

                  <div className="flex gap-3 items-end max-w-[85%] group/msg">
                    {agentDetails?.icon_url ? (
                      <img
                        src={agentDetails.icon_url}
                        alt={agentDetails.name}
                        className="w-8 h-8 rounded-full object-cover border flex-shrink-0 border-[var(--border-color)] transition-all duration-500 ease-in-out"
                      />
                    ) : (
                      <div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 transition-all duration-500 ease-in-out" style={{ background: 'var(--accent)', color: 'var(--accent-text)' }}>
                        <RiRobot2Fill className="w-4 h-4" />
                      </div>
                    )}
                    <div className="flex-1 space-y-3">
                      <div className="flex items-end gap-2">
                        <div
                          className="backdrop-blur-sm rounded-2xl rounded-tl-md px-4 py-3 shadow-xl border inline-block"
                          style={{
                            background: 'var(--agent-bubble)',
                            color: 'var(--agent-text)',
                            borderColor: 'var(--border-color)'
                          }}
                        >
                          <div className="prose prose-sm max-w-none" style={{ color: 'var(--agent-text)' }}>
                            <p>
                              {agentDetails.welcome_message ||
                                `Hello! I am ${agentDetails.name}. ${agentDetails.description ||
                                "How can I assist you today?"
                                }`}
                            </p>
                          </div>
                        </div>
                        <div className="opacity-0 group-hover/msg:opacity-100 transition-opacity">
                          <CopyButton text={agentDetails?.welcome_message || `Hello! I am ${agentDetails.name}. ${agentDetails.description || "How can I assist you today?"}`} />
                        </div>
                      </div>
                      {agentDetails.initial_suggestions?.length > 0 && (
                        <div className="flex flex-wrap gap-2 pt-2">
                          {agentDetails.initial_suggestions.map((sug, i) => (
                            <button
                              key={i}
                              type="button"
                              onClick={() => {
                                setInput(sug.prompt);
                                if (textareaRef.current) {
                                  textareaRef.current.focus();
                                }
                              }}
                              className="flex items-center gap-2 text-xs font-medium border px-3 py-2 rounded-lg transition-all group hover:opacity-80"
                              style={{
                                background: 'var(--component-bg)',
                                borderColor: 'var(--border-color)',
                                color: 'var(--text-primary)'
                              }}
                            >
                              <HiLightBulb className="w-3.5 h-3.5 text-yellow-500 group-hover:scale-110 transition-transform" />
                              {sug.label}
                            </button>
                          ))}
                        </div>
                      )}
                    </div>
                  </div>
                </div>
              </div>
            )}
            {messages.map((msg, idx) => {
              const prevMsg = messages[idx - 1];
              const showDateHeader =
                !prevMsg ||
                new Date(msg.timestamp).toDateString() !==
                new Date(prevMsg.timestamp).toDateString();

              return (
                <div key={idx} className="space-y-6">
                  {showDateHeader && msg.timestamp && (
                    <div className="flex justify-center">
                      <div className="px-4 py-1.5 rounded-full border text-[10px] uppercase tracking-widest font-bold bg-[var(--component-bg)] border-[var(--border-color)] text-[var(--text-secondary)]">
                        {getDateHeader(msg.timestamp)}
                      </div>
                    </div>
                  )}
                  <div
                    className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"
                      } animate-in fade-in slide-in-from-bottom-2 duration-300`}
                  >
                    {msg.role === "user" ? (
                      <div className="flex flex-col items-end max-w-[80%] group/msg">
                        <div className="flex items-center gap-2 mb-1 mr-11">
                          {msg.timestamp && (
                            <div className="text-[10px] font-medium text-[var(--text-secondary)]">
                              {formatMessageTime(msg.timestamp)}
                            </div>
                          )}
                          <div className="text-xs font-bold text-[var(--text-primary)]">
                            {userName}
                          </div>
                        </div>

                        <div className="flex gap-3 items-end w-full justify-end">
                          <div className="flex-1 space-y-1 text-right">
                            <div className="flex items-end justify-end gap-2">
                              <div className="opacity-0 group-hover/msg:opacity-100 transition-opacity">
                                <CopyButton text={msg.content} />
                              </div>
                              <div
                                className="px-4 py-3 rounded-2xl rounded-tr-md shadow-xl inline-block text-left"
                                style={{
                                  background: 'var(--user-bubble)',
                                  color: 'var(--user-text)',
                                }}
                              >
                                {msg.attachments?.length > 0 && (
                                  <div className="mb-3 flex flex-wrap justify-end gap-2">
                                    {msg.attachments.map((url, i) => (
                                      <div key={i} className="relative group/user-att">
                                        <img
                                          src={url}
                                          alt="Uploaded Attachment"
                                          className="w-24 h-24 sm:w-32 sm:h-32 rounded-xl object-cover border border-white/20 shadow-md cursor-pointer hover:scale-[1.02] transition-transform"
                                          onClick={() => setSelectedMedia({ type: "image", url })}
                                        />
                                      </div>
                                    ))}
                                  </div>
                                )}
                                <p className="text-sm leading-relaxed font-medium whitespace-pre-wrap">
                                  {msg.content}
                                </p>
                              </div>
                            </div>
                          </div>
                          {userProfile ? (
                            <img
                              src={userProfile}
                              alt={userName}
                              className="w-8 h-8 rounded-full object-cover border flex-shrink-0 border-[var(--border-color)] transition-all duration-500 ease-in-out"
                            />
                          ) : (
                            <div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 transition-all duration-500 ease-in-out" style={{ background: 'var(--accent)', color: 'var(--accent-text)' }}>
                              <MdPerson className="w-4 h-4" />
                            </div>
                          )}
                        </div>
                      </div>
                    ) : (
                      <div className="flex flex-col items-start max-w-[85%] group/msg">
                        <div className="flex items-center gap-2 mb-1 ml-11">
                          <div className="text-xs font-bold text-[var(--text-primary)]">
                            {agentDetails?.name}
                          </div>
                          {msg.timestamp && (
                            <div className="text-[10px] font-medium text-[var(--text-secondary)]">
                              {formatMessageTime(msg.timestamp)}
                            </div>
                          )}
                        </div>

                        <div className="flex gap-3 items-end w-full">
                          {agentDetails?.icon_url ? (
                            <img
                              src={agentDetails.icon_url}
                              alt={agentDetails.name}
                              className="w-8 h-8 rounded-full object-cover border flex-shrink-0 border-[var(--border-color)] transition-all duration-500 ease-in-out"
                            />
                          ) : (
                            <div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 transition-all duration-500 ease-in-out" style={{ background: 'var(--accent)', color: 'var(--accent-text)' }}>
                              <RiRobot2Fill className="w-4 h-4" />
                            </div>
                          )}

                          <div className="flex-1 space-y-3">
                            {msg.status?.length > 0 && (
                              <div className="flex flex-wrap gap-2">
                                {msg.status.map((st, i) => (
                                  <div
                                    key={i}
                                    className="flex items-center gap-1.5 text-xs px-3 py-1 rounded-full border"
                                    style={{
                                      background: 'var(--component-bg)',
                                      borderColor: 'var(--border-color)',
                                      color: 'var(--accent)'
                                    }}
                                  >
                                    <MdTerminal className="w-3 h-3" />
                                    <span>{st}</span>
                                  </div>
                                ))}
                              </div>
                            )}

                            {msg.thoughts && (
                              <div className="border rounded-xl p-4 space-y-2 bg-[var(--component-bg)] border-[var(--border-color)]">
                                <div className="flex items-center gap-2 text-xs font-medium text-[var(--text-secondary)]">
                                  <RiRobot2Fill className="w-3.5 h-3.5" />
                                  <span>Thinking process</span>
                                </div>
                                <p className="text-xs leading-relaxed italic text-[var(--text-secondary)]">
                                  {msg.thoughts}
                                </p>
                              </div>
                            )}

                            {(msg.content || (isStreaming && idx === messages.length - 1)) && (
                              <div className="flex items-end gap-2">
                                <div
                                  className="backdrop-blur-sm rounded-2xl rounded-tl-md px-4 py-3 shadow-xl border inline-block"
                                  style={{
                                    background: 'var(--agent-bubble)',
                                    color: 'var(--agent-text)',
                                    borderColor: 'var(--border-color)',
                                  }}
                                >
                                  <div className="prose prose-sm max-w-none" style={{ color: 'var(--agent-text)' }}>
                                    {parseMessageContent(msg.content || " ").map((part, i) => (
                                      <div key={i}>
                                        {part.type === "text" && (
                                          <ReactMarkdown remarkPlugins={[remarkGfm]}>
                                            {part.content}
                                          </ReactMarkdown>
                                        )}
                                        {part.type === "image" && (
                                          <div className="my-3 rounded-xl overflow-hidden border shadow-lg relative w-fit group/media bg-[var(--component-bg)] border-[var(--border-color)]">
                                            <img
                                              src={part.url}
                                              alt="Generated Media"
                                              className="w-full h-auto max-h-[300px] object-contain transition-transform duration-500 group-hover/media:scale-[1.02]"
                                              loading="lazy"
                                            />
                                            <div className="absolute inset-0 bg-black/40 opacity-0 group-hover/media:opacity-100 transition-opacity duration-300 flex items-center justify-center gap-4">
                                              <button
                                                onClick={() => setSelectedMedia({ type: "image", url: part.url })}
                                                type="button"
                                                className="p-3 rounded-full bg-white/10 hover:bg-white/20 text-white backdrop-blur-md border border-white/20 transition-all hover:scale-110"
                                                title="View Full Screen"
                                              >
                                                <MdFullscreen className="w-6 h-6" />
                                              </button>
                                              <button
                                                onClick={() => handleDownloadFile(part.url, `image-${Date.now()}.png`)}
                                                type="button"
                                                className="p-3 rounded-full bg-white/10 hover:bg-white/20 text-white backdrop-blur-md border border-white/20 transition-all hover:scale-110 disabled:opacity-50"
                                                title="Download"
                                                disabled={downloadingUrl === part.url}
                                              >
                                                {downloadingUrl === part.url ? (
                                                  <BiLoaderAlt className="w-6 h-6 animate-spin" />
                                                ) : (
                                                  <MdFileDownload className="w-6 h-6" />
                                                )}
                                              </button>
                                            </div>
                                          </div>
                                        )}
                                        {part.type === "video" && (
                                          <div className="my-3 rounded-xl overflow-hidden border shadow-lg relative w-fit group/media bg-[var(--component-bg)] border-[var(--border-color)]">
                                            <video
                                              src={part.url}
                                              className="w-full h-auto max-h-[300px] transition-transform duration-500 group-hover/media:scale-[1.02]"
                                            />
                                            <div className="absolute top-4 right-4 flex flex-col gap-2 opacity-0 group-hover/media:opacity-100 transition-opacity duration-300 z-10">
                                              <button
                                                onClick={() => setSelectedMedia({ type: "video", url: part.url })}
                                                className="p-2 rounded-lg bg-black/60 hover:bg-black/80 text-white backdrop-blur-md border border-white/20 transition-all hover:scale-105"
                                                title="View Full Screen"
                                              >
                                                <MdFullscreen className="w-5 h-5" />
                                              </button>
                                              <button
                                                onClick={() => handleDownloadFile(part.url, `video-${Date.now()}.mp4`)}
                                                className="p-2 rounded-lg bg-black/60 hover:bg-black/80 text-white backdrop-blur-md border border-white/20 transition-all hover:scale-105 disabled:opacity-50"
                                                title="Download"
                                                disabled={downloadingUrl === part.url}
                                              >
                                                {downloadingUrl === part.url ? (
                                                  <BiLoaderAlt className="w-5 h-5 animate-spin" />
                                                ) : (
                                                  <MdFileDownload className="w-5 h-5" />
                                                )}
                                              </button>
                                            </div>
                                          </div>
                                        )}
                                        {part.type === "audio" && (
                                          <div className="my-3 flex items-center gap-3 p-3 rounded-xl border backdrop-blur-sm bg-[var(--component-bg)] border-[var(--border-color)]">
                                            <div
                                              className="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0"
                                              style={{ background: 'var(--component-hover)', color: 'var(--accent)' }}
                                            >
                                              <svg
                                                xmlns="http://www.w3.org/2000/svg"
                                                fill="none"
                                                viewBox="0 0 24 24"
                                                strokeWidth={1.5}
                                                stroke="currentColor"
                                                className="w-5 h-5"
                                              >
                                                <path
                                                  strokeLinecap="round"
                                                  strokeLinejoin="round"
                                                  d="M19.114 5.636a9 9 0 0 1 0 12.728M16.463 8.288a5.25 5.25 0 0 1 0 7.424M6.75 8.25l4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z"
                                                />
                                              </svg>
                                            </div>
                                            <audio src={part.url} controls className="w-full h-8" />
                                          </div>
                                        )}
                                      </div>
                                    ))}
                                  </div>
                                  {isStreaming && idx === messages.length - 1 && (
                                    <div className="flex gap-1 mt-2">
                                      <div
                                        className="w-2 h-2 rounded-full animate-bounce"
                                        style={{ background: 'var(--accent)', animationDelay: "0ms" }}
                                      ></div>
                                      <div
                                        className="w-2 h-2 rounded-full animate-bounce"
                                        style={{ background: 'var(--accent)', animationDelay: "150ms" }}
                                      ></div>
                                      <div
                                        className="w-2 h-2 rounded-full animate-bounce"
                                        style={{ background: 'var(--accent)', animationDelay: "300ms" }}
                                      ></div>
                                    </div>
                                  )}
                                </div>
                                <div className="opacity-0 group-hover/msg:opacity-100 transition-opacity">
                                  <CopyButton text={msg.content} />
                                </div>
                              </div>
                            )}

                            {msg.suggestions?.length > 0 && (
                              <div className="flex flex-wrap gap-2">
                                {msg.suggestions.map((sug, i) => (
                                  <button
                                    key={i}
                                    onClick={() => setInput(sug.prompt)}
                                    className="flex items-center gap-2 text-xs font-medium border px-3 py-2 rounded-lg transition-all hover:opacity-80"
                                    style={{
                                      background: 'var(--component-bg)',
                                      borderColor: 'var(--border-color)',
                                      color: 'var(--text-primary)'
                                    }}
                                  >
                                    <HiLightBulb className="w-3.5 h-3.5 text-yellow-500" />
                                    {sug.label}
                                  </button>
                                ))}
                              </div>
                            )}
                          </div>
                        </div>
                      </div>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </div>
        {/* {showDebug && (
          <div className="w-80 border-l backdrop-blur-xl overflow-y-auto p-4 custom-scrollbar animate-in slide-in-from-right duration-300 bg-[var(--header-bg)] border-[var(--border-color)]">
            <div className="flex items-center justify-between mb-4 pb-3 border-b border-[var(--border-color)]">
              <h3 className="text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
                Debug Logs
              </h3>
              <button
                type="button"
                onClick={() => setShowDebug(false)}
                className="text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
              >
                <MdClose className="w-4 h-4" />
              </button>
            </div>
            <div className="space-y-2">
              {debugLogs.length === 0 && (
                <p className="text-xs italic text-[var(--text-secondary)]">
                  No logs yet...
                </p>
              )}
              {debugLogs.map((log, i) => (
                <div
                  key={i}
                  className={`p-2 rounded-lg border text-xs font-mono ${log.type === "error"
                      ? "bg-red-500/10 border-red-500/20 text-red-400"
                      : log.type === "warn"
                        ? "bg-yellow-500/10 border-yellow-500/20 text-yellow-400"
                        : "bg-[var(--component-bg)] border-[var(--border-color)] text-[var(--text-secondary)]"
                    }`}
                >
                  <span className="text-[10px] opacity-50 mr-2">
                    [{log.time}]
                  </span>
                  {log.msg}
                </div>
              ))}
            </div>
          </div>
        )} */}
      </div>
      <footer className="flex-shrink-0 p-4">
        <div className="max-w-3xl mx-auto">
          {error && (
            <div className="mb-3 p-3 bg-red-500/10 border border-red-500/20 rounded-xl flex items-center justify-between">
              <span className="text-xs text-red-400 font-medium">
                Error: {error}
              </span>
              <button
                onClick={() => setError(null)}
                className="text-red-400 hover:text-red-300"
              >
                <MdClose className="w-4 h-4" />
              </button>
            </div>
          )}
          <form
            onSubmit={handleSendMessage}
            onDragOver={handleDragOver}
            onDragLeave={handleDragLeave}
            onDrop={handleDrop}
            className={`relative border rounded-2xl flex items-end gap-2 p-2 transition-all shadow-inner focus-within:border-[var(--accent)] ${
              isDragging ? "ring-2 ring-[var(--accent)] border-[var(--accent)] bg-[var(--accent)]/5" : ""
            }`}
            style={{
              background: 'var(--input-bg)',
              borderColor: 'var(--border-color)'
            }}
          >
            {isDragging && (
              <div className="absolute inset-0 z-50 flex items-center justify-center bg-[var(--accent)]/10 backdrop-blur-[2px] rounded-2xl pointer-events-none border-2 border-dashed border-[var(--accent)] animate-in fade-in duration-200">
                <div className="flex items-center justify-center gap-2 text-[var(--accent)]">
                  <IoAdd className="w-8 h-8 animate-bounce" />
                  <span className="text-sm font-bold uppercase tracking-wider">Drop image to upload</span>
                </div>
              </div>
            )}
            {attachments.length > 0 && (
              <div className="absolute bottom-full left-0 right-0 mb-2 flex flex-wrap gap-2 animate-in slide-in-from-bottom-2">
                {attachments.map((url, i) => (
                  <div key={i} className="relative group/att">
                    <img
                      src={url}
                      className="w-16 h-16 rounded-xl object-cover border-2 border-[var(--border-color)] shadow-lg"
                      alt="Attachment Preview"
                    />
                    <button
                      onClick={() => removeAttachment(url)}
                      type="button"
                      className="absolute -top-1.5 -right-1.5 p-1 bg-red-500 text-white rounded-full shadow-lg opacity-0 group-hover/att:opacity-100 transition-opacity"
                    >
                      <MdClose className="w-3 h-3" />
                    </button>
                  </div>
                ))}
              </div>
            )}
            <input
              type="file"
              ref={fileInputRef}
              onChange={handleFileUpload}
              className="hidden"
              accept="image/*"
            />
            <button
              onClick={() => fileInputRef.current?.click()}
              type="button"
              disabled={isUploading || isStreaming}
              className="flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all bg-[var(--component-hover)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50 shadow-sm relative overflow-hidden"
              title="Upload Image"
            >
              {isUploading ? (
                <>
                  <BiLoaderAlt className="w-4 h-4 animate-spin opacity-20" />
                  <span className="absolute inset-0 flex items-center justify-center text-[10px] font-bold text-[var(--accent)]">
                    {uploadProgress}%
                  </span>
                </>
              ) : (
                <IoAdd className="w-5 h-5" />
              )}
            </button>
            <textarea
              ref={textareaRef}
              value={input}
              onChange={(e) => setInput(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === "Enter" && !e.shiftKey) {
                  e.preventDefault();
                  handleSendMessage();
                }
              }}
              disabled={isStreaming}
              placeholder={isStreaming ? "Agent is thinking..." : "Type here or drop an image..."}
              className="flex-1 bg-transparent px-3 py-2.5 text-sm focus:outline-none resize-none max-h-32 placeholder:text-gray-500 custom-scrollbar text-[var(--text-primary)]"
              rows={1}
            />
            <button
              type="submit"
              disabled={!input.trim() || isStreaming}
              className="flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all disabled:opacity-50 disabled:cursor-not-allowed shadow-lg"
              style={{
                background: 'var(--accent)',
                color: 'var(--accent-text)'
              }}
            >
              {isStreaming ? (
                <BiLoaderAlt className="w-4 h-4 animate-spin" />
              ) : (
                <IoSend className="w-4 h-4" />
              )}
            </button>
          </form>
        </div>
      </footer>
      {selectedMedia && (
        <div
          className="fixed inset-0 z-[100] flex items-center justify-center bg-black/95 backdrop-blur-sm animate-in fade-in duration-300"
          onClick={() => setSelectedMedia(null)}
        >
          <button
            type="button"
            className="absolute top-6 right-6 p-2 rounded-full bg-white/5 hover:bg-white/10 text-white transition-all border border-white/10 z-[110]"
            onClick={() => setSelectedMedia(null)}
          >
            <MdClose className="w-6 h-6" />
          </button>
          <div
            className="max-w-[90vw] max-h-[90vh] relative animate-in zoom-in-95 duration-300"
            onClick={(e) => e.stopPropagation()}
          >
            {selectedMedia.type === "image" ? (
              <img
                src={selectedMedia.url}
                alt="Full Screen"
                className="w-full h-auto max-h-[90vh] object-contain rounded-lg shadow-2xl border border-white/10"
              />
            ) : (
              <video
                src={selectedMedia.url}
                controls
                autoPlay
                className="w-full h-auto max-h-[90vh] rounded-lg shadow-2xl border border-white/10"
              />
            )}
            <div className="flex justify-center">
              <button
                onClick={() =>
                  handleDownloadFile(
                    selectedMedia.url,
                    `${selectedMedia.type}-${Date.now()}.${selectedMedia.type === "image" ? "png" : "mp4"
                    }`
                  )
                }
                type="button"
                className="flex items-center gap-2 px-6 py-2.5 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-medium transition-all shadow-lg shadow-blue-500/20 disabled:opacity-50"
                disabled={downloadingUrl === selectedMedia.url}
              >
                {downloadingUrl === selectedMedia.url ? (
                  <>
                    <BiLoaderAlt className="w-5 h-5 animate-spin" />
                    Preparing...
                  </>
                ) : (
                  <>
                    <MdFileDownload className="w-5 h-5" />
                    Download
                  </>
                )}
              </button>
            </div>
          </div>
        </div>
      )}
      {showCustomColorPanel && (
        <div className="absolute inset-0 z-[100] flex items-center justify-center p-4">
          <div 
            className="absolute inset-0 bg-black/10 backdrop-blur-sm transition-opacity"
            onClick={() => setShowCustomColorPanel(false)}
          />
          <div className="relative w-full max-w-md bg-[var(--header-bg)] border border-[var(--border-color)] rounded-2xl shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200">
            <div className="flex items-center justify-between px-6 py-4 border-b border-[var(--border-color)]">
              <div className="flex items-center gap-2">
                <IoColorPalette className="w-5 h-5 text-[var(--accent)]" />
                <h3 className="font-bold text-[var(--text-primary)]">Customize Theme</h3>
              </div>
              <button 
                onClick={() => setShowCustomColorPanel(false)}
                className="p-1 rounded-lg hover:bg-[var(--component-hover)] text-[var(--text-secondary)] transition-colors"
              >
                <MdClose className="w-6 h-6" />
              </button>
            </div>
            
            <div className="p-6 max-h-[70vh] overflow-y-auto custom-scrollbar space-y-4">
              {[
                { 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) => (
                <div key={item.key} className="flex items-center justify-between p-3 rounded-xl border border-[var(--border-color)] bg-[var(--component-bg)]/50">
                  <span className="text-sm font-medium text-[var(--text-primary)]">{item.label}</span>
                  <div className="flex items-center gap-3">
                    <span className="text-[10px] font-mono text-[var(--text-secondary)] uppercase">
                      {currentTheme.colors[item.key]}
                    </span>
                    <input 
                      type="color" 
                      value={currentTheme.colors[item.key]?.startsWith('#') ? currentTheme.colors[item.key] : '#000000'} 
                      onChange={(e) => handleCustomColorChange(item.key, e.target.value)}
                      className="w-10 h-10 rounded-lg cursor-pointer border-none bg-transparent"
                    />
                  </div>
                </div>
              ))}
            </div>

            <div className="p-4 bg-[var(--component-bg)]/50 border-t border-[var(--border-color)]">
              <button 
                onClick={() => 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
              </button>
            </div>
          </div>
        </div>
      )}
    </main>
  );
};

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 (
    <div className="h-screen w-full flex flex-col bg-gray-100 transition-all duration-300 ease-in-out">
      <main className="flex flex-col items-center gap-2 w-full h-full overflow-y-auto pt-8">
        <CreateAgent useUser={useUser} usedIn={usedIn} />
      </main>
      <Toaster position="top-center" reverseOrder={false} />
    </div>
  );
};

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 (
    <div className="h-dvh w-full flex flex-col bg-blue-50/50 transition-all duration-300 ease-in-out">
      <Toaster position="top-center" reverseOrder={false} />
      <main className="flex flex-col items-center gap-2 w-full h-full overflow-y-auto pt-8">
        <EditAgent useUser={useUser} usedIn={usedIn} />
      </main>
    </div>
  );
};

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 (
    <ThemeProvider 
      attribute="class" 
      defaultTheme="system" 
      enableSystem
      disableTransitionOnChange
    >
      {children}
    </ThemeProvider>
  )
}

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 (
    <div className="flex-1 flex flex-col gap-8 items-center w-full max-w-[95%] sm:max-w-[90%] lg:max-w-[80%] relative pb-12">
      <div className="flex items-start gap-2 w-full">
        <Link
          href="/agents"
          className="p-2 hover:bg-gray-100 dark:hover:bg-secondary-bg rounded-full transition-colors group"
        >
          <IoArrowBackOutline className="w-4 h-4 text-gray-800 dark:text-primary-text group-hover:scale-110 transition-transform" />
        </Link>
        <div className="flex flex-col gap-2 w-full">
          <h1 className="text-2xl font-bold text-black dark:text-white">
            Prompt Any Assistant
          </h1>
          <p className="text-gray-500 dark:text-secondary-text text-sm font-medium">
            Use this to prompt up an assistant to help you with any topic!
          </p>
        </div>
      </div>
      <form onSubmit={handleArchitectAgent} className="space-y-8 w-full">
        <div className="space-y-4">
          <label className="text-lg font-semibold text-black dark:text-white block">
            What should your assistant be able to do and be knowledgeable in?
          </label>
          <div className="relative">
            <textarea
              value={prompt}
              autoFocus
              onChange={(e) => setPrompt(e.target.value)}
              placeholder="Ex: A helpful travel agent that finds the best destinations in Italy..."
              className="w-full bg-white dark:bg-secondary-bg border border-gray-200 dark:border-divider rounded-xl p-4 text-gray-900 dark:text-primary-text text-sm focus:outline-none focus:ring-2 focus:ring-black/10 dark:focus:ring-primary/10 focus:border-gray-400 dark:focus:border-primary transition-all resize-none min-h-[140px] shadow-sm"
              disabled={loading}
            />
          </div>
        </div>
        
        <div className="flex items-center gap-6">
        </div>

        <div className="flex flex-col gap-4">
          <button
            type="submit"
            disabled={loading || !prompt.trim()}
            className="w-full py-3 bg-blue-500 dark:bg-primary hover:bg-blue-600 dark:hover:bg-primary/90 disabled:bg-gray-200 dark:disabled:bg-divider disabled:text-gray-400 dark:disabled:text-secondary-text disabled:cursor-not-allowed text-white text-base font-semibold rounded-xl transition-all flex items-center justify-center gap-3 active:scale-[0.98]"
          >
            {loading ? (
              <>
                <BiLoaderAlt className="w-6 h-6 animate-spin" />
                <span>Creating agent...</span>
              </>
            ) : (
              "Create agent"
            )}
          </button>
          {loading && (
            <p className="text-center text-gray-400 dark:text-secondary-text text-sm animate-pulse">
              Analyzing prompt and building capabilities...
            </p>
          )}
          {error && (
            <div className="p-4 bg-red-50 dark:bg-red-900/10 border border-red-100 dark:border-red-900/30 rounded-xl text-red-600 dark:text-red-400 text-sm flex items-center gap-3 animate-in fade-in duration-300">
              <svg
                className="w-5 h-5 flex-shrink-0"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="2"
                  d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
                />
              </svg>
              {error}
            </div>
          )}
        </div>
      </form>
    </div>
  )
};

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 (
      <main className="flex-1 flex items-center justify-center">
        <div className="flex flex-col items-center gap-2">
          <BiLoaderAlt className="w-12 h-12 text-blue-600 animate-spin" />
          <p className="text-gray-500 font-medium animate-pulse">Loading Identity Data...</p>
        </div>
      </main>
    );
  }
  
  if (error) {
    return (
      <main className="flex-1 flex flex-col items-center justify-center h-full gap-4 text-center p-8">
        <div className="w-16 h-16 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center mb-2">
          <IoCloseOutline className="w-10 h-10 text-red-500 dark:text-red-400" />
        </div>
        <h2 className="text-2xl font-bold text-gray-900 dark:text-white">Access Denied</h2>
        <p className="text-gray-600 dark:text-secondary-text max-w-md font-medium">
          {error}
        </p>
        <Link 
          href="/agents"
          className="mt-4 px-8 py-3 bg-gray-900 dark:bg-primary text-white font-bold rounded-xl hover:bg-gray-800 dark:hover:bg-primary/90 transition-all shadow-lg active:scale-95"
        >
          Return to My Agents
        </Link>
      </main>
    );
  }

  return (
    <div className="flex-1 flex flex-col gap-8 items-center w-full max-w-[95%] sm:max-w-[90%] lg:max-w-[80%] relative">
      <div className="flex items-center justify-between pb-2 border-b border-gray-50 dark:border-divider w-full">
        <Link 
          href="/agents"
          className="flex items-center gap-2 text-gray-500 hover:text-gray-900 dark:text-secondary-text dark:hover:text-primary-text transition-colors text-sm font-medium"
        >
          <IoChevronBack className="w-4 h-4" />
          Back
        </Link>
        <div className="flex items-center gap-3">
          <Link 
            href={`${window.location.origin}/agents/${id}`}
            className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-xl text-sm font-bold text-white transition-all active:scale-95 shadow-sm"
          >
            <IoChatbubblesOutline className="w-4 h-4" />
            Chat
          </Link>
          <button 
            type="button"
            onClick={handleShare}
            className="flex items-center gap-2 px-4 py-2 border border-gray-100 dark:border-divider rounded-xl text-sm font-bold text-gray-600 dark:text-primary-text hover:bg-gray-50 dark:hover:bg-secondary-bg transition-all active:scale-95"
          >
            <IoShareOutline className="w-4 h-4" />
          </button>
          <button 
            type="button"
            onClick={handleDelete}
            disabled={saving}
            className="flex items-center gap-2 px-4 py-2 border border-red-50 dark:border-red-900/30 rounded-xl text-sm font-bold text-red-500 hover:bg-red-50 dark:hover:bg-red-900/10 transition-all active:scale-95 disabled:opacity-50"
          >
            <IoTrashOutline className="w-4 h-4" />
          </button>
          <Link 
            href="/docs/agents"
            target="_blank"
            className="flex items-center gap-2 px-3 py-1.5 bg-gray-50 dark:bg-secondary-bg border border-gray-100 dark:border-divider rounded-lg text-xs font-bold text-blue-600 dark:text-primary hover:bg-blue-50 dark:hover:bg-primary-bg transition-all active:scale-95 shadow-sm"
          >
            Docs
          </Link>
        </div>
      </div>
      <div className="flex flex-col items-center gap-2 w-full">
        <form id="edit-agent-form" onSubmit={handleSubmit} className="flex flex-col gap-12 w-full">
          <div className="flex flex-col md:flex-row md:items-center gap-8 w-full">
            <div className="flex items-center gap-8 w-full">
              <div className="relative">
                <div 
                  onClick={() => 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 ? (
                    <img src={formData.icon_url} alt="Profile" className="w-full h-full object-cover transition-transform group-hover:scale-110" />
                  ) : (
                    <div className="w-full h-full flex items-center justify-center bg-gray-50 dark:bg-primary-bg transition-colors group-hover:bg-gray-100 dark:group-hover:bg-secondary-bg">
                      <RiRobot2Fill className="w-12 h-12 text-gray-300 dark:text-divider group-hover:text-blue-500 transition-colors" />
                    </div>
                  )}
                  <div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity rounded-full">
                    <IoPencilOutline className="w-6 h-6 text-white" />
                  </div>
                  {uploading && (
                    <div className="absolute inset-0 bg-white/95 dark:bg-primary-bg/95 flex items-center justify-center rounded-full z-10 backdrop-blur-[1px]">
                      <div className="relative w-16 h-16">
                        <svg className="w-full h-full -rotate-90" viewBox="0 0 36 36">
                          <circle
                            cx="18"
                            cy="18"
                            r="16"
                            fill="none"
                            className="stroke-gray-100 dark:stroke-divider"
                            strokeWidth="3.5"
                          />
                          <circle
                            cx="18"
                            cy="18"
                            r="16"
                            fill="none"
                            className="stroke-black dark:stroke-primary transition-all duration-500 ease-out"
                            strokeWidth="3.5"
                            strokeDasharray="100.53"
                            strokeDashoffset={100.53 * (1 - uploadProgress / 100)}
                            strokeLinecap="round"
                          />
                        </svg>
                        <div className="absolute inset-0 flex items-center justify-center">
                          <span className="text-xs font-bold text-gray-900 dark:text-white">
                            {uploadProgress}%
                          </span>
                        </div>
                      </div>
                    </div>
                  )}
                </div>
                <input type="file" ref={fileInputRef} onChange={handleFileUpload} className="hidden" accept="image/*" />
              </div>
              <div className="flex flex-col gap-2 w-full">
                <div className="flex items-center gap-2 group/title w-full">
                  <input
                    type="text"
                    name="name"
                    value={formData.name}
                    onChange={handleInputChange}
                    className="text-3xl font-bold text-gray-900 dark:text-white leading-tight tracking-tight truncate bg-transparent border-none p-0 focus:ring-0 w-full"
                    placeholder="Unnamed Agent"
                    required
                  />
                  <IoPencilOutline className="w-5 h-5 text-gray-300 dark:text-divider opacity-0 group-hover/title:opacity-100 transition-opacity" />
                </div>
                <div className="flex items-center gap-3 mt-1 mr-auto">
                  {/* Toggles moved to better location */}
                </div>
              </div>
            </div>
            <div className="flex flex-col gap-4">
              <button
                type="submit"
                form="edit-agent-form"
                disabled={saving}
                className="px-6 py-3 whitespace-nowrap bg-black dark:bg-primary hover:bg-gray-800 dark:hover:bg-primary/90 disabled:opacity-50 text-white font-bold rounded-xl transition-all shadow-lg text-sm active:scale-95"
              >
                {saving ? "Saving..." : "Save Changes"}
              </button>
              <div className="flex items-center gap-1.5 p-1 bg-gray-100 dark:bg-secondary-bg rounded-2xl border border-gray-200 dark:border-divider w-fit">
                <div 
                  onClick={() => 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"
                  }`}
                >
                  <div className={`w-2 h-2 rounded-full transition-all duration-500 ${formData.is_published ? "bg-blue-500 shadow-[0_0_8px_rgba(59,130,246,0.5)]" : "bg-gray-300 dark:bg-gray-600"}`} />
                  <span className="text-xs font-bold tracking-wider">Publish</span>
                </div>
              </div>
            </div>
          </div>
          <div className="flex flex-col gap-12">
            <div className="flex flex-col gap-6">
              <div className="flex flex-col gap-2">
                <h2 className="text-xl font-bold text-gray-900 dark:text-white">Behavior & Identity</h2>
                <p className="text-sm text-gray-500 dark:text-secondary-text font-medium">
                  Shape how your agent thinks, responds, and describes itself
                </p>
              </div>
              <div className="flex flex-col gap-6">
                <div className="flex flex-col gap-2">
                  <div className="flex items-center justify-between border-l-4 border-black dark:border-primary pl-3 ml-1 mb-1">
                    <label className="text-base font-bold text-gray-900 dark:text-white">Instructions</label>
                    <button
                      type="button"
                      onClick={handleRealign}
                      disabled={isRealigning || JSON.stringify(formData.skill_ids.sort()) === JSON.stringify(initialSkills.sort())}
                      className="flex items-center gap-2 px-3 py-1.5 bg-violet-600 hover:bg-violet-700 disabled:bg-gray-100 disabled:text-gray-400 text-white text-xs font-bold rounded-lg transition-all active:scale-95 shadow-sm"
                      title={JSON.stringify(formData.skill_ids.sort()) === JSON.stringify(initialSkills.sort()) ? "No changes to skills" : "Sync instructions with current skills"}
                    >
                      {isRealigning ? <BiLoaderAlt className="animate-spin" /> : "✨ Realign with Skills"}
                    </button>
                  </div>
                  <div className="relative group">
                    <textarea
                      name="system_prompt"
                      value={formData.system_prompt}
                      onChange={handleInputChange}
                      className="w-full bg-white dark:bg-secondary-bg border border-gray-100 dark:border-divider rounded-2xl px-6 py-6 text-gray-800 dark:text-primary-text text-sm focus:ring-4 focus:ring-black/5 dark:focus:ring-primary/5 focus:border-black dark:focus:border-primary transition-all outline-none min-h-[200px] leading-relaxed shadow-sm font-medium"
                      placeholder="Define how your agent thinks and communicates..."
                      required
                    />
                    <p className="text-xs text-gray-400 dark:text-secondary-text font-medium ml-1">
                      Define how your agent thinks and communicates. Start with &quot;You are...&quot; and include specific examples.
                    </p>
                  </div>
                </div>
                <div className="flex flex-col gap-2">
                  <label className="text-base font-bold text-gray-900 dark:text-white border-l-4 border-black dark:border-primary pl-3 ml-1">Description</label>
                  <textarea
                    name="description"
                    value={formData.description}
                    onChange={handleInputChange}
                    className="w-full bg-white dark:bg-secondary-bg border border-gray-100 dark:border-divider rounded-2xl px-6 py-4 text-gray-800 dark:text-primary-text text-sm focus:ring-4 focus:ring-black/5 dark:focus:ring-primary/5 focus:border-black dark:focus:border-primary transition-all outline-none min-h-[100px] leading-relaxed shadow-sm font-medium"
                    placeholder="Add a description that describes your agent to others..."
                  />
                  <p className="text-xs text-gray-400 dark:text-secondary-text font-medium ml-1">
                    This will be visible to users when they discover your agent.
                  </p>
                </div>
              </div>
            </div>
            <div className="flex flex-col gap-6 border-t border-gray-50 dark:border-divider pt-12">
              <div className="flex flex-col gap-2">
                <h2 className="text-base font-bold text-gray-900 dark:text-white border-l-4 border-black dark:border-primary pl-3 ml-1">Theme & Appearance</h2>
                <p className="text-sm text-gray-500 dark:text-secondary-text font-medium ml-1">
                  Customize how your agent looks in the chat interface
                </p>
              </div>
              
              <div className="bg-white dark:bg-secondary-bg shadow-lg rounded-3xl p-8 border border-gray-100 dark:border-divider flex flex-col lg:flex-row gap-8">
                {/* Theme Selection */}
                <div className="flex-1 flex flex-col gap-4">
                  <h4 className="text-xs text-gray-400 dark:text-secondary-text font-bold uppercase tracking-wider ml-1">Select Theme</h4>
                  <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
                    {Object.values(themes || {}).map((theme) => (
                      <button
                        key={theme.id}
                        type="button"
                        onClick={() => 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"
                        }`}
                      >
                        <div 
                          className="w-full aspect-video rounded-xl shadow-inner border border-black/5 flex items-center justify-center relative overflow-hidden"
                          style={{ background: theme.colors.background }}
                        >
                          <div className="flex flex-col gap-1 w-[60%]">
                            <div className="h-1.5 w-[80%] rounded-full opacity-40" style={{ background: theme.colors.foreground }}></div>
                            <div className="h-1.5 w-[50%] rounded-full opacity-40 ml-auto" style={{ background: theme.colors.userBubble }}></div>
                          </div>
                        </div>
                        <span className={`text-xs font-bold transition-colors ${
                          formData.theme === theme.id ? "text-black dark:text-white" : "text-gray-500 dark:text-secondary-text group-hover:text-gray-700 dark:group-hover:text-primary-text"
                        }`}>
                          {theme.name}
                        </span>
                        {formData.theme === theme.id && (
                          <div className="absolute -top-2 -right-2 w-5 h-5 bg-black dark:bg-primary text-white rounded-full flex items-center justify-center shadow-lg">
                            <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" d="M5 13l4 4L19 7" />
                            </svg>
                          </div>
                        )}
                      </button>
                    ))}
                  </div>
                </div>
                <div className="flex-1 flex flex-col gap-4">
                  <h4 className="text-xs text-gray-400 dark:text-secondary-text font-bold uppercase tracking-wider ml-1">Chat Preview</h4>
                  <div 
                    className="w-full h-[300px] rounded-3xl overflow-hidden shadow-2xl border border-gray-100 dark:border-divider relative"
                    style={{
                      background: (themes[formData.theme] || themes.cosmic)?.colors.background,
                      color: (themes[formData.theme] || themes.cosmic)?.colors.foreground
                    }}
                  >
                    <div 
                      className="px-4 py-3 flex items-center gap-2 border-b"
                      style={{ 
                        background: (themes[formData.theme] || themes.cosmic)?.colors.headerBg,
                        borderColor: (themes[formData.theme] || themes.cosmic)?.colors.border
                      }}
                    >
                      <div className="w-8 h-8 rounded-full bg-gray-400 overflow-hidden">
                        {formData.icon_url ? (
                          <img src={formData.icon_url} className="w-full h-full object-cover" />
                        ) : (
                          <RiRobot2Fill className="w-full h-full p-1.5 text-white/50" />
                        )}
                      </div>
                      <div className="flex flex-col">
                        <span className="text-xs font-bold truncate">{formData.name || "Agent Name"}</span>
                        <span className="text-[10px] opacity-60">Online</span>
                      </div>
                    </div>
                    <div className="p-4 flex flex-col gap-4 h-[180px] overflow-y-auto">
                      <div className="flex flex-col items-end gap-1 max-w-[85%] ml-auto">
                        <div 
                          className="px-3 py-2 rounded-2xl text-xs font-medium shadow-sm"
                          style={{ 
                            background: (themes[formData.theme] || themes.cosmic)?.colors.userBubble,
                            color: (themes[formData.theme] || themes.cosmic)?.colors.userText
                          }}
                        >
                          Hi! How can you help me today?
                        </div>
                      </div>
                      <div className="flex flex-col items-start gap-1 max-w-[85%]">
                        <div 
                          className="px-3 py-2 rounded-2xl text-xs font-medium border shadow-sm"
                          style={{ 
                            background: (themes[formData.theme] || themes.cosmic)?.colors.agentBubble,
                            color: (themes[formData.theme] || themes.cosmic)?.colors.agentText,
                            borderColor: (themes[formData.theme] || themes.cosmic)?.colors.border
                          }}
                        >
                          I can help you with tasks, answer questions, and much more using {formData.skill_ids.length} configured skills!
                        </div>
                      </div>
                    </div>
                    <div className="absolute bottom-0 w-full p-4">
                      <div 
                        className="h-10 rounded-xl flex items-center px-4 gap-2 border shadow-inner"
                        style={{ 
                          background: (themes[formData.theme] || themes.cosmic)?.colors.inputBg,
                          borderColor: (themes[formData.theme] || themes.cosmic)?.colors.border
                        }}
                      >
                        <span className="text-xs opacity-30 flex-1">Type a message...</span>
                        <div 
                          className="w-6 h-6 rounded-lg flex items-center justify-center"
                          style={{ background: (themes[formData.theme] || themes.cosmic)?.colors.accent }}
                        >
                          <div className="w-1.5 h-1.5 rounded-full" style={{ background: (themes[formData.theme] || themes.cosmic)?.colors.accentText }}></div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <p className="text-xs text-gray-400 dark:text-secondary-text font-medium ml-1">
                This theme will be automatically applied to the chat interface for all users.
              </p>
            </div>

            <div className="flex flex-col gap-6 border-t border-gray-50 dark:border-divider pt-12">
              <h2 className="text-base font-bold text-gray-900 dark:text-white border-l-4 border-black dark:border-primary pl-3 ml-1">Capabilities</h2>
              <div className="bg-white dark:bg-secondary-bg shadow-lg rounded-3xl p-8 border border-gray-100 dark:border-divider flex flex-col gap-4">
                <div className="relative">
                  <input
                    type="text"
                    placeholder="Type to search and add skills (e.g. image generation, web search)..."
                    value={searchTerm}
                    onChange={(e) => 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"
                  />
                </div>
                <div className="flex flex-col gap-4">
                  <h4 className="text-xs text-gray-400 dark:text-secondary-text ml-1">
                    Active Agent Skills ({formData.skill_ids.length})
                  </h4>
                  <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                    {formData.skill_ids.length > 0 ? (
                      formData.skill_ids.map((id) => {
                        const skill = availableSkills.find(s => s.id === id);
                        if (!skill) return null;
                        return (
                          <button
                            key={skill.id}
                            type="button"
                            onClick={() => 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"
                          >
                            <div className="flex flex-col text-left">
                              <span title={skill.name} className="text-base font-bold text-gray-900 dark:text-white line-clamp-1">
                                {skill.name}
                              </span>
                              <span title={skill.description} className="text-xs text-gray-400 dark:text-secondary-text line-clamp-2">
                                {skill.description}
                              </span>
                            </div>
                            <FaRegTrashCan size={18} className="absolute right-4 opacity-0 group-hover:opacity-100 transition-all duration-300 ease-in-out bg-white dark:bg-primary-bg text-red-500" />
                          </button>
                        );
                      })
                    ) : (
                      <div className="col-span-full p-12 rounded-2xl border border-dashed border-gray-200 dark:border-divider text-center bg-white/50 dark:bg-primary-bg/50">
                        <p className="text-sm text-gray-400 dark:text-secondary-text">No skills configured yet</p>
                      </div>
                    )}
                  </div>
                    <div className="border-t border-gray-200/50 dark:border-divider pt-4">
                      <h4 className="text-xs text-gray-400 dark:text-secondary-text ml-1 mb-2">
                        Available in Registry
                      </h4>
                      <div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-[400px] overflow-y-auto pr-2 custom-scrollbar">
                        {availableSkills
                          .filter(skill => 
                            !formData.skill_ids.includes(skill.id) && 
                            (skill.name.toLowerCase().includes(searchTerm.toLowerCase()) || 
                              skill.id.toLowerCase().includes(searchTerm.toLowerCase()))
                          )
                          .map((skill) => (
                            <button
                              key={skill.id}
                              type="button"
                              onClick={() => {
                                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"
                            >
                              <div className="flex flex-col text-left">
                                <span title={skill.name} className="text-base font-bold text-gray-900 dark:text-white line-clamp-1">
                                  {skill.name}
                                </span>
                                <span title={skill.description} className="text-xs text-gray-400 dark:text-secondary-text line-clamp-2">
                                  {skill.description}
                                </span>
                              </div>
                              <span className="text-lg text-white bg-black dark:bg-primary rounded-full p-0.5 w-5 h-5 flex items-center justify-center flex-shrink-0">+</span>
                            </button>
                          ))
                        }
                      </div>
                    </div>
                </div>
              </div>
              <p className="text-xs text-gray-400 dark:text-secondary-text font-medium ml-1">
                Manage tools and skills your agent can use to perform tasks
              </p>
            </div>
          </div>
          {error && (
            <div className="p-4 bg-red-50 dark:bg-red-900/10 border border-red-100 dark:border-red-900/30 rounded-xl text-red-600 dark:text-red-400 text-sm flex items-center gap-3 animate-shake">
              <svg className="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
              </svg>
              <span className="font-medium">{error}</span>
            </div>
          )}
        </form>
      </div>
      {showRealignModal && (
        <div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[100] flex items-center justify-center p-4 animate-in fade-in duration-200">
          <div className="bg-white dark:bg-secondary-bg rounded-3xl w-full max-w-4xl max-h-[90vh] overflow-hidden shadow-2xl flex flex-col animate-in zoom-in-95 duration-200">
            <div className="px-8 py-6 border-b border-gray-100 dark:border-divider flex items-center justify-between bg-violet-50/50 dark:bg-violet-900/10">
              <div className="flex items-center gap-3">
                <div className="w-10 h-10 rounded-xl bg-violet-600 flex items-center justify-center text-white shadow-lg shadow-violet-200 dark:shadow-none">
                  <RiRobot2Fill className="w-6 h-6" />
                </div>
                <div>
                  <h3 className="text-xl font-bold text-gray-900 dark:text-white">Review Brain Realignment</h3>
                  <p className="text-xs text-gray-500 dark:text-secondary-text font-medium">The AI has refactored your instructions to match your new skills.</p>
                </div>
              </div>
              <button
                onClick={() => 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"
              >
                <MdClose className="w-6 h-6" />
              </button>
            </div>

            <div className="flex-1 overflow-y-auto p-8 flex flex-col md:flex-row gap-6 custom-scrollbar">
              <div className="flex-1 flex flex-col gap-3">
                <label className="text-xs font-bold text-gray-400 dark:text-secondary-text uppercase tracking-wider ml-1">Current Instructions</label>
                <div className="flex-1 p-5 bg-gray-50 dark:bg-primary-bg border border-gray-100 dark:border-divider rounded-2xl text-sm text-gray-600 dark:text-secondary-text font-medium whitespace-pre-wrap overflow-y-auto max-h-[400px]">
                  {formData.system_prompt}
                </div>
              </div>
              <div className="hidden md:flex items-center justify-center text-violet-300 dark:text-violet-500">
                <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
                </svg>
              </div>
              <div className="flex-1 flex flex-col gap-3">
                <label className="text-xs font-bold text-violet-600 dark:text-violet-400 uppercase tracking-wider ml-1">Proposed Instructions</label>
                <textarea
                  value={realignedPrompt}
                  onChange={(e) => 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]"
                />
              </div>
            </div>

            <div className="px-8 py-6 bg-gray-50 dark:bg-primary-bg border-t border-gray-100 dark:border-divider flex items-center justify-end gap-3">
              <button
                onClick={() => 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
              </button>
              <button
                onClick={applyRealignedPrompt}
                className="px-8 py-2.5 bg-violet-600 hover:bg-violet-700 text-white text-sm font-bold rounded-xl transition-all shadow-lg shadow-violet-200 dark:shadow-none active:scale-95"
              >
                Accept & Apply
              </button>
            </div>
          </div>
        </div>
      )}
      {showIconPromptModal && (
        <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
          <div className="bg-white dark:bg-secondary-bg w-full max-w-lg rounded-3xl shadow-2xl border border-gray-100 dark:border-divider overflow-hidden transform animate-in zoom-in-95 duration-200">
            <div className="p-6 border-b border-gray-100 dark:border-divider flex items-center justify-between bg-gray-50/50 dark:bg-primary-bg/50">
              <h3 className="text-xl font-bold dark:text-white flex items-center gap-2">
                <span className="text-2xl">✨</span> Customize AI Icon Prompt
              </h3>
              <button
                onClick={() => 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"
              >
                <IoCloseOutline className="w-6 h-6" />
              </button>
            </div>

            <div className="p-8">
              <p className="text-sm text-gray-500 dark:text-secondary-text mb-6">
                Tell the AI what kind of icon you want. You can describe style, colors, and specific elements.
              </p>

              <div className="space-y-4">
                <textarea
                  value={iconPrompt}
                  onChange={(e) => 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"
                />

                <div className="flex gap-3 pt-4">
                  <button
                    onClick={() => 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
                  </button>
                  <button
                    onClick={() => 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 ? (
                      <>
                        <BiLoaderAlt className="w-5 h-5 animate-spin" />
                        Generating...
                      </>
                    ) : (
                      <>
                        <span className="text-lg">✨</span>
                        Generate Icon
                      </>
                    )}
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
      {showIconSelectionModal && (
        <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
          <div className="bg-white dark:bg-secondary-bg w-full max-w-md rounded-[2.5rem] shadow-2xl border border-gray-100 dark:border-divider overflow-hidden transform animate-in zoom-in-95 duration-200">
            <div className="p-8 border-b border-gray-50 dark:border-divider flex items-center justify-between">
              <div>
                <h3 className="text-2xl font-black dark:text-white leading-tight">Profile Icon</h3>
                <p className="text-sm text-gray-500 dark:text-secondary-text mt-1 font-medium">Choose how to update your agent's look</p>
              </div>
              <button 
                onClick={() => 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"
              >
                <IoCloseOutline className="w-6 h-6" />
              </button>
            </div>
            
            <div className="p-8 grid grid-cols-1 gap-4">
              <button
                onClick={() => {
                  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]"
              >
                <div className="w-16 h-16 rounded-2xl bg-white dark:bg-secondary-bg shadow-sm flex items-center justify-center text-gray-400 group-hover:text-blue-500 transition-colors duration-300">
                  <IoImageOutline className="w-8 h-8" />
                </div>
                <div className="text-center">
                  <h4 className="font-bold text-gray-900 dark:text-white text-lg">Upload Photo</h4>
                  <p className="text-sm text-gray-500 dark:text-secondary-text mt-1">Pick a file from your device</p>
                </div>
              </button>

              <button
                onClick={() => {
                  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]"
              >
                <div className="w-16 h-16 rounded-2xl bg-blue-600 shadow-lg shadow-blue-500/30 flex items-center justify-center text-white transform transition-transform duration-500 group-hover:rotate-12 group-hover:scale-110">
                  <IoSparklesOutline className="w-8 h-8" />
                </div>
                <div className="text-center">
                  <h4 className="font-bold text-blue-600 dark:text-primary text-lg">Generate with AI</h4>
                  <p className="text-sm text-blue-500/70 dark:text-primary/70 mt-1">Create unique icon from prompt</p>
                </div>
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

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 (
      <div className="flex flex-col items-center justify-center py-32 gap-3 w-full">
        <BiLoaderAlt className="w-8 h-8 text-gray-400 dark:text-secondary-text animate-spin" />
        <p className="text-gray-400 dark:text-secondary-text text-sm">Loading agent profile...</p>
      </div>
    );
  }

  if (error || !profile) {
    return (
      <div className="flex flex-col items-center justify-center py-32 gap-2 w-full">
        <RiRobot2Fill className="w-12 h-12 text-gray-300 dark:text-secondary-text mx-auto" />
        <p className="text-gray-800 dark:text-primary-text font-bold">Agent not found</p>
        <p className="text-gray-500 dark:text-secondary-text text-sm">{error}</p>
      </div>
    );
  }

  const { agent, total_messages, total_chats, recent_chats } = profile;

  const chatUrl = agent.agent_id
    ? `/agents/${agent.agent_id}`
    : `/agents/${agent.id}`;

  return (
    <div className="w-full max-w-5xl mx-auto px-4 sm:px-6 pb-16">
      <div className="border-b border-gray-200 dark:border-divider py-6">
        <div className="flex flex-col md:flex-row md:items-start gap-5">
          <div className="flex items-center gap-5">
            <div className="relative w-16 h-16 rounded-full overflow-hidden bg-gray-100 dark:bg-secondary-bg border-2 border-gray-200 dark:border-divider shrink-0">
              {agent.icon_url ? (
                <Image src={agent.icon_url} alt={agent.name} fill className="object-cover" />
              ) : (
                <div className="w-full h-full flex items-center justify-center">
                  <RiRobot2Fill className="w-8 h-8 text-gray-400 dark:text-secondary-text" />
                </div>
              )}
            </div>
            <div className="flex-1 min-w-0">
              <div className="flex items-center gap-2 flex-wrap">
                <h1 className="text-2xl font-bold text-black dark:text-white">
                  {agent.name}
                </h1>
                {agent.is_published && (
                  <span className="flex items-center gap-1 text-xs font-bold px-2 py-0.5 rounded-full bg-blue-50 dark:bg-white/10 text-blue-600 dark:text-gray-300 border border-blue-100 dark:border-white/10">
                    <MdOutlineVerified className="w-3 h-3" /> Public
                  </span>
                )}
              </div>
              {agent.description && (
                <p className="text-gray-500 dark:text-secondary-text text-sm mt-1 leading-relaxed max-w-xl">
                  {agent.description}
                </p>
              )}
              { (agent.owner_username || agent.owner_email) && (
                <p className="text-xs text-gray-400 dark:text-secondary-text mt-1.5">
                  by{" "}
                  <span className="text-gray-600 dark:text-gray-300 font-medium">
                    {agent.owner_username || agent.owner_email.split("@")[0]}
                  </span>
                </p>
              )}
            </div>
          </div>
          <div className="flex items-center gap-2 shrink-0">
            <button
              type="button"
              onClick={handleLike}
              className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-100 dark:bg-secondary-bg hover:bg-gray-200 dark:hover:bg-white/10 border border-gray-200 dark:border-divider text-sm transition-all"
            >
              {liked ? (
                <IoHeart className="w-4 h-4 text-red-500" />
              ) : (
                <IoHeartOutline className="w-4 h-4 text-gray-500 dark:text-secondary-text" />
              )}
              <span className="font-medium text-gray-700 dark:text-gray-300">
                {agent.like_count || 0}
              </span>
            </button>
            <button
              onClick={handleShare}
              title={copied ? "Copied!" : "Share link"}
              className="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-gray-100 dark:bg-secondary-bg hover:bg-gray-200 dark:hover:bg-white/10 border border-gray-200 dark:border-divider text-sm transition-all"
            >
              <IoShareOutline className="w-4 h-4 text-gray-500 dark:text-secondary-text" />
              {copied && <span className="text-xs text-green-500 dark:text-green-400">Copied!</span>}
            </button>
            <Link
              href={chatUrl}
              className="flex items-center gap-2 px-4 py-2 bg-violet-600 hover:bg-violet-500 text-white text-sm font-bold rounded-lg transition-all shadow-sm"
            >
              <IoChatbubbleEllipsesSharp className="w-4 h-4" />
              Chat
            </Link>
          </div>
        </div>
      </div>
      <div className="grid grid-cols-1 lg:grid-cols-[1fr_280px] gap-8 mt-8">
        <div className="space-y-8">
          {agent.skills && agent.skills.length > 0 && (
            <section>
              <p className="text-[11px] font-bold text-gray-400 dark:text-secondary-text uppercase tracking-widest mb-3">
                Workflows
              </p>
              <div className="flex flex-wrap gap-2">
                {agent.skills.map((skill) => (
                  <span
                    key={skill.id}
                    className="flex items-center gap-1.5 px-3 py-1.5 bg-gray-100 dark:bg-secondary-bg border border-gray-200 dark:border-divider rounded-lg text-xs text-gray-600 dark:text-gray-300 font-medium hover:bg-gray-200 dark:hover:bg-white/10 transition-colors"
                  >
                    <FiZap className="w-3 h-3 text-violet-500 dark:text-violet-400" />
                    {skill.name}
                  </span>
                ))}
              </div>
            </section>
          )}
          {agent.description && (
            <section>
              <p className="text-[11px] font-bold text-gray-400 dark:text-secondary-text uppercase tracking-widest mb-3">
                About {agent.name}
              </p>
              <p className="text-gray-700 dark:text-gray-300 text-sm leading-relaxed">
                {agent.description}
              </p>
            </section>
          )}
          {agent.welcome_message && (
            <section className="bg-gray-50 dark:bg-secondary-bg border border-gray-200 dark:border-divider rounded-xl p-4">
              <p className="text-[11px] font-bold text-gray-400 dark:text-secondary-text uppercase tracking-widest mb-2">
                Greeting
              </p>
              <p className="text-gray-600 dark:text-gray-300 text-sm italic leading-relaxed">
                "{agent.welcome_message}"
              </p>
            </section>
          )}
          <section>
            <p className="text-[11px] font-bold text-gray-400 dark:text-secondary-text uppercase tracking-widest mb-3">
              Details
            </p>
            <div className="space-y-2.5">
              <DetailRow label="Messages" value={formatCount(total_messages)} />
              <DetailRow label="Chats"    value={formatCount(total_chats)} />
              <DetailRow label="Created"  value={timeAgo(agent.created_at)} />
              {agent.skills && agent.skills.length > 0 && (
                <DetailRow label="Skills" value={agent.skills.length.toString()} />
              )}
            </div>
          </section>
        </div>
        <div className="space-y-4">
          {recent_chats && recent_chats.length > 0 && (
            <div className="bg-gray-50 dark:bg-secondary-bg border border-gray-200 dark:border-divider rounded-2xl p-4">
              <div className="flex items-center gap-2 mb-4">
                <FiClock className="w-3.5 h-3.5 text-gray-400 dark:text-secondary-text" />
                <p className="text-[11px] font-bold text-gray-400 dark:text-secondary-text uppercase tracking-widest">
                  Recent chats with this agent
                </p>
              </div>
              <div className="space-y-1">
                {recent_chats.map((chat) => (
                  <Link
                    key={chat.id}
                    href={
                      chat.agent_slug
                        ? `/agents/${chat.agent_slug}/${chat.id}`
                        : `/agents/${chat.agent_id}/${chat.id}`
                    }
                    className="flex items-center gap-3 p-2.5 rounded-xl hover:bg-gray-100 dark:hover:bg-white/5 transition-colors group"
                  >
                    <div className="w-8 h-8 rounded-lg bg-gray-200 dark:bg-gray-700 flex items-center justify-center shrink-0">
                      <IoChatbubbleEllipsesSharp className="w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-gray-800 dark:group-hover:text-white transition-colors" />
                    </div>
                    <div className="flex-1 min-w-0">
                      <p className="text-sm font-medium text-gray-800 dark:text-gray-200 truncate group-hover:text-black dark:group-hover:text-white transition-colors">
                        {chat.title || "New Chat"}
                      </p>
                      <p className="text-[11px] text-gray-400 dark:text-secondary-text">
                        {chat.message_count} msg{chat.message_count !== 1 ? "s" : ""} · {timeAgo(chat.updated_at)}
                      </p>
                    </div>
                  </Link>
                ))}
              </div>
              <Link
                href={chatUrl}
                className="mt-3 flex items-center justify-center gap-2 w-full py-2.5 rounded-xl bg-gray-100 dark:bg-white/5 hover:bg-gray-200 dark:hover:bg-white/10 border border-gray-200 dark:border-divider text-sm text-gray-600 dark:text-gray-300 hover:text-black dark:hover:text-white transition-all font-medium"
              >
                <HiPlus className="w-4 h-4" />
                New chat
              </Link>
            </div>
          )}
          {agent.initial_suggestions && agent.initial_suggestions.length > 0 && (
            <div className="bg-gray-50 dark:bg-secondary-bg border border-gray-200 dark:border-divider rounded-2xl p-4">
              <p className="text-[11px] font-bold text-gray-400 dark:text-secondary-text uppercase tracking-widest mb-3">
                Try asking
              </p>
              <div className="space-y-2">
                {agent.initial_suggestions.slice(0, 4).map((s, i) => (
                  <Link
                    key={i}
                    href={`${chatUrl}?prompt=${encodeURIComponent(s.prompt || s.label || "")}`}
                    className="block text-sm text-gray-600 dark:text-gray-300 hover:text-black dark:hover:text-white bg-white dark:bg-white/5 hover:bg-gray-100 dark:hover:bg-white/10 border border-gray-200 dark:border-divider rounded-xl px-3 py-2 transition-all truncate"
                  >
                    {s.label || s.prompt}
                  </Link>
                ))}
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function DetailRow({ label, value }) {
  return (
    <div className="flex items-center gap-4">
      <span className="text-sm text-gray-400 dark:text-secondary-text w-24 shrink-0">{label}</span>
      <span className="text-sm text-gray-800 dark:text-primary-text font-medium">{value}</span>
    </div>
  );
}


================================================
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
Download .txt
gitextract_79z3ryl8/

├── .gitignore
├── README.md
├── client/
│   ├── .gitignore
│   ├── app/
│   │   ├── agents/
│   │   │   ├── [agent_id]/
│   │   │   │   ├── [conversation_id]/
│   │   │   │   │   └── page.js
│   │   │   │   ├── page.js
│   │   │   │   └── profile/
│   │   │   │       └── page.js
│   │   │   ├── create/
│   │   │   │   └── page.js
│   │   │   ├── edit/
│   │   │   │   └── [id]/
│   │   │   │       └── page.js
│   │   │   ├── loading.js
│   │   │   └── page.js
│   │   ├── globals.css
│   │   ├── layout.js
│   │   └── page.js
│   ├── components/
│   │   └── AgentClientWrapper.js
│   ├── context/
│   │   └── fetchAgentData.js
│   ├── jsconfig.json
│   ├── next.config.mjs
│   ├── package.json
│   └── postcss.config.mjs
├── package.json
├── packages/
│   └── agents/
│       ├── .gitignore
│       ├── README.md
│       ├── THEME_SETUP.md
│       ├── babel.config.json
│       ├── package.json
│       ├── postcss.config.js
│       ├── src/
│       │   ├── AgentProfile.jsx
│       │   ├── AiAgent.jsx
│       │   ├── CreatePage.jsx
│       │   ├── EditPage.jsx
│       │   ├── components/
│       │   │   ├── AgentThemeProvider.jsx
│       │   │   ├── CreateAgent.jsx
│       │   │   ├── EditAgent.jsx
│       │   │   ├── ProfileAgent.jsx
│       │   │   └── themes.jsx
│       │   ├── index.js
│       │   ├── tailwind.css
│       │   └── utils/
│       │       └── server.js
│       └── tailwind.config.js
└── server/
    ├── .gitignore
    ├── app/
    │   ├── main.py
    │   ├── routers/
    │   │   ├── __init__.py
    │   │   └── agent_proxy.py
    │   └── utils/
    │       └── agent_helper.py
    └── requirements.txt
Download .txt
SYMBOL INDEX (42 symbols across 19 files)

FILE: client/app/agents/[agent_id]/[conversation_id]/page.js
  function Page (line 5) | async function Page({ params }) {

FILE: client/app/agents/[agent_id]/page.js
  function Page (line 5) | async function Page({ params }) {

FILE: client/app/agents/[agent_id]/profile/page.js
  function AgentProfileRoute (line 16) | function AgentProfileRoute() {

FILE: client/app/agents/create/page.js
  function CreateAgentRoute (line 15) | function CreateAgentRoute() {

FILE: client/app/agents/edit/[id]/page.js
  function EditAgentRoute (line 16) | function EditAgentRoute() {

FILE: client/app/agents/loading.js
  function Loading (line 1) | function Loading() {

FILE: client/app/agents/page.js
  function AgentsLibrary (line 88) | function AgentsLibrary() {

FILE: client/app/layout.js
  function RootLayout (line 19) | function RootLayout({ children }) {

FILE: client/app/page.js
  function WelcomePage (line 7) | function WelcomePage() {

FILE: client/components/AgentClientWrapper.js
  function AgentClientWrapper (line 15) | function AgentClientWrapper({ initialAgentDetails, initialHistory = null...

FILE: client/context/fetchAgentData.js
  function fetchAgentData (line 1) | async function fetchAgentData(id, cookieHeader, byName = false) {
  function fetchHistoryData (line 15) | async function fetchHistoryData(agentSlug, conversationId, cookieHeader) {

FILE: client/next.config.mjs
  method rewrites (line 14) | async rewrites() {

FILE: packages/agents/src/AiAgent.jsx
  constant BASE_URL (line 18) | const BASE_URL = "/api/agents";

FILE: packages/agents/src/components/CreateAgent.jsx
  constant BASE_URL (line 9) | const BASE_URL = "/api/agents";

FILE: packages/agents/src/components/EditAgent.jsx
  constant BASE_URL (line 13) | const BASE_URL = "/api/agents";

FILE: packages/agents/src/components/ProfileAgent.jsx
  constant BASE_URL (line 20) | const BASE_URL = "/api/agents";
  function timeAgo (line 22) | function timeAgo(dateStr) {
  function formatCount (line 38) | function formatCount(n) {
  function ProfileAgent (line 54) | function ProfileAgent({ useUser, usedIn = "muapiapp" }) {
  function DetailRow (line 338) | function DetailRow({ label, value }) {

FILE: server/app/main.py
  function root (line 26) | async def root():
  function health_check (line 30) | async def health_check():

FILE: server/app/routers/agent_proxy.py
  function get_user_agents (line 11) | async def get_user_agents():
  function get_template_agents (line 15) | async def get_template_agents():
  function get_featured_agents (line 19) | async def get_featured_agents():
  function get_suggested_agents (line 23) | async def get_suggested_agents(request: Request):
  function create_agent (line 28) | async def create_agent(request: Request):
  function get_agent_skills (line 34) | async def get_agent_skills():
  function get_agent_by_slug (line 38) | async def get_agent_by_slug(slug: str):
  function get_agent_profile (line 42) | async def get_agent_profile(slug: str):
  function update_agent_by_slug (line 46) | async def update_agent_by_slug(slug: str, request: Request):
  function agent_chat (line 51) | async def agent_chat(slug: str, request: Request):
  function like_agent (line 56) | async def like_agent(slug: str, request: Request):
  function get_conversation_history (line 61) | async def get_conversation_history(slug: str, conv_id: str):
  function get_agent_preview (line 65) | async def get_agent_preview(slug: str, request: Request):
  function get_prediction_result (line 72) | async def get_prediction_result(request_id: str):
  function generate_flux_image (line 76) | async def generate_flux_image(request: Request):
  function get_upload_url (line 83) | async def get_upload_url(request: Request):
  function get_signed_url (line 88) | async def get_signed_url(request: Request):

FILE: server/app/utils/agent_helper.py
  function get_api_key (line 12) | async def get_api_key():
  function proxy_request (line 18) | async def proxy_request(method: str, path: str, payload: Optional[dict] ...
Condensed preview — 45 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (195K chars).
[
  {
    "path": ".gitignore",
    "chars": 13,
    "preview": "node_modules\n"
  },
  {
    "path": "README.md",
    "chars": 1652,
    "preview": "# Open-Poe-AI\n\nOpen-source, self-hosted alternative to [Poe AI](https://poe.com) — chat with multiple large language mod"
  },
  {
    "path": "client/.gitignore",
    "chars": 499,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "client/app/agents/[agent_id]/[conversation_id]/page.js",
    "chars": 699,
    "preview": "import { cookies } from \"next/headers\"; \nimport { fetchAgentData, fetchHistoryData } from \"@/context/fetchAgentData\";\nim"
  },
  {
    "path": "client/app/agents/[agent_id]/page.js",
    "chars": 482,
    "preview": "import { cookies } from \"next/headers\";\nimport { fetchAgentData } from \"@/context/fetchAgentData\";\nimport AgentClientWra"
  },
  {
    "path": "client/app/agents/[agent_id]/profile/page.js",
    "chars": 570,
    "preview": "\"use client\";\n\nimport { AgentProfile } from \"ai-agent\";\nimport \"ai-agent/dist/tailwind.css\";\nimport { useParams } from \""
  },
  {
    "path": "client/app/agents/create/page.js",
    "chars": 443,
    "preview": "\"use client\";\n\nimport { CreateAgentPage } from \"ai-agent\";\nimport \"ai-agent/dist/tailwind.css\";\n\n// Mock user context to"
  },
  {
    "path": "client/app/agents/edit/[id]/page.js",
    "chars": 538,
    "preview": "\"use client\";\n\nimport { EditAgentPage } from \"ai-agent\";\nimport \"ai-agent/dist/tailwind.css\";\nimport { useParams } from "
  },
  {
    "path": "client/app/agents/loading.js",
    "chars": 371,
    "preview": "export default function Loading() {\n  return (\n    <div className=\"flex flex-col items-center justify-center min-h-[60vh"
  },
  {
    "path": "client/app/agents/page.js",
    "chars": 11366,
    "preview": "\"use client\";\n\nimport { useState, useEffect, useCallback } from \"react\";\nimport Link from \"next/link\";\nimport axios from"
  },
  {
    "path": "client/app/globals.css",
    "chars": 488,
    "preview": "@import \"tailwindcss\";\n\n:root {\n  --background: #ffffff;\n  --foreground: #171717;\n}\n\n@theme inline {\n  --color-backgroun"
  },
  {
    "path": "client/app/layout.js",
    "chars": 615,
    "preview": "import { Geist, Geist_Mono } from \"next/font/google\";\nimport \"./globals.css\";\n\nconst geistSans = Geist({\n  variable: \"--"
  },
  {
    "path": "client/app/page.js",
    "chars": 5949,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport Link from \"next/link\";\nimport { RiRobot2Fill, RiTeamLine, RiFlashlightL"
  },
  {
    "path": "client/components/AgentClientWrapper.js",
    "chars": 578,
    "preview": "\"use client\";\n\nimport { AiAgent } from \"ai-agent\";\nimport \"ai-agent/dist/tailwind.css\";\n\n// Mock user context to pass in"
  },
  {
    "path": "client/context/fetchAgentData.js",
    "chars": 686,
    "preview": "export async function fetchAgentData(id, cookieHeader, byName = false) {\n  const endpoint = `http://127.0.0.1:8000/api/a"
  },
  {
    "path": "client/jsconfig.json",
    "chars": 108,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\n        \"./*\"\n      ]\n    }\n  }\n}"
  },
  {
    "path": "client/next.config.mjs",
    "chars": 446,
    "preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  transpilePackages: ['ai-agent'],\n  images: {\n    remoteP"
  },
  {
    "path": "client/package.json",
    "chars": 568,
    "preview": "{\n  \"name\": \"vibe-agents-client\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"bu"
  },
  {
    "path": "client/postcss.config.mjs",
    "chars": 94,
    "preview": "const config = {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "package.json",
    "chars": 378,
    "preview": "{\n    \"name\": \"vibe-agents-monorepo\",\n    \"version\": \"1.0.0\",\n    \"private\": true,\n    \"workspaces\": [\n        \"packages"
  },
  {
    "path": "packages/agents/.gitignore",
    "chars": 35,
    "preview": "node_modules\npackage-lock.json\ndist"
  },
  {
    "path": "packages/agents/README.md",
    "chars": 824,
    "preview": "# AI Agent\n\nA premium React component for AI interactions.\n\n## Installation\n\nTo use this component in another project, r"
  },
  {
    "path": "packages/agents/THEME_SETUP.md",
    "chars": 1415,
    "preview": "# Theme Setup Guide\n\nThis library uses `next-themes` to manage light and dark modes. For the theme switching buttons to "
  },
  {
    "path": "packages/agents/babel.config.json",
    "chars": 104,
    "preview": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    [\"@babel/preset-react\", { \"runtime\": \"automatic\" }]\n  ]\n}\n"
  },
  {
    "path": "packages/agents/package.json",
    "chars": 929,
    "preview": "{\n  \"name\": \"ai-agent\",\n  \"version\": \"1.0.0\",\n  \"description\": \"AI Agent\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/"
  },
  {
    "path": "packages/agents/postcss.config.js",
    "chars": 82,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "packages/agents/src/AgentProfile.jsx",
    "chars": 567,
    "preview": "\"use client\";\n\nimport { Toaster } from \"react-hot-toast\";\nimport ProfileAgent from \"./components/ProfileAgent\";\n\nconst A"
  },
  {
    "path": "packages/agents/src/AiAgent.jsx",
    "chars": 68489,
    "preview": "\"use client\";\n\nimport React, { useState, useEffect, useRef } from \"react\";\nimport { useParams, useRouter, useSearchParam"
  },
  {
    "path": "packages/agents/src/CreatePage.jsx",
    "chars": 568,
    "preview": "\"use client\";\n\nimport CreateAgent from \"./components/CreateAgent\";\nimport { Toaster } from \"react-hot-toast\";\n\nconst Cre"
  },
  {
    "path": "packages/agents/src/EditPage.jsx",
    "chars": 557,
    "preview": "\"use client\";\n\nimport EditAgent from \"./components/EditAgent\";\nimport { Toaster } from \"react-hot-toast\";\n\nconst EditAge"
  },
  {
    "path": "packages/agents/src/components/AgentThemeProvider.jsx",
    "chars": 360,
    "preview": "'use client'\n\nimport React from 'react'\nimport { ThemeProvider } from 'next-themes'\n\nexport const AgentThemeProvider = ("
  },
  {
    "path": "packages/agents/src/components/CreateAgent.jsx",
    "chars": 5939,
    "preview": "import React, { useEffect, useState } from \"react\";\nimport Link from \"next/link\";\nimport axios from \"axios\";\nimport { Bi"
  },
  {
    "path": "packages/agents/src/components/EditAgent.jsx",
    "chars": 47715,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport { useParams, useRouter } from \"next/navigation\";\nimpo"
  },
  {
    "path": "packages/agents/src/components/ProfileAgent.jsx",
    "chars": 14950,
    "preview": "\"use client\";\n\nimport React, { useState, useEffect, useCallback } from \"react\";\nimport Image from \"next/image\";\nimport L"
  },
  {
    "path": "packages/agents/src/components/themes.jsx",
    "chars": 7031,
    "preview": "\nexport const themes = {\n  cosmic: {\n    id: 'cosmic',\n    name: 'Cosmic',\n    colors: {\n      background: 'radial-gradi"
  },
  {
    "path": "packages/agents/src/index.js",
    "chars": 398,
    "preview": "export { default as AiAgent } from \"./AiAgent\";\nexport { getAgentDetails } from \"./utils/server\";\nexport { themes } from"
  },
  {
    "path": "packages/agents/src/tailwind.css",
    "chars": 1909,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer components {\n  .premium-bg {\n    @apply bg-white dark"
  },
  {
    "path": "packages/agents/src/utils/server.js",
    "chars": 727,
    "preview": "export const getAgentDetails = async (agentId, options = {}) => {\n  const {\n    baseUrl = \"http://127.0.0.1:8000/agents\""
  },
  {
    "path": "packages/agents/tailwind.config.js",
    "chars": 400,
    "preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  darkMode: 'class',\n  content: [\"./src/**/*.{js,jsx}\"],\n"
  },
  {
    "path": "server/.gitignore",
    "chars": 128,
    "preview": "__pycache__/\n*.py[cod]\n*$py.class\n*.so\n.Python\nenv/\nvenv/\nENV/\n.venv\n*.egg-info/\ndist/\nbuild/\n.pytest_cache/\n.coverage\nh"
  },
  {
    "path": "server/app/main.py",
    "chars": 741,
    "preview": "import os\nfrom dotenv import load_dotenv\n\nenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')\nload_dotenv(e"
  },
  {
    "path": "server/app/routers/__init__.py",
    "chars": 26,
    "preview": "from . import agent_proxy\n"
  },
  {
    "path": "server/app/routers/agent_proxy.py",
    "chars": 3477,
    "preview": "from fastapi import APIRouter, Request, HTTPException\nfrom app.utils.agent_helper import proxy_request\nfrom typing impor"
  },
  {
    "path": "server/app/utils/agent_helper.py",
    "chars": 1698,
    "preview": "import os\nimport httpx\nimport logging\nfrom fastapi import HTTPException\nfrom typing import Optional\n\nlogging.basicConfig"
  },
  {
    "path": "server/requirements.txt",
    "chars": 60,
    "preview": "fastapi\nuvicorn\nsqlalchemy\nasyncpg\nhttpx\npython-dotenv\nruff\n"
  }
]

About this extraction

This page contains the full source code of the Anil-matcha/Chat-With-Excel GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 45 files (181.3 KB), approximately 44.0k tokens, and a symbol index with 42 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!