@tellet/create 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -0
- package/dist/ai/generate.d.ts +33 -0
- package/dist/ai/generate.js +108 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +337 -0
- package/dist/scaffold/project.d.ts +44 -0
- package/dist/scaffold/project.js +318 -0
- package/package.json +48 -0
- package/template/Dockerfile +35 -0
- package/template/app/(dashboard)/agents/page.tsx +14 -0
- package/template/app/(dashboard)/conversations/[id]/page.tsx +103 -0
- package/template/app/(dashboard)/conversations/page.tsx +50 -0
- package/template/app/(dashboard)/dashboard/page.tsx +102 -0
- package/template/app/(dashboard)/layout.tsx +15 -0
- package/template/app/(dashboard)/settings/page.tsx +46 -0
- package/template/app/(site)/layout.tsx +3 -0
- package/template/app/(site)/page.tsx +25 -0
- package/template/app/api/chat/route.ts +129 -0
- package/template/app/api/cron/route.ts +29 -0
- package/template/app/api/orchestrator/route.ts +139 -0
- package/template/app/globals.css +30 -0
- package/template/app/layout.tsx +18 -0
- package/template/components/chat/ChatWidget.tsx +109 -0
- package/template/components/chat/Markdown.tsx +136 -0
- package/template/components/dashboard/AgentChat.tsx +192 -0
- package/template/components/dashboard/AgentsListClient.tsx +86 -0
- package/template/components/dashboard/DashboardAgentGrid.tsx +73 -0
- package/template/components/dashboard/OrchestratorChat.tsx +251 -0
- package/template/components/dashboard/Sidebar.tsx +44 -0
- package/template/components/dashboard/StatsCards.tsx +40 -0
- package/template/components/dashboard/Welcome.tsx +139 -0
- package/template/components/sections/Agents.tsx +67 -0
- package/template/components/sections/CTA.tsx +46 -0
- package/template/components/sections/FAQ.tsx +81 -0
- package/template/components/sections/Features.tsx +51 -0
- package/template/components/sections/Footer.tsx +22 -0
- package/template/components/sections/Hero.tsx +86 -0
- package/template/components/sections/Icons.tsx +29 -0
- package/template/components/ui/Button.tsx +26 -0
- package/template/docker-compose.yml +32 -0
- package/template/infra/bin/app.ts +16 -0
- package/template/infra/cdk.json +6 -0
- package/template/infra/lib/tellet-stack.ts +216 -0
- package/template/infra/package.json +20 -0
- package/template/infra/tsconfig.json +16 -0
- package/template/lib/db.ts +37 -0
- package/template/lib/engine/default.ts +227 -0
- package/template/lib/engine/index.ts +17 -0
- package/template/lib/mcp/client.ts +97 -0
- package/template/lib/mcp/knowledge.ts +84 -0
- package/template/lib/mcp/registry.ts +106 -0
- package/template/lib/orchestrator/executor.ts +202 -0
- package/template/lib/orchestrator/tools.ts +245 -0
- package/template/lib/providers/anthropic.ts +41 -0
- package/template/lib/providers/index.ts +36 -0
- package/template/lib/providers/openai.ts +46 -0
- package/template/lib/scheduler.ts +115 -0
- package/template/lib/supabase.ts +30 -0
- package/template/lib/tellet.ts +45 -0
- package/template/lib/utils.ts +6 -0
- package/template/next.config.ts +7 -0
- package/template/public/widget.js +172 -0
- package/template/railway.toml +9 -0
- package/template/tsconfig.json +21 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
function parseLine(line: string): ReactNode {
|
|
4
|
+
// Bold
|
|
5
|
+
let parts = line.split(/\*\*(.+?)\*\*/g);
|
|
6
|
+
if (parts.length > 1) {
|
|
7
|
+
return parts.map((part, i) =>
|
|
8
|
+
i % 2 === 1 ? <strong key={i}>{part}</strong> : part
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Inline code
|
|
13
|
+
parts = line.split(/`(.+?)`/g);
|
|
14
|
+
if (parts.length > 1) {
|
|
15
|
+
return parts.map((part, i) =>
|
|
16
|
+
i % 2 === 1 ? (
|
|
17
|
+
<code
|
|
18
|
+
key={i}
|
|
19
|
+
className="bg-bg-tertiary text-accent px-1 py-0.5 rounded text-xs"
|
|
20
|
+
>
|
|
21
|
+
{part}
|
|
22
|
+
</code>
|
|
23
|
+
) : (
|
|
24
|
+
part
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return line;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Markdown({ content }: { content: string }) {
|
|
33
|
+
const lines = content.split("\n");
|
|
34
|
+
const elements: ReactNode[] = [];
|
|
35
|
+
let i = 0;
|
|
36
|
+
|
|
37
|
+
while (i < lines.length) {
|
|
38
|
+
const line = lines[i];
|
|
39
|
+
|
|
40
|
+
// Code block
|
|
41
|
+
if (line.startsWith("```")) {
|
|
42
|
+
const lang = line.slice(3).trim();
|
|
43
|
+
const codeLines: string[] = [];
|
|
44
|
+
i++;
|
|
45
|
+
while (i < lines.length && !lines[i].startsWith("```")) {
|
|
46
|
+
codeLines.push(lines[i]);
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
i++; // skip closing ```
|
|
50
|
+
elements.push(
|
|
51
|
+
<pre
|
|
52
|
+
key={elements.length}
|
|
53
|
+
className="bg-bg-tertiary border border-border rounded-lg p-3 my-2 overflow-x-auto"
|
|
54
|
+
>
|
|
55
|
+
<code className="text-xs text-text-primary">{codeLines.join("\n")}</code>
|
|
56
|
+
</pre>
|
|
57
|
+
);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Heading
|
|
62
|
+
if (line.startsWith("### ")) {
|
|
63
|
+
elements.push(
|
|
64
|
+
<p key={elements.length} className="font-semibold text-sm mt-2 mb-1">
|
|
65
|
+
{line.slice(4)}
|
|
66
|
+
</p>
|
|
67
|
+
);
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (line.startsWith("## ")) {
|
|
73
|
+
elements.push(
|
|
74
|
+
<p key={elements.length} className="font-semibold mt-2 mb-1">
|
|
75
|
+
{line.slice(3)}
|
|
76
|
+
</p>
|
|
77
|
+
);
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Bullet list
|
|
83
|
+
if (line.match(/^[-*] /)) {
|
|
84
|
+
const items: string[] = [];
|
|
85
|
+
while (i < lines.length && lines[i].match(/^[-*] /)) {
|
|
86
|
+
items.push(lines[i].replace(/^[-*] /, ""));
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
elements.push(
|
|
90
|
+
<ul key={elements.length} className="list-disc list-inside space-y-0.5 my-1">
|
|
91
|
+
{items.map((item, j) => (
|
|
92
|
+
<li key={j} className="text-sm">
|
|
93
|
+
{parseLine(item)}
|
|
94
|
+
</li>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
97
|
+
);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Numbered list
|
|
102
|
+
if (line.match(/^\d+\. /)) {
|
|
103
|
+
const items: string[] = [];
|
|
104
|
+
while (i < lines.length && lines[i].match(/^\d+\. /)) {
|
|
105
|
+
items.push(lines[i].replace(/^\d+\. /, ""));
|
|
106
|
+
i++;
|
|
107
|
+
}
|
|
108
|
+
elements.push(
|
|
109
|
+
<ol key={elements.length} className="list-decimal list-inside space-y-0.5 my-1">
|
|
110
|
+
{items.map((item, j) => (
|
|
111
|
+
<li key={j} className="text-sm">
|
|
112
|
+
{parseLine(item)}
|
|
113
|
+
</li>
|
|
114
|
+
))}
|
|
115
|
+
</ol>
|
|
116
|
+
);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Empty line
|
|
121
|
+
if (line.trim() === "") {
|
|
122
|
+
i++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Regular paragraph
|
|
127
|
+
elements.push(
|
|
128
|
+
<p key={elements.length} className="text-sm">
|
|
129
|
+
{parseLine(line)}
|
|
130
|
+
</p>
|
|
131
|
+
);
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return <div className="space-y-1">{elements}</div>;
|
|
136
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { Markdown } from "@/components/chat/Markdown";
|
|
7
|
+
|
|
8
|
+
interface ChatMessage {
|
|
9
|
+
role: "user" | "assistant";
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface AgentChatProps {
|
|
14
|
+
agent: { id: string; name: string; role: string };
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function AgentChat({ agent, onClose }: AgentChatProps) {
|
|
19
|
+
const [input, setInput] = useState("");
|
|
20
|
+
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
21
|
+
const [streaming, setStreaming] = useState(false);
|
|
22
|
+
const [conversationId, setConversationId] = useState<string | null>(null);
|
|
23
|
+
const endRef = useRef<HTMLDivElement>(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
endRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
27
|
+
}, [messages]);
|
|
28
|
+
|
|
29
|
+
const send = async () => {
|
|
30
|
+
const text = input.trim();
|
|
31
|
+
if (!text || streaming) return;
|
|
32
|
+
setInput("");
|
|
33
|
+
setMessages((p) => [...p, { role: "user", content: text }]);
|
|
34
|
+
setStreaming(true);
|
|
35
|
+
setMessages((p) => [...p, { role: "assistant", content: "" }]);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch("/api/chat", {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "Content-Type": "application/json" },
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
message: text,
|
|
43
|
+
agent_id: agent.id,
|
|
44
|
+
conversation_id: conversationId,
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
const reader = res.body?.getReader();
|
|
48
|
+
const decoder = new TextDecoder();
|
|
49
|
+
if (!reader) return;
|
|
50
|
+
|
|
51
|
+
while (true) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) break;
|
|
54
|
+
for (const line of decoder
|
|
55
|
+
.decode(value)
|
|
56
|
+
.split("\n")
|
|
57
|
+
.filter((l) => l.startsWith("data: "))) {
|
|
58
|
+
try {
|
|
59
|
+
const data = JSON.parse(line.slice(6));
|
|
60
|
+
if (data.text) {
|
|
61
|
+
setMessages((p) => {
|
|
62
|
+
const u = [...p];
|
|
63
|
+
const last = u[u.length - 1];
|
|
64
|
+
if (last.role === "assistant")
|
|
65
|
+
u[u.length - 1] = { ...last, content: last.content + data.text };
|
|
66
|
+
return u;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (data.conversation_id) setConversationId(data.conversation_id);
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
setMessages((p) => {
|
|
75
|
+
const u = [...p];
|
|
76
|
+
u[u.length - 1] = { role: "assistant", content: "Something went wrong." };
|
|
77
|
+
return u;
|
|
78
|
+
});
|
|
79
|
+
} finally {
|
|
80
|
+
setStreaming(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const roleLabel = agent.role.replace("_", " ");
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<AnimatePresence>
|
|
88
|
+
<motion.div
|
|
89
|
+
className="fixed inset-0 bg-black/30 z-40"
|
|
90
|
+
initial={{ opacity: 0 }}
|
|
91
|
+
animate={{ opacity: 1 }}
|
|
92
|
+
exit={{ opacity: 0 }}
|
|
93
|
+
onClick={onClose}
|
|
94
|
+
/>
|
|
95
|
+
<motion.div
|
|
96
|
+
className="fixed top-0 right-0 h-full w-full max-w-[440px] z-50 border-l border-border bg-bg-primary shadow-2xl flex flex-col"
|
|
97
|
+
initial={{ x: "100%" }}
|
|
98
|
+
animate={{ x: 0 }}
|
|
99
|
+
exit={{ x: "100%" }}
|
|
100
|
+
transition={{ type: "spring", damping: 25, stiffness: 200 }}
|
|
101
|
+
>
|
|
102
|
+
{/* Header */}
|
|
103
|
+
<div className="flex items-center justify-between px-5 py-4 border-b border-border">
|
|
104
|
+
<div className="flex items-center gap-3">
|
|
105
|
+
<div className="w-8 h-8 rounded-full bg-accent/10 text-accent text-xs font-bold flex items-center justify-center">
|
|
106
|
+
{agent.name[0]}
|
|
107
|
+
</div>
|
|
108
|
+
<div>
|
|
109
|
+
<p className="text-sm font-semibold">{agent.name}</p>
|
|
110
|
+
<p className="text-[11px] text-text-tertiary capitalize">{roleLabel}</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<button
|
|
114
|
+
onClick={onClose}
|
|
115
|
+
className="text-text-tertiary hover:text-text-secondary transition-colors cursor-pointer"
|
|
116
|
+
>
|
|
117
|
+
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
118
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
119
|
+
</svg>
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Messages */}
|
|
124
|
+
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-3">
|
|
125
|
+
{messages.length === 0 && (
|
|
126
|
+
<div className="text-center py-12">
|
|
127
|
+
<div className="w-12 h-12 rounded-full bg-accent/10 text-accent mx-auto mb-3 flex items-center justify-center text-lg font-bold">
|
|
128
|
+
{agent.name[0]}
|
|
129
|
+
</div>
|
|
130
|
+
<p className="text-sm text-text-secondary">
|
|
131
|
+
Chat with {agent.name}
|
|
132
|
+
</p>
|
|
133
|
+
<p className="text-xs text-text-tertiary mt-1 capitalize">
|
|
134
|
+
{roleLabel}
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
{messages.map((m, i) => (
|
|
139
|
+
<div
|
|
140
|
+
key={i}
|
|
141
|
+
className={cn("flex", m.role === "user" ? "justify-end" : "justify-start")}
|
|
142
|
+
>
|
|
143
|
+
<div
|
|
144
|
+
className={cn(
|
|
145
|
+
"rounded-xl px-3 py-2 max-w-[85%] text-sm leading-relaxed",
|
|
146
|
+
m.role === "user"
|
|
147
|
+
? "bg-accent text-white"
|
|
148
|
+
: "bg-bg-secondary text-text-primary border border-border"
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
{m.content ? <Markdown content={m.content} /> : (
|
|
152
|
+
<span className="inline-flex gap-1">
|
|
153
|
+
<span className="w-1.5 h-1.5 rounded-full bg-text-tertiary animate-pulse" />
|
|
154
|
+
<span className="w-1.5 h-1.5 rounded-full bg-text-tertiary animate-pulse [animation-delay:150ms]" />
|
|
155
|
+
<span className="w-1.5 h-1.5 rounded-full bg-text-tertiary animate-pulse [animation-delay:300ms]" />
|
|
156
|
+
</span>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
))}
|
|
161
|
+
<div ref={endRef} />
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
{/* Input */}
|
|
165
|
+
<form
|
|
166
|
+
onSubmit={(e) => {
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
send();
|
|
169
|
+
}}
|
|
170
|
+
className="px-4 py-3 border-t border-border flex gap-2"
|
|
171
|
+
>
|
|
172
|
+
<input
|
|
173
|
+
value={input}
|
|
174
|
+
onChange={(e) => setInput(e.target.value)}
|
|
175
|
+
placeholder="Type a message..."
|
|
176
|
+
disabled={streaming}
|
|
177
|
+
className="flex-1 rounded-lg bg-bg-secondary border border-border px-3 py-2 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:border-accent disabled:opacity-50"
|
|
178
|
+
/>
|
|
179
|
+
<button
|
|
180
|
+
type="submit"
|
|
181
|
+
disabled={streaming || !input.trim()}
|
|
182
|
+
className="rounded-lg bg-accent px-3 py-2 text-white text-sm hover:bg-accent-hover disabled:opacity-50 cursor-pointer transition-colors"
|
|
183
|
+
>
|
|
184
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
185
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
|
|
186
|
+
</svg>
|
|
187
|
+
</button>
|
|
188
|
+
</form>
|
|
189
|
+
</motion.div>
|
|
190
|
+
</AnimatePresence>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { AgentChat } from "./AgentChat";
|
|
5
|
+
|
|
6
|
+
interface Agent {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
role: string;
|
|
10
|
+
model: string;
|
|
11
|
+
status: string;
|
|
12
|
+
system_prompt: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AgentsListClient({ agents }: { agents: Agent[] }) {
|
|
16
|
+
const [chatAgent, setChatAgent] = useState<Agent | null>(null);
|
|
17
|
+
|
|
18
|
+
if (!agents || agents.length === 0) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="rounded-lg border border-dashed border-border bg-bg-secondary/20 p-8 text-center">
|
|
21
|
+
<p className="text-text-secondary text-sm">No agents configured.</p>
|
|
22
|
+
<p className="text-text-tertiary text-xs mt-1">
|
|
23
|
+
Check your tellet.json and run the database migration.
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<div className="grid gap-4">
|
|
32
|
+
{agents.map((agent) => (
|
|
33
|
+
<div
|
|
34
|
+
key={agent.id}
|
|
35
|
+
className="rounded-xl border border-border bg-bg-secondary/50 p-5"
|
|
36
|
+
>
|
|
37
|
+
<div className="flex items-center gap-4">
|
|
38
|
+
<div className="w-10 h-10 rounded-full bg-accent/10 text-accent text-sm font-bold flex items-center justify-center">
|
|
39
|
+
{agent.name[0]}
|
|
40
|
+
</div>
|
|
41
|
+
<div className="flex-1">
|
|
42
|
+
<p className="font-semibold">{agent.name}</p>
|
|
43
|
+
<p className="text-sm text-text-secondary capitalize">
|
|
44
|
+
{agent.role.replace("_", " ")} · {agent.model}
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="flex items-center gap-3">
|
|
48
|
+
<div className="flex items-center gap-2">
|
|
49
|
+
<span
|
|
50
|
+
className={`w-2 h-2 rounded-full ${
|
|
51
|
+
agent.status === "active" ? "bg-green-400" : "bg-text-tertiary"
|
|
52
|
+
}`}
|
|
53
|
+
/>
|
|
54
|
+
<span className="text-xs text-text-secondary capitalize">
|
|
55
|
+
{agent.status}
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
<button
|
|
59
|
+
onClick={() => setChatAgent(agent)}
|
|
60
|
+
className="text-xs text-text-secondary border border-border rounded-lg px-3 py-1.5 hover:border-accent hover:text-accent transition-colors cursor-pointer"
|
|
61
|
+
>
|
|
62
|
+
Chat now
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div className="mt-4 rounded-lg bg-bg-primary border border-border p-3">
|
|
67
|
+
<p className="text-xs text-text-tertiary uppercase tracking-wider mb-1">
|
|
68
|
+
System Prompt
|
|
69
|
+
</p>
|
|
70
|
+
<p className="text-sm text-text-secondary leading-relaxed line-clamp-3">
|
|
71
|
+
{agent.system_prompt}
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
))}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{chatAgent && (
|
|
79
|
+
<AgentChat
|
|
80
|
+
agent={chatAgent}
|
|
81
|
+
onClose={() => setChatAgent(null)}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { AgentChat } from "./AgentChat";
|
|
5
|
+
|
|
6
|
+
interface Agent {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
role: string;
|
|
10
|
+
status: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const roleColors: Record<string, string> = {
|
|
14
|
+
customer_support: "bg-green-500/10 text-green-400 border-green-500/20",
|
|
15
|
+
marketing: "bg-blue-500/10 text-blue-400 border-blue-500/20",
|
|
16
|
+
sales: "bg-amber-500/10 text-amber-400 border-amber-500/20",
|
|
17
|
+
operations: "bg-purple-500/10 text-purple-400 border-purple-500/20",
|
|
18
|
+
development: "bg-cyan-500/10 text-cyan-400 border-cyan-500/20",
|
|
19
|
+
analytics: "bg-rose-500/10 text-rose-400 border-rose-500/20",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function DashboardAgentGrid({ agents }: { agents: Agent[] }) {
|
|
23
|
+
const [chatAgent, setChatAgent] = useState<Agent | null>(null);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
28
|
+
{agents.map((agent) => (
|
|
29
|
+
<div
|
|
30
|
+
key={agent.id}
|
|
31
|
+
className="rounded-xl border border-border bg-bg-secondary/50 p-5 transition-colors hover:border-border-hover"
|
|
32
|
+
>
|
|
33
|
+
<div className="flex items-center justify-between mb-3">
|
|
34
|
+
<span className="text-lg font-semibold">{agent.name}</span>
|
|
35
|
+
<span
|
|
36
|
+
className={`inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium ${
|
|
37
|
+
roleColors[agent.role] || "bg-bg-tertiary text-text-secondary border-border"
|
|
38
|
+
}`}
|
|
39
|
+
>
|
|
40
|
+
{agent.role.replace("_", " ")}
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
<div className="flex items-center justify-between">
|
|
44
|
+
<div className="flex items-center gap-2">
|
|
45
|
+
<span
|
|
46
|
+
className={`w-2 h-2 rounded-full ${
|
|
47
|
+
agent.status === "active" ? "bg-green-400" : "bg-text-tertiary"
|
|
48
|
+
}`}
|
|
49
|
+
/>
|
|
50
|
+
<span className="text-xs text-text-secondary capitalize">
|
|
51
|
+
{agent.status}
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
<button
|
|
55
|
+
onClick={() => setChatAgent(agent)}
|
|
56
|
+
className="text-xs text-text-secondary border border-border rounded-lg px-3 py-1.5 hover:border-accent hover:text-accent transition-colors cursor-pointer"
|
|
57
|
+
>
|
|
58
|
+
Chat
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{chatAgent && (
|
|
66
|
+
<AgentChat
|
|
67
|
+
agent={chatAgent}
|
|
68
|
+
onClose={() => setChatAgent(null)}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|