Skip to content

Commit 5cbcd88

Browse files
authored
Merge pull request #93 from jaiprasad04/feat/modernize-studio-upload
feat: integrate AI agent studio with minimal UI and upgrade Tailwind v4
2 parents 9de0de3 + 4efb859 commit 5cbcd88

25 files changed

Lines changed: 1813 additions & 153 deletions

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "packages/workflow-ui"]
22
path = packages/workflow-ui
33
url = https://github.com/Anil-matcha/workflow-ui.git
4+
[submodule "packages/ai-agent"]
5+
path = packages/ai-agent
6+
url = https://github.com/jaiprasad04/ai-agent
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"use client";
2+
3+
import { AiAgent } from "ai-agent";
4+
import "ai-agent/dist/tailwind.css";
5+
import { useCallback, useEffect, useRef } from "react";
6+
import axios from "axios";
7+
8+
const STORAGE_KEY = "muapi_key";
9+
10+
/**
11+
* AgentChatClient — mirrors muapiapp's AgentClient.js.
12+
* Renders the AiAgent library component with server-fetched agent details
13+
* and optional initial history.
14+
*
15+
* IMPORTANT: StandaloneShell is NOT in the tree on /agents/* pages, so we
16+
* must set up our own axios interceptor here to inject the API key into
17+
* all requests made by the AiAgent library.
18+
*/
19+
export default function AgentChatClient({ agentDetails, initialHistory, userData }) {
20+
const interceptorRef = useRef(null);
21+
22+
console.log("[AgentChatClient] Rendering", {
23+
hasAgentDetails: !!agentDetails,
24+
hasHistory: !!initialHistory,
25+
hasUserData: !!userData
26+
});
27+
28+
useEffect(() => {
29+
const getKey = () => {
30+
if (typeof window === "undefined") return null;
31+
const fromStorage = localStorage.getItem(STORAGE_KEY);
32+
if (fromStorage) return fromStorage;
33+
const match = document.cookie.match(/muapi_key=([^;]+)/);
34+
return match ? match[1] : null;
35+
};
36+
37+
const apiKey = getKey();
38+
if (!apiKey) return;
39+
40+
interceptorRef.current = axios.interceptors.request.use((config) => {
41+
const isRelative =
42+
config.url.startsWith("/") || !config.url.startsWith("http");
43+
// Include specific proxy paths to be sure
44+
const isInternalProxy = config.url.includes('/api/app') || config.url.includes('/api/workflow') || config.url.includes('/api/agents') || config.url.includes('/api/api') || config.url.includes('/api/v1');
45+
46+
if (isRelative || isInternalProxy) {
47+
config.headers["x-api-key"] = apiKey;
48+
}
49+
return config;
50+
});
51+
52+
return () => {
53+
if (interceptorRef.current !== null) {
54+
axios.interceptors.request.eject(interceptorRef.current);
55+
}
56+
};
57+
}, []);
58+
59+
const useUser = useCallback(
60+
() => ({
61+
user: {
62+
username: userData?.email?.split("@")[0] || "Studio User",
63+
name: userData?.email?.split("@")[0] || "Studio User",
64+
email: userData?.email || null,
65+
profile_photo: null,
66+
balance: userData?.balance || 0,
67+
},
68+
isAuthorized: !!userData,
69+
}),
70+
[userData]
71+
);
72+
73+
return (
74+
<div className="h-screen w-full bg-black">
75+
<AiAgent
76+
initialAgentDetails={agentDetails}
77+
initialHistory={initialHistory}
78+
useUser={useUser}
79+
usedIn="muapiapp"
80+
/>
81+
</div>
82+
);
83+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { cookies } from "next/headers";
2+
import AgentChatClient from "../AgentChatClient";
3+
4+
/**
5+
* Server component — fetches both agentDetails and initialHistory
6+
* from the /api/agents proxy using the muapi_key cookie, then renders
7+
* the client chat component with existing conversation messages pre-loaded.
8+
*
9+
* URL: /agents/[agent_id]/[conversation_id]
10+
*/
11+
export async function generateMetadata({ params }) {
12+
return {
13+
title: `Agent Chat — Open Generative AI`,
14+
};
15+
}
16+
17+
const BASE_URL = 'https://api.muapi.ai';
18+
19+
async function fetchAgentDetails(agentId, apiKey) {
20+
if (!apiKey) return null;
21+
try {
22+
const res = await fetch(
23+
`${BASE_URL}/agents/by-slug/${agentId}`,
24+
{
25+
cache: "no-store",
26+
headers: { "x-api-key": apiKey },
27+
}
28+
);
29+
if (res.ok) return await res.json();
30+
31+
if (agentId.length > 20) {
32+
const resId = await fetch(
33+
`${BASE_URL}/agents/${agentId}`,
34+
{
35+
cache: "no-store",
36+
headers: { "x-api-key": apiKey },
37+
}
38+
);
39+
if (resId.ok) return await resId.json();
40+
}
41+
return null;
42+
} catch {
43+
return null;
44+
}
45+
}
46+
47+
async function fetchHistory(agentId, conversationId, apiKey) {
48+
if (!apiKey) return null;
49+
try {
50+
// Try by slug first
51+
const res = await fetch(
52+
`${BASE_URL}/agents/by-slug/${agentId}/${conversationId}`,
53+
{
54+
cache: "no-store",
55+
headers: { "x-api-key": apiKey },
56+
}
57+
);
58+
if (res.ok) return await res.json();
59+
60+
// Fallback to direct agent ID if needed
61+
if (agentId.length > 20) {
62+
const resId = await fetch(
63+
`${BASE_URL}/agents/${agentId}/${conversationId}`,
64+
{
65+
cache: "no-store",
66+
headers: { "x-api-key": apiKey },
67+
}
68+
);
69+
if (resId.ok) return await resId.json();
70+
}
71+
return null;
72+
} catch {
73+
return null;
74+
}
75+
}
76+
77+
async function fetchUserData(apiKey) {
78+
if (!apiKey) return null;
79+
try {
80+
const res = await fetch(`${BASE_URL}/api/v1/account/balance`, {
81+
cache: "no-store",
82+
headers: { "x-api-key": apiKey },
83+
});
84+
if (!res.ok) return null;
85+
return await res.json();
86+
} catch {
87+
return null;
88+
}
89+
}
90+
91+
export default async function AgentConversationPage({ params }) {
92+
const { agent_id, conversation_id } = await params;
93+
const cookieStore = await cookies();
94+
const apiKey = cookieStore.get("muapi_key")?.value;
95+
96+
console.log(`[ConvPage] Loading for agent: ${agent_id}, conv: ${conversation_id}, hasKey: ${!!apiKey}`);
97+
98+
const [agentDetails, initialHistory, userData] = await Promise.all([
99+
fetchAgentDetails(agent_id, apiKey),
100+
fetchHistory(agent_id, conversation_id, apiKey),
101+
fetchUserData(apiKey)
102+
]);
103+
104+
return (
105+
<AgentChatClient
106+
agentDetails={agentDetails}
107+
initialHistory={initialHistory}
108+
userData={userData}
109+
/>
110+
);
111+
}

app/agents/[agent_id]/page.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { cookies } from "next/headers";
2+
import AgentChatClient from "./AgentChatClient";
3+
4+
/**
5+
* Server component — fetches agentDetails from the /api/agents proxy
6+
* (which forwards to https://api.muapi.ai/agents/by-slug/{id})
7+
* using the muapi_key cookie for auth, then renders the client chat component.
8+
*
9+
* URL: /agents/[agent_id] (new chat — no conversation ID yet)
10+
*/
11+
export async function generateMetadata({ params }) {
12+
const { agent_id } = await params;
13+
return {
14+
title: `Agent Chat — Open Generative AI`,
15+
};
16+
}
17+
18+
const BASE_URL = 'https://api.muapi.ai';
19+
20+
async function fetchAgentDetails(agentId, apiKey) {
21+
if (!apiKey) return null;
22+
23+
// Try fetching by slug first
24+
try {
25+
console.log(`[AgentPage] Fetching agent by slug: ${agentId}`);
26+
const res = await fetch(
27+
`${BASE_URL}/agents/by-slug/${agentId}`,
28+
{
29+
cache: "no-store",
30+
headers: { "x-api-key": apiKey },
31+
}
32+
);
33+
if (res.ok) return await res.json();
34+
35+
// If by-slug fails, try fetching by direct ID (if it looks like a UUID)
36+
if (agentId.length > 20) {
37+
console.log(`[AgentPage] Fetch by slug failed, trying by ID: ${agentId}`);
38+
const resId = await fetch(
39+
`${BASE_URL}/agents/${agentId}`,
40+
{
41+
cache: "no-store",
42+
headers: { "x-api-key": apiKey },
43+
}
44+
);
45+
if (resId.ok) return await resId.json();
46+
}
47+
48+
console.warn(`[AgentPage] Failed to fetch agent details for: ${agentId}`);
49+
return null;
50+
} catch (error) {
51+
console.error("[AgentPage] Fetch error:", error);
52+
return null;
53+
}
54+
}
55+
56+
async function fetchUserData(apiKey) {
57+
if (!apiKey) return null;
58+
try {
59+
const res = await fetch(`${BASE_URL}/api/v1/account/balance`, {
60+
cache: "no-store",
61+
headers: { "x-api-key": apiKey },
62+
});
63+
if (!res.ok) return null;
64+
return await res.json();
65+
} catch {
66+
return null;
67+
}
68+
}
69+
70+
export default async function AgentPage({ params }) {
71+
const { agent_id } = await params;
72+
const cookieStore = await cookies();
73+
const apiKey = cookieStore.get("muapi_key")?.value;
74+
75+
console.log(`[AgentPage] Loading page for agent: ${agent_id}, hasKey: ${!!apiKey}`);
76+
77+
const [agentDetails, userData] = await Promise.all([
78+
fetchAgentDetails(agent_id, apiKey),
79+
fetchUserData(apiKey)
80+
]);
81+
82+
return (
83+
<AgentChatClient
84+
agentDetails={agentDetails}
85+
initialHistory={null}
86+
userData={userData}
87+
/>
88+
);
89+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"use client";
2+
3+
import { CreateAgentPage } from "ai-agent";
4+
import "ai-agent/dist/tailwind.css";
5+
import { useCallback, useEffect, useRef } from "react";
6+
import axios from "axios";
7+
8+
const STORAGE_KEY = "muapi_key";
9+
10+
export default function AgentCreateClient({ userData }) {
11+
const interceptorRef = useRef(null);
12+
13+
useEffect(() => {
14+
const getKey = () => {
15+
if (typeof window === "undefined") return null;
16+
const fromStorage = localStorage.getItem(STORAGE_KEY);
17+
if (fromStorage) return fromStorage;
18+
const match = document.cookie.match(/muapi_key=([^;]+)/);
19+
return match ? match[1] : null;
20+
};
21+
22+
const apiKey = getKey();
23+
if (!apiKey) return;
24+
25+
interceptorRef.current = axios.interceptors.request.use((config) => {
26+
const isRelative = config.url.startsWith("/") || !config.url.startsWith("http");
27+
const isInternalProxy = config.url.includes('/api/app') || config.url.includes('/api/workflow') || config.url.includes('/api/agents') || config.url.includes('/api/api') || config.url.includes('/api/v1');
28+
29+
if (isRelative || isInternalProxy) {
30+
config.headers["x-api-key"] = apiKey;
31+
}
32+
return config;
33+
});
34+
35+
return () => {
36+
if (interceptorRef.current !== null) {
37+
axios.interceptors.request.eject(interceptorRef.current);
38+
}
39+
};
40+
}, []);
41+
42+
const useUser = useCallback(
43+
() => ({
44+
user: {
45+
username: userData?.email?.split("@")[0] || "Studio User",
46+
name: userData?.email?.split("@")[0] || "Studio User",
47+
email: userData?.email || null,
48+
profile_photo: null,
49+
balance: userData?.balance || 0,
50+
},
51+
isAuthorized: !!userData,
52+
}),
53+
[userData]
54+
);
55+
56+
return (
57+
<CreateAgentPage
58+
useUser={useUser}
59+
usedIn="studio"
60+
/>
61+
);
62+
}

app/agents/create/page.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { cookies } from "next/headers";
2+
import AgentCreateClient from "./AgentCreateClient";
3+
4+
const BASE_URL = 'https://api.muapi.ai';
5+
6+
async function fetchUserData(apiKey) {
7+
if (!apiKey) return null;
8+
try {
9+
const res = await fetch(`${BASE_URL}/api/v1/account/balance`, {
10+
cache: "no-store",
11+
headers: { "x-api-key": apiKey },
12+
});
13+
if (!res.ok) return null;
14+
return await res.json();
15+
} catch {
16+
return null;
17+
}
18+
}
19+
20+
export default async function CreateAgentPage() {
21+
const cookieStore = await cookies();
22+
const apiKey = cookieStore.get("muapi_key")?.value;
23+
24+
const userData = await fetchUserData(apiKey);
25+
26+
return (
27+
<AgentCreateClient userData={userData} />
28+
);
29+
}

0 commit comments

Comments
 (0)