@greatapps/greatagents-ui 0.2.0 → 0.2.1
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/dist/index.d.ts +31 -5
- package/dist/index.js +483 -259
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/agents/agent-tabs.tsx +14 -13
- package/src/components/conversations/agent-conversations-panel.tsx +44 -0
- package/src/components/conversations/agent-conversations-table.tsx +160 -0
- package/src/components/conversations/conversation-view.tsx +124 -0
- package/src/index.ts +3 -0
- package/src/pages/agent-detail-page.tsx +3 -4
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import type { GagentsHookConfig } from "../../hooks/types";
|
|
|
3
3
|
import { AgentToolsList } from "./agent-tools-list";
|
|
4
4
|
import { AgentObjectivesList } from "./agent-objectives-list";
|
|
5
5
|
import { AgentPromptEditor } from "./agent-prompt-editor";
|
|
6
|
+
import { AgentConversationsPanel } from "../conversations/agent-conversations-panel";
|
|
6
7
|
import {
|
|
7
8
|
Tabs,
|
|
8
9
|
TabsList,
|
|
@@ -14,10 +15,10 @@ import { Wrench, Target, FileText, MessageCircle } from "lucide-react";
|
|
|
14
15
|
interface AgentTabsProps {
|
|
15
16
|
agent: Agent;
|
|
16
17
|
config: GagentsHookConfig;
|
|
17
|
-
|
|
18
|
+
renderChatLink?: (inboxId: number) => React.ReactNode;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export function AgentTabs({ agent, config,
|
|
21
|
+
export function AgentTabs({ agent, config, renderChatLink }: AgentTabsProps) {
|
|
21
22
|
return (
|
|
22
23
|
<Tabs defaultValue="prompt">
|
|
23
24
|
<TabsList>
|
|
@@ -33,12 +34,10 @@ export function AgentTabs({ agent, config, renderConversationsTab }: AgentTabsPr
|
|
|
33
34
|
<Wrench className="h-3.5 w-3.5" />
|
|
34
35
|
Ferramentas
|
|
35
36
|
</TabsTrigger>
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</TabsTrigger>
|
|
41
|
-
)}
|
|
37
|
+
<TabsTrigger value="conversas" className="flex items-center gap-1.5">
|
|
38
|
+
<MessageCircle className="h-3.5 w-3.5" />
|
|
39
|
+
Conversas
|
|
40
|
+
</TabsTrigger>
|
|
42
41
|
</TabsList>
|
|
43
42
|
|
|
44
43
|
<TabsContent value="prompt" className="mt-4">
|
|
@@ -53,11 +52,13 @@ export function AgentTabs({ agent, config, renderConversationsTab }: AgentTabsPr
|
|
|
53
52
|
<AgentToolsList agent={agent} config={config} />
|
|
54
53
|
</TabsContent>
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
<TabsContent value="conversas" className="mt-4">
|
|
56
|
+
<AgentConversationsPanel
|
|
57
|
+
agent={agent}
|
|
58
|
+
config={config}
|
|
59
|
+
renderChatLink={renderChatLink}
|
|
60
|
+
/>
|
|
61
|
+
</TabsContent>
|
|
61
62
|
</Tabs>
|
|
62
63
|
);
|
|
63
64
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Agent } from "../../types";
|
|
2
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
3
|
+
import { useAgentConversations, useGagentsContacts, useObjectives } from "../../hooks";
|
|
4
|
+
import { AgentConversationsTable } from "./agent-conversations-table";
|
|
5
|
+
|
|
6
|
+
interface AgentConversationsPanelProps {
|
|
7
|
+
agent: Agent;
|
|
8
|
+
config: GagentsHookConfig;
|
|
9
|
+
renderChatLink?: (inboxId: number) => React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function AgentConversationsPanel({
|
|
13
|
+
agent,
|
|
14
|
+
config,
|
|
15
|
+
renderChatLink,
|
|
16
|
+
}: AgentConversationsPanelProps) {
|
|
17
|
+
const { data: conversationsData, isLoading } = useAgentConversations(
|
|
18
|
+
config,
|
|
19
|
+
agent.id,
|
|
20
|
+
);
|
|
21
|
+
const { data: contactsData } = useGagentsContacts(config);
|
|
22
|
+
const { data: objectivesData } = useObjectives(config, agent.id);
|
|
23
|
+
|
|
24
|
+
const conversations = conversationsData?.data || [];
|
|
25
|
+
const contactsMap = new Map(
|
|
26
|
+
(contactsData?.data || []).map((c) => [c.id, c.name]),
|
|
27
|
+
);
|
|
28
|
+
const objectivesMap = new Map(
|
|
29
|
+
(objectivesData?.data || []).map((o) => [o.id, o.title]),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="p-4">
|
|
34
|
+
<AgentConversationsTable
|
|
35
|
+
conversations={conversations}
|
|
36
|
+
isLoading={isLoading}
|
|
37
|
+
contactsMap={contactsMap}
|
|
38
|
+
objectivesMap={objectivesMap}
|
|
39
|
+
config={config}
|
|
40
|
+
renderChatLink={renderChatLink}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Conversation } from "../../types";
|
|
3
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
4
|
+
import {
|
|
5
|
+
Table,
|
|
6
|
+
TableBody,
|
|
7
|
+
TableCell,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableHeader,
|
|
10
|
+
TableRow,
|
|
11
|
+
Skeleton,
|
|
12
|
+
Badge,
|
|
13
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
14
|
+
import { MessageCircle, ExternalLink } from "lucide-react";
|
|
15
|
+
import { ConversationView } from "./conversation-view";
|
|
16
|
+
|
|
17
|
+
interface AgentConversationsTableProps {
|
|
18
|
+
conversations: Conversation[];
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
contactsMap?: Map<number, string>;
|
|
21
|
+
objectivesMap?: Map<number, string>;
|
|
22
|
+
config: GagentsHookConfig;
|
|
23
|
+
renderChatLink?: (inboxId: number) => React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatRelativeDate(dateStr: string | null): string {
|
|
27
|
+
if (!dateStr) return "—";
|
|
28
|
+
const date = new Date(dateStr);
|
|
29
|
+
const now = new Date();
|
|
30
|
+
const diffMs = now.getTime() - date.getTime();
|
|
31
|
+
const diffMin = Math.floor(diffMs / 60000);
|
|
32
|
+
if (diffMin < 1) return "agora";
|
|
33
|
+
if (diffMin < 60) return `${diffMin}m atrás`;
|
|
34
|
+
const diffHours = Math.floor(diffMin / 60);
|
|
35
|
+
if (diffHours < 24) return `${diffHours}h atrás`;
|
|
36
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
37
|
+
if (diffDays < 30) return `${diffDays}d atrás`;
|
|
38
|
+
return date.toLocaleDateString("pt-BR");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function AgentConversationsTable({
|
|
42
|
+
conversations,
|
|
43
|
+
isLoading,
|
|
44
|
+
contactsMap,
|
|
45
|
+
objectivesMap,
|
|
46
|
+
config,
|
|
47
|
+
renderChatLink,
|
|
48
|
+
}: AgentConversationsTableProps) {
|
|
49
|
+
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|
50
|
+
|
|
51
|
+
if (isLoading) {
|
|
52
|
+
return (
|
|
53
|
+
<div className="space-y-3">
|
|
54
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
55
|
+
<Skeleton key={i} className="h-12 w-full" />
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (conversations.length === 0) {
|
|
62
|
+
return (
|
|
63
|
+
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
|
|
64
|
+
<MessageCircle className="mb-2 h-8 w-8 text-muted-foreground" />
|
|
65
|
+
<p className="text-sm text-muted-foreground">
|
|
66
|
+
Nenhuma conversa encontrada.
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className="space-y-4">
|
|
74
|
+
<Table>
|
|
75
|
+
<TableHeader>
|
|
76
|
+
<TableRow>
|
|
77
|
+
<TableHead>ID</TableHead>
|
|
78
|
+
<TableHead>Contato</TableHead>
|
|
79
|
+
<TableHead>Objetivo</TableHead>
|
|
80
|
+
<TableHead className="text-right">Mensagens</TableHead>
|
|
81
|
+
<TableHead className="text-right">Tokens</TableHead>
|
|
82
|
+
<TableHead>Criado em</TableHead>
|
|
83
|
+
<TableHead>Atualizado em</TableHead>
|
|
84
|
+
<TableHead className="w-10"></TableHead>
|
|
85
|
+
</TableRow>
|
|
86
|
+
</TableHeader>
|
|
87
|
+
<TableBody>
|
|
88
|
+
{conversations.map((conversation) => (
|
|
89
|
+
<TableRow
|
|
90
|
+
key={conversation.id}
|
|
91
|
+
className="cursor-pointer"
|
|
92
|
+
onClick={() =>
|
|
93
|
+
setSelectedId(
|
|
94
|
+
selectedId === conversation.id ? null : conversation.id,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
data-state={selectedId === conversation.id ? "selected" : undefined}
|
|
98
|
+
>
|
|
99
|
+
<TableCell className="font-mono text-xs">
|
|
100
|
+
{conversation.id}
|
|
101
|
+
</TableCell>
|
|
102
|
+
<TableCell>
|
|
103
|
+
{contactsMap?.get(conversation.id_contact) ?? conversation.id_contact}
|
|
104
|
+
</TableCell>
|
|
105
|
+
<TableCell>
|
|
106
|
+
{conversation.id_objective && objectivesMap?.get(conversation.id_objective) ? (
|
|
107
|
+
<Badge variant="secondary" className="text-xs">
|
|
108
|
+
{objectivesMap.get(conversation.id_objective)}
|
|
109
|
+
</Badge>
|
|
110
|
+
) : (
|
|
111
|
+
<span className="text-xs text-muted-foreground">—</span>
|
|
112
|
+
)}
|
|
113
|
+
</TableCell>
|
|
114
|
+
<TableCell className="text-right">
|
|
115
|
+
{conversation.message_count ?? "—"}
|
|
116
|
+
</TableCell>
|
|
117
|
+
<TableCell className="text-right">
|
|
118
|
+
{conversation.usage_tokens ?? "—"}
|
|
119
|
+
</TableCell>
|
|
120
|
+
<TableCell>
|
|
121
|
+
{new Date(conversation.datetime_add).toLocaleDateString("pt-BR")}
|
|
122
|
+
</TableCell>
|
|
123
|
+
<TableCell>
|
|
124
|
+
{formatRelativeDate(conversation.datetime_alt)}
|
|
125
|
+
</TableCell>
|
|
126
|
+
<TableCell>
|
|
127
|
+
{conversation.id_external ? (
|
|
128
|
+
renderChatLink ? (
|
|
129
|
+
<span onClick={(e) => e.stopPropagation()}>
|
|
130
|
+
{renderChatLink(conversation.id_external)}
|
|
131
|
+
</span>
|
|
132
|
+
) : (
|
|
133
|
+
<a
|
|
134
|
+
href={`/gchat/inbox/${conversation.id_external}`}
|
|
135
|
+
title="Ver no Chat"
|
|
136
|
+
onClick={(e) => e.stopPropagation()}
|
|
137
|
+
className="inline-flex items-center justify-center rounded-md p-1.5 text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
|
138
|
+
>
|
|
139
|
+
<ExternalLink className="h-4 w-4" />
|
|
140
|
+
</a>
|
|
141
|
+
)
|
|
142
|
+
) : null}
|
|
143
|
+
</TableCell>
|
|
144
|
+
</TableRow>
|
|
145
|
+
))}
|
|
146
|
+
</TableBody>
|
|
147
|
+
</Table>
|
|
148
|
+
|
|
149
|
+
{selectedId && (
|
|
150
|
+
<ConversationView
|
|
151
|
+
conversationId={selectedId}
|
|
152
|
+
onClose={() => setSelectedId(null)}
|
|
153
|
+
contactsMap={contactsMap}
|
|
154
|
+
config={config}
|
|
155
|
+
renderChatLink={renderChatLink}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useConversation } from "../../hooks";
|
|
2
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
3
|
+
import { Skeleton, Button } from "@greatapps/greatauth-ui/ui";
|
|
4
|
+
import { X, MessageSquare } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
interface ConversationViewProps {
|
|
7
|
+
conversationId: number;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
contactsMap?: Map<number, string>;
|
|
10
|
+
config: GagentsHookConfig;
|
|
11
|
+
renderChatLink?: (inboxId: number) => React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatDateTime(dateStr: string | null): string {
|
|
15
|
+
if (!dateStr) return "—";
|
|
16
|
+
return new Date(dateStr).toLocaleString("pt-BR");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ConversationView({
|
|
20
|
+
conversationId,
|
|
21
|
+
onClose,
|
|
22
|
+
contactsMap,
|
|
23
|
+
config,
|
|
24
|
+
renderChatLink,
|
|
25
|
+
}: ConversationViewProps) {
|
|
26
|
+
const { data: conversation, isLoading } = useConversation(config, conversationId);
|
|
27
|
+
|
|
28
|
+
if (isLoading) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="rounded-lg border bg-card p-4 space-y-3">
|
|
31
|
+
<Skeleton className="h-6 w-48" />
|
|
32
|
+
<div className="grid grid-cols-2 gap-3">
|
|
33
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
34
|
+
<Skeleton key={i} className="h-10 w-full" />
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!conversation) {
|
|
42
|
+
return (
|
|
43
|
+
<div className="rounded-lg border bg-card p-4">
|
|
44
|
+
<div className="flex items-center justify-between">
|
|
45
|
+
<p className="text-sm text-muted-foreground">
|
|
46
|
+
Conversa não encontrada.
|
|
47
|
+
</p>
|
|
48
|
+
<Button variant="ghost" size="icon" onClick={onClose}>
|
|
49
|
+
<X className="h-4 w-4" />
|
|
50
|
+
</Button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="rounded-lg border bg-card p-4 space-y-4">
|
|
58
|
+
<div className="flex items-center justify-between">
|
|
59
|
+
<h3 className="font-semibold">Detalhes da conversa #{conversation.id}</h3>
|
|
60
|
+
<div className="flex items-center gap-1">
|
|
61
|
+
<Button variant="ghost" size="icon" onClick={onClose}>
|
|
62
|
+
<X className="h-4 w-4" />
|
|
63
|
+
</Button>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{conversation.id_external ? (
|
|
68
|
+
renderChatLink ? (
|
|
69
|
+
renderChatLink(conversation.id_external)
|
|
70
|
+
) : (
|
|
71
|
+
<Button variant="outline" size="sm" asChild>
|
|
72
|
+
<a href={`/gchat/inbox/${conversation.id_external}`}>
|
|
73
|
+
<MessageSquare className="mr-2 h-4 w-4" />
|
|
74
|
+
Ver no Chat
|
|
75
|
+
</a>
|
|
76
|
+
</Button>
|
|
77
|
+
)
|
|
78
|
+
) : null}
|
|
79
|
+
|
|
80
|
+
<div className="grid grid-cols-2 gap-x-6 gap-y-3 text-sm">
|
|
81
|
+
<div>
|
|
82
|
+
<span className="text-muted-foreground">Thread ID</span>
|
|
83
|
+
<p className="font-mono text-xs mt-0.5">
|
|
84
|
+
{conversation.openai_thread_id || "—"}
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
<div>
|
|
88
|
+
<span className="text-muted-foreground">Contato</span>
|
|
89
|
+
<p className="mt-0.5">
|
|
90
|
+
{contactsMap?.get(conversation.id_contact) ?? conversation.id_contact}
|
|
91
|
+
</p>
|
|
92
|
+
</div>
|
|
93
|
+
<div>
|
|
94
|
+
<span className="text-muted-foreground">Agente</span>
|
|
95
|
+
<p className="mt-0.5">{conversation.id_agent ?? "—"}</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div>
|
|
98
|
+
<span className="text-muted-foreground">Mensagens</span>
|
|
99
|
+
<p className="mt-0.5">{conversation.message_count ?? "—"}</p>
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<span className="text-muted-foreground">Tokens</span>
|
|
103
|
+
<p className="mt-0.5">{conversation.usage_tokens ?? "—"}</p>
|
|
104
|
+
</div>
|
|
105
|
+
<div>
|
|
106
|
+
<span className="text-muted-foreground">Rotações de Thread</span>
|
|
107
|
+
<p className="mt-0.5">{conversation.rotation_count ?? "—"}</p>
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
<span className="text-muted-foreground">Criado em</span>
|
|
111
|
+
<p className="mt-0.5">
|
|
112
|
+
{formatDateTime(conversation.datetime_add)}
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
<div>
|
|
116
|
+
<span className="text-muted-foreground">Atualizado em</span>
|
|
117
|
+
<p className="mt-0.5">
|
|
118
|
+
{formatDateTime(conversation.datetime_alt)}
|
|
119
|
+
</p>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,9 @@ export { ToolsTable } from "./components/tools/tools-table";
|
|
|
37
37
|
export { ToolFormDialog } from "./components/tools/tool-form-dialog";
|
|
38
38
|
export { ToolCredentialsForm } from "./components/tools/tool-credentials-form";
|
|
39
39
|
export { Sortable, SortableContent, SortableItem, SortableItemHandle, SortableOverlay } from "./components/ui/sortable";
|
|
40
|
+
export { AgentConversationsPanel } from "./components/conversations/agent-conversations-panel";
|
|
41
|
+
export { AgentConversationsTable } from "./components/conversations/agent-conversations-table";
|
|
42
|
+
export { ConversationView } from "./components/conversations/conversation-view";
|
|
40
43
|
|
|
41
44
|
// Page Compositions
|
|
42
45
|
export * from "./pages";
|
|
@@ -6,20 +6,19 @@ import { Badge, Button, Skeleton } from "@greatapps/greatauth-ui/ui";
|
|
|
6
6
|
import { EntityAvatar } from "@greatapps/greatauth-ui";
|
|
7
7
|
import { ArrowLeft, Pencil } from "lucide-react";
|
|
8
8
|
import type { GagentsHookConfig } from "../hooks/types";
|
|
9
|
-
import type { Agent } from "../types";
|
|
10
9
|
|
|
11
10
|
export interface AgentDetailPageProps {
|
|
12
11
|
config: GagentsHookConfig;
|
|
13
12
|
agentId: number;
|
|
14
13
|
onBack?: () => void;
|
|
15
|
-
|
|
14
|
+
renderChatLink?: (inboxId: number) => React.ReactNode;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export function AgentDetailPage({
|
|
19
18
|
config,
|
|
20
19
|
agentId,
|
|
21
20
|
onBack,
|
|
22
|
-
|
|
21
|
+
renderChatLink,
|
|
23
22
|
}: AgentDetailPageProps) {
|
|
24
23
|
const { data: agent, isLoading } = useAgent(config, agentId);
|
|
25
24
|
const [editOpen, setEditOpen] = useState(false);
|
|
@@ -95,7 +94,7 @@ export function AgentDetailPage({
|
|
|
95
94
|
<AgentTabs
|
|
96
95
|
agent={agent}
|
|
97
96
|
config={config}
|
|
98
|
-
|
|
97
|
+
renderChatLink={renderChatLink}
|
|
99
98
|
/>
|
|
100
99
|
|
|
101
100
|
{editOpen && (
|