@agent-relay/dashboard 2.0.81 → 2.0.82
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/out/404.html +1 -1
- package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
- package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
- package/out/about.html +2 -2
- package/out/about.txt +1 -1
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +1 -1
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +1 -1
- package/out/changelog.html +2 -2
- package/out/changelog.txt +1 -1
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +1 -1
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +1 -1
- package/out/contact.html +2 -2
- package/out/contact.txt +1 -1
- package/out/docs.html +2 -2
- package/out/docs.txt +1 -1
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +1 -1
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +1 -1
- package/out/privacy.html +2 -2
- package/out/privacy.txt +1 -1
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +1 -1
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +1 -1
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +1 -1
- package/out/providers.html +1 -1
- package/out/providers.txt +1 -1
- package/out/security.html +2 -2
- package/out/security.txt +1 -1
- package/out/signup.html +2 -2
- package/out/signup.txt +1 -1
- package/out/terms.html +2 -2
- package/out/terms.txt +1 -1
- package/package.json +7 -1
- package/src/app/about/page.tsx +7 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
- package/src/app/app/[[...slug]]/page.tsx +23 -0
- package/src/app/app/onboarding/page.tsx +394 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
- package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
- package/src/app/blog/page.tsx +15 -0
- package/src/app/careers/page.tsx +7 -0
- package/src/app/changelog/page.tsx +7 -0
- package/src/app/cloud/link/page.tsx +464 -0
- package/src/app/complete-profile/page.tsx +204 -0
- package/src/app/connect-repos/page.tsx +410 -0
- package/src/app/contact/page.tsx +7 -0
- package/src/app/docs/page.tsx +7 -0
- package/src/app/favicon.png +0 -0
- package/src/app/globals.css +200 -0
- package/src/app/history/page.tsx +658 -0
- package/src/app/layout.tsx +25 -0
- package/src/app/login/page.tsx +424 -0
- package/src/app/metrics/page.tsx +781 -0
- package/src/app/page.tsx +59 -0
- package/src/app/pricing/page.tsx +7 -0
- package/src/app/privacy/page.tsx +7 -0
- package/src/app/providers/page.tsx +193 -0
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
- package/src/app/providers/setup/[provider]/constants.ts +35 -0
- package/src/app/providers/setup/[provider]/page.tsx +42 -0
- package/src/app/security/page.tsx +7 -0
- package/src/app/signup/page.tsx +533 -0
- package/src/app/terms/page.tsx +7 -0
- package/src/components/ActivityFeed.tsx +216 -0
- package/src/components/AddWorkspaceModal.tsx +170 -0
- package/src/components/AgentCard.test.tsx +134 -0
- package/src/components/AgentCard.tsx +585 -0
- package/src/components/AgentList.test.tsx +147 -0
- package/src/components/AgentList.tsx +419 -0
- package/src/components/AgentLogPreview.tsx +173 -0
- package/src/components/AgentProfilePanel.tsx +569 -0
- package/src/components/App.tsx +3424 -0
- package/src/components/BillingPanel.tsx +922 -0
- package/src/components/BillingResult.tsx +447 -0
- package/src/components/BroadcastComposer.tsx +690 -0
- package/src/components/ChannelAdminPanel.tsx +773 -0
- package/src/components/ChannelBrowser.tsx +385 -0
- package/src/components/ChannelChat.tsx +261 -0
- package/src/components/ChannelSidebar.tsx +399 -0
- package/src/components/CloudSessionProvider.tsx +130 -0
- package/src/components/CommandPalette.tsx +815 -0
- package/src/components/ConfirmationDialog.tsx +133 -0
- package/src/components/ConversationHistory.tsx +518 -0
- package/src/components/CoordinatorPanel.tsx +956 -0
- package/src/components/DecisionQueue.tsx +717 -0
- package/src/components/DirectMessageView.tsx +164 -0
- package/src/components/FileAutocomplete.tsx +368 -0
- package/src/components/FleetOverview.tsx +278 -0
- package/src/components/LogViewer.tsx +310 -0
- package/src/components/LogViewerPanel.tsx +482 -0
- package/src/components/Logo.tsx +284 -0
- package/src/components/MentionAutocomplete.tsx +384 -0
- package/src/components/MessageComposer.tsx +473 -0
- package/src/components/MessageList.tsx +725 -0
- package/src/components/MessageSenderName.tsx +91 -0
- package/src/components/MessageStatusIndicator.tsx +142 -0
- package/src/components/NewConversationModal.tsx +400 -0
- package/src/components/NotificationToast.tsx +488 -0
- package/src/components/OnlineUsersIndicator.tsx +164 -0
- package/src/components/Pagination.tsx +124 -0
- package/src/components/PricingPlans.tsx +386 -0
- package/src/components/ProjectList.tsx +711 -0
- package/src/components/ProviderAuthFlow.tsx +343 -0
- package/src/components/ProviderConnectionList.tsx +375 -0
- package/src/components/ProvisioningProgress.tsx +730 -0
- package/src/components/ReactionChips.tsx +70 -0
- package/src/components/ReactionPicker.tsx +121 -0
- package/src/components/RepoAccessPanel.tsx +787 -0
- package/src/components/RepositoriesPanel.tsx +901 -0
- package/src/components/ServerCard.tsx +202 -0
- package/src/components/SessionExpiredModal.tsx +128 -0
- package/src/components/SpawnModal.test.tsx +190 -0
- package/src/components/SpawnModal.tsx +1001 -0
- package/src/components/TaskAssignmentUI.tsx +375 -0
- package/src/components/TerminalProviderSetup.tsx +517 -0
- package/src/components/ThemeProvider.tsx +159 -0
- package/src/components/ThinkingIndicator.tsx +231 -0
- package/src/components/ThreadList.tsx +198 -0
- package/src/components/ThreadPanel.tsx +405 -0
- package/src/components/TrajectoryViewer.tsx +698 -0
- package/src/components/TypingIndicator.tsx +69 -0
- package/src/components/UsageBanner.tsx +231 -0
- package/src/components/UserProfilePanel.tsx +233 -0
- package/src/components/WorkspaceContext.tsx +95 -0
- package/src/components/WorkspaceSelector.tsx +234 -0
- package/src/components/WorkspaceStatusIndicator.tsx +396 -0
- package/src/components/XTermInteractive.tsx +516 -0
- package/src/components/XTermLogViewer.tsx +719 -0
- package/src/components/channels/ChannelDialogs.tsx +1411 -0
- package/src/components/channels/ChannelHeader.tsx +317 -0
- package/src/components/channels/ChannelMessageList.tsx +463 -0
- package/src/components/channels/ChannelViewV1.tsx +146 -0
- package/src/components/channels/MessageInput.tsx +302 -0
- package/src/components/channels/SearchInput.tsx +172 -0
- package/src/components/channels/SearchResults.tsx +336 -0
- package/src/components/channels/api.test.ts +1527 -0
- package/src/components/channels/api.ts +703 -0
- package/src/components/channels/index.ts +76 -0
- package/src/components/channels/mockApi.ts +344 -0
- package/src/components/channels/types.ts +566 -0
- package/src/components/hooks/index.ts +58 -0
- package/src/components/hooks/useAgentLogs.ts +504 -0
- package/src/components/hooks/useAgents.ts +127 -0
- package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
- package/src/components/hooks/useBroadcastDedup.ts +86 -0
- package/src/components/hooks/useChannelAdmin.ts +329 -0
- package/src/components/hooks/useChannelBrowser.ts +239 -0
- package/src/components/hooks/useChannelCommands.ts +138 -0
- package/src/components/hooks/useChannels.ts +367 -0
- package/src/components/hooks/useDebounce.ts +29 -0
- package/src/components/hooks/useDirectMessage.test.ts +952 -0
- package/src/components/hooks/useDirectMessage.ts +141 -0
- package/src/components/hooks/useMessages.ts +310 -0
- package/src/components/hooks/useOrchestrator.test.ts +165 -0
- package/src/components/hooks/useOrchestrator.ts +424 -0
- package/src/components/hooks/usePinnedAgents.test.ts +356 -0
- package/src/components/hooks/usePinnedAgents.ts +140 -0
- package/src/components/hooks/usePresence.test.ts +245 -0
- package/src/components/hooks/usePresence.ts +377 -0
- package/src/components/hooks/useRecentRepos.ts +130 -0
- package/src/components/hooks/useSession.ts +209 -0
- package/src/components/hooks/useThread.ts +138 -0
- package/src/components/hooks/useTrajectory.ts +265 -0
- package/src/components/hooks/useWebSocket.ts +290 -0
- package/src/components/hooks/useWorkspaceMembers.ts +132 -0
- package/src/components/hooks/useWorkspaceRepos.ts +73 -0
- package/src/components/hooks/useWorkspaceStatus.ts +237 -0
- package/src/components/index.ts +81 -0
- package/src/components/layout/Header.tsx +311 -0
- package/src/components/layout/RepoContextHeader.tsx +361 -0
- package/src/components/layout/Sidebar.archive.test.tsx +126 -0
- package/src/components/layout/Sidebar.test.tsx +691 -0
- package/src/components/layout/Sidebar.tsx +900 -0
- package/src/components/layout/index.ts +7 -0
- package/src/components/settings/BillingSettingsPanel.tsx +564 -0
- package/src/components/settings/SettingsPage.tsx +683 -0
- package/src/components/settings/TeamSettingsPanel.tsx +560 -0
- package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
- package/src/components/settings/index.ts +11 -0
- package/src/components/settings/types.ts +79 -0
- package/src/components/utils/messageFormatting.test.tsx +331 -0
- package/src/components/utils/messageFormatting.tsx +597 -0
- package/src/index.ts +63 -0
- package/src/landing/AboutPage.tsx +77 -0
- package/src/landing/BlogContent.tsx +187 -0
- package/src/landing/BlogPage.tsx +47 -0
- package/src/landing/CareersPage.tsx +53 -0
- package/src/landing/ChangelogPage.tsx +33 -0
- package/src/landing/ContactPage.tsx +41 -0
- package/src/landing/DocsPage.tsx +43 -0
- package/src/landing/LandingPage.tsx +702 -0
- package/src/landing/PricingPage.tsx +549 -0
- package/src/landing/PrivacyPage.tsx +117 -0
- package/src/landing/SecurityPage.tsx +42 -0
- package/src/landing/StaticPage.tsx +165 -0
- package/src/landing/TermsPage.tsx +125 -0
- package/src/landing/blogData.ts +312 -0
- package/src/landing/index.ts +18 -0
- package/src/landing/styles.css +3673 -0
- package/src/lib/agent-merge.test.ts +43 -0
- package/src/lib/agent-merge.ts +35 -0
- package/src/lib/api.ts +1294 -0
- package/src/lib/cloudApi.ts +893 -0
- package/src/lib/colors.test.ts +175 -0
- package/src/lib/colors.ts +218 -0
- package/src/lib/config.ts +109 -0
- package/src/lib/hierarchy.ts +242 -0
- package/src/lib/stuckDetection.ts +142 -0
- package/src/lib/useUrlRouting.ts +190 -0
- package/src/types/index.ts +317 -0
- package/src/types/threading.ts +7 -0
- package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
- package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
- /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
- /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
- /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
- /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfirmationDialog Component
|
|
3
|
+
*
|
|
4
|
+
* A reusable confirmation modal for destructive or important actions.
|
|
5
|
+
* Supports danger and primary variants.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useEffect, useRef } from 'react';
|
|
9
|
+
|
|
10
|
+
export interface ConfirmationDialogProps {
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
title: string;
|
|
13
|
+
message: string;
|
|
14
|
+
confirmLabel?: string;
|
|
15
|
+
cancelLabel?: string;
|
|
16
|
+
confirmVariant?: 'danger' | 'primary';
|
|
17
|
+
onConfirm: () => void;
|
|
18
|
+
onCancel: () => void;
|
|
19
|
+
isProcessing?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ConfirmationDialog({
|
|
23
|
+
isOpen,
|
|
24
|
+
title,
|
|
25
|
+
message,
|
|
26
|
+
confirmLabel = 'Confirm',
|
|
27
|
+
cancelLabel = 'Cancel',
|
|
28
|
+
confirmVariant = 'danger',
|
|
29
|
+
onConfirm,
|
|
30
|
+
onCancel,
|
|
31
|
+
isProcessing = false,
|
|
32
|
+
}: ConfirmationDialogProps) {
|
|
33
|
+
const cancelButtonRef = useRef<HTMLButtonElement>(null);
|
|
34
|
+
|
|
35
|
+
// Focus cancel button when dialog opens
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (isOpen && cancelButtonRef.current) {
|
|
38
|
+
cancelButtonRef.current.focus();
|
|
39
|
+
}
|
|
40
|
+
}, [isOpen]);
|
|
41
|
+
|
|
42
|
+
// Handle escape key
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!isOpen) return;
|
|
45
|
+
|
|
46
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
47
|
+
if (e.key === 'Escape' && !isProcessing) {
|
|
48
|
+
onCancel();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
53
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
54
|
+
}, [isOpen, isProcessing, onCancel]);
|
|
55
|
+
|
|
56
|
+
if (!isOpen) return null;
|
|
57
|
+
|
|
58
|
+
const confirmButtonClasses = confirmVariant === 'danger'
|
|
59
|
+
? 'bg-error hover:bg-error/90 text-white'
|
|
60
|
+
: 'bg-accent-cyan hover:bg-accent-cyan/90 text-bg-deep';
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className="fixed inset-0 bg-black/60 flex items-center justify-center z-[1100] animate-fade-in"
|
|
65
|
+
onClick={(e) => {
|
|
66
|
+
if (e.target === e.currentTarget && !isProcessing) {
|
|
67
|
+
onCancel();
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<div
|
|
72
|
+
className="bg-sidebar-bg border border-sidebar-border rounded-xl w-[400px] max-w-[90vw] shadow-modal animate-slide-down"
|
|
73
|
+
role="alertdialog"
|
|
74
|
+
aria-modal="true"
|
|
75
|
+
aria-labelledby="confirmation-title"
|
|
76
|
+
aria-describedby="confirmation-message"
|
|
77
|
+
>
|
|
78
|
+
{/* Header */}
|
|
79
|
+
<div className="p-4 border-b border-sidebar-border">
|
|
80
|
+
<h2
|
|
81
|
+
id="confirmation-title"
|
|
82
|
+
className="text-lg font-semibold text-text-primary m-0"
|
|
83
|
+
>
|
|
84
|
+
{title}
|
|
85
|
+
</h2>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{/* Body */}
|
|
89
|
+
<div className="p-4">
|
|
90
|
+
<p
|
|
91
|
+
id="confirmation-message"
|
|
92
|
+
className="text-sm text-text-secondary m-0 leading-relaxed"
|
|
93
|
+
>
|
|
94
|
+
{message}
|
|
95
|
+
</p>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Footer */}
|
|
99
|
+
<div className="flex justify-end gap-3 p-4 pt-0">
|
|
100
|
+
<button
|
|
101
|
+
ref={cancelButtonRef}
|
|
102
|
+
onClick={onCancel}
|
|
103
|
+
disabled={isProcessing}
|
|
104
|
+
className="px-4 py-2 text-sm font-medium text-text-secondary border border-sidebar-border rounded-lg hover:bg-sidebar-border disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
105
|
+
>
|
|
106
|
+
{cancelLabel}
|
|
107
|
+
</button>
|
|
108
|
+
<button
|
|
109
|
+
onClick={onConfirm}
|
|
110
|
+
disabled={isProcessing}
|
|
111
|
+
className={`px-4 py-2 text-sm font-medium rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-2 ${confirmButtonClasses}`}
|
|
112
|
+
>
|
|
113
|
+
{isProcessing && (
|
|
114
|
+
<svg className="animate-spin" width="14" height="14" viewBox="0 0 24 24">
|
|
115
|
+
<circle
|
|
116
|
+
cx="12"
|
|
117
|
+
cy="12"
|
|
118
|
+
r="10"
|
|
119
|
+
stroke="currentColor"
|
|
120
|
+
strokeWidth="2"
|
|
121
|
+
fill="none"
|
|
122
|
+
strokeDasharray="32"
|
|
123
|
+
strokeLinecap="round"
|
|
124
|
+
/>
|
|
125
|
+
</svg>
|
|
126
|
+
)}
|
|
127
|
+
{confirmLabel}
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation History Viewer
|
|
3
|
+
*
|
|
4
|
+
* Displays historical conversations from the database with:
|
|
5
|
+
* - Session list with filtering
|
|
6
|
+
* - Conversation view grouped by agent pairs
|
|
7
|
+
* - Message search
|
|
8
|
+
* - Storage statistics
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
12
|
+
import {
|
|
13
|
+
api,
|
|
14
|
+
type HistorySession,
|
|
15
|
+
type HistoryMessage,
|
|
16
|
+
type Conversation,
|
|
17
|
+
type HistoryStats,
|
|
18
|
+
} from '../lib/api';
|
|
19
|
+
|
|
20
|
+
type ViewMode = 'conversations' | 'sessions' | 'messages';
|
|
21
|
+
|
|
22
|
+
interface ConversationHistoryProps {
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
onClose: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ConversationHistory({ isOpen, onClose }: ConversationHistoryProps) {
|
|
28
|
+
const [viewMode, setViewMode] = useState<ViewMode>('conversations');
|
|
29
|
+
const [conversations, setConversations] = useState<Conversation[]>([]);
|
|
30
|
+
const [sessions, setSessions] = useState<HistorySession[]>([]);
|
|
31
|
+
const [messages, setMessages] = useState<HistoryMessage[]>([]);
|
|
32
|
+
const [stats, setStats] = useState<HistoryStats | null>(null);
|
|
33
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
34
|
+
const [error, setError] = useState<string | null>(null);
|
|
35
|
+
|
|
36
|
+
// Filters
|
|
37
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
38
|
+
const [agentFilter, setAgentFilter] = useState('');
|
|
39
|
+
const [selectedConversation, setSelectedConversation] = useState<Conversation | null>(null);
|
|
40
|
+
|
|
41
|
+
// Fetch stats on mount
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!isOpen) return;
|
|
44
|
+
|
|
45
|
+
const fetchStats = async () => {
|
|
46
|
+
const result = await api.getHistoryStats();
|
|
47
|
+
if (result.success && result.data) {
|
|
48
|
+
setStats(result.data);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
fetchStats();
|
|
52
|
+
}, [isOpen]);
|
|
53
|
+
|
|
54
|
+
// Fetch data based on view mode
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!isOpen) return;
|
|
57
|
+
|
|
58
|
+
const fetchData = async () => {
|
|
59
|
+
setIsLoading(true);
|
|
60
|
+
setError(null);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
if (viewMode === 'conversations') {
|
|
64
|
+
const result = await api.getHistoryConversations();
|
|
65
|
+
if (result.success && result.data) {
|
|
66
|
+
setConversations(result.data.conversations);
|
|
67
|
+
} else {
|
|
68
|
+
setError(result.error || 'Failed to fetch conversations');
|
|
69
|
+
}
|
|
70
|
+
} else if (viewMode === 'sessions') {
|
|
71
|
+
const result = await api.getHistorySessions({
|
|
72
|
+
agent: agentFilter || undefined,
|
|
73
|
+
limit: 50,
|
|
74
|
+
});
|
|
75
|
+
if (result.success && result.data) {
|
|
76
|
+
setSessions(result.data.sessions);
|
|
77
|
+
} else {
|
|
78
|
+
setError(result.error || 'Failed to fetch sessions');
|
|
79
|
+
}
|
|
80
|
+
} else if (viewMode === 'messages') {
|
|
81
|
+
const params: Parameters<typeof api.getHistoryMessages>[0] = {
|
|
82
|
+
limit: 100,
|
|
83
|
+
order: 'desc',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (searchQuery) {
|
|
87
|
+
params.search = searchQuery;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (selectedConversation) {
|
|
91
|
+
// Get messages between the two participants
|
|
92
|
+
const [p1, p2] = selectedConversation.participants;
|
|
93
|
+
// We need to fetch messages in both directions
|
|
94
|
+
const result1 = await api.getHistoryMessages({ ...params, from: p1, to: p2 });
|
|
95
|
+
const result2 = await api.getHistoryMessages({ ...params, from: p2, to: p1 });
|
|
96
|
+
|
|
97
|
+
if (result1.success && result2.success) {
|
|
98
|
+
const allMessages = [
|
|
99
|
+
...(result1.data?.messages || []),
|
|
100
|
+
...(result2.data?.messages || []),
|
|
101
|
+
].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
102
|
+
setMessages(allMessages);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
const result = await api.getHistoryMessages(params);
|
|
106
|
+
if (result.success && result.data) {
|
|
107
|
+
setMessages(result.data.messages);
|
|
108
|
+
} else {
|
|
109
|
+
setError(result.error || 'Failed to fetch messages');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (_err) {
|
|
114
|
+
setError('Failed to load data');
|
|
115
|
+
} finally {
|
|
116
|
+
setIsLoading(false);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
fetchData();
|
|
121
|
+
}, [isOpen, viewMode, agentFilter, searchQuery, selectedConversation]);
|
|
122
|
+
|
|
123
|
+
// Handle conversation click - show messages for that conversation
|
|
124
|
+
const handleConversationClick = useCallback((conv: Conversation) => {
|
|
125
|
+
setSelectedConversation(conv);
|
|
126
|
+
setViewMode('messages');
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
// Handle back from conversation messages
|
|
130
|
+
const handleBackToConversations = useCallback(() => {
|
|
131
|
+
setSelectedConversation(null);
|
|
132
|
+
setViewMode('conversations');
|
|
133
|
+
}, []);
|
|
134
|
+
|
|
135
|
+
if (!isOpen) return null;
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50">
|
|
139
|
+
<div className="bg-bg-primary rounded-lg shadow-xl w-[90vw] max-w-4xl h-[80vh] flex flex-col">
|
|
140
|
+
{/* Header */}
|
|
141
|
+
<div className="flex items-center justify-between p-4 border-b border-border">
|
|
142
|
+
<div className="flex items-center gap-4">
|
|
143
|
+
<h2 className="text-lg font-semibold text-text-primary">Conversation History</h2>
|
|
144
|
+
{stats && typeof stats.messageCount === 'number' && (
|
|
145
|
+
<div className="flex gap-4 text-sm text-text-muted">
|
|
146
|
+
<span>{stats.messageCount} messages</span>
|
|
147
|
+
<span>{stats.sessionCount} sessions</span>
|
|
148
|
+
<span>{stats.uniqueAgents} agents</span>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
<button
|
|
153
|
+
onClick={onClose}
|
|
154
|
+
className="p-2 text-text-muted hover:text-text-primary rounded transition-colors"
|
|
155
|
+
aria-label="Close"
|
|
156
|
+
>
|
|
157
|
+
<CloseIcon />
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
{/* Toolbar */}
|
|
162
|
+
<div className="flex items-center gap-4 p-4 border-b border-border">
|
|
163
|
+
{/* View mode tabs */}
|
|
164
|
+
<div className="flex gap-1 bg-bg-secondary rounded-md p-1">
|
|
165
|
+
<TabButton
|
|
166
|
+
active={viewMode === 'conversations'}
|
|
167
|
+
onClick={() => {
|
|
168
|
+
setSelectedConversation(null);
|
|
169
|
+
setViewMode('conversations');
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
Conversations
|
|
173
|
+
</TabButton>
|
|
174
|
+
<TabButton
|
|
175
|
+
active={viewMode === 'sessions'}
|
|
176
|
+
onClick={() => setViewMode('sessions')}
|
|
177
|
+
>
|
|
178
|
+
Sessions
|
|
179
|
+
</TabButton>
|
|
180
|
+
<TabButton
|
|
181
|
+
active={viewMode === 'messages'}
|
|
182
|
+
onClick={() => setViewMode('messages')}
|
|
183
|
+
>
|
|
184
|
+
Messages
|
|
185
|
+
</TabButton>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Search */}
|
|
189
|
+
{viewMode === 'messages' && (
|
|
190
|
+
<div className="flex-1 max-w-xs">
|
|
191
|
+
<input
|
|
192
|
+
type="text"
|
|
193
|
+
placeholder="Search messages..."
|
|
194
|
+
value={searchQuery}
|
|
195
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
196
|
+
className="w-full px-3 py-2 bg-bg-secondary border border-border rounded-md text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:border-accent"
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
|
|
201
|
+
{/* Agent filter for sessions */}
|
|
202
|
+
{viewMode === 'sessions' && (
|
|
203
|
+
<div className="flex-1 max-w-xs">
|
|
204
|
+
<input
|
|
205
|
+
type="text"
|
|
206
|
+
placeholder="Filter by agent..."
|
|
207
|
+
value={agentFilter}
|
|
208
|
+
onChange={(e) => setAgentFilter(e.target.value)}
|
|
209
|
+
className="w-full px-3 py-2 bg-bg-secondary border border-border rounded-md text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:border-accent"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{/* Back button when viewing conversation messages */}
|
|
215
|
+
{selectedConversation && (
|
|
216
|
+
<button
|
|
217
|
+
onClick={handleBackToConversations}
|
|
218
|
+
className="flex items-center gap-1 px-3 py-2 text-sm text-text-muted hover:text-text-primary"
|
|
219
|
+
>
|
|
220
|
+
<BackIcon /> Back to conversations
|
|
221
|
+
</button>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* Content */}
|
|
226
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
227
|
+
{isLoading ? (
|
|
228
|
+
<div className="flex items-center justify-center h-full">
|
|
229
|
+
<LoadingSpinner />
|
|
230
|
+
</div>
|
|
231
|
+
) : error ? (
|
|
232
|
+
<div className="flex items-center justify-center h-full text-error">
|
|
233
|
+
{error}
|
|
234
|
+
</div>
|
|
235
|
+
) : viewMode === 'conversations' ? (
|
|
236
|
+
<ConversationList
|
|
237
|
+
conversations={conversations}
|
|
238
|
+
onConversationClick={handleConversationClick}
|
|
239
|
+
/>
|
|
240
|
+
) : viewMode === 'sessions' ? (
|
|
241
|
+
<SessionList sessions={sessions} />
|
|
242
|
+
) : (
|
|
243
|
+
<MessageHistoryList
|
|
244
|
+
messages={messages}
|
|
245
|
+
conversationTitle={
|
|
246
|
+
selectedConversation
|
|
247
|
+
? `${selectedConversation.participants[0]} & ${selectedConversation.participants[1]}`
|
|
248
|
+
: undefined
|
|
249
|
+
}
|
|
250
|
+
/>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Sub-components
|
|
259
|
+
|
|
260
|
+
interface TabButtonProps {
|
|
261
|
+
active: boolean;
|
|
262
|
+
onClick: () => void;
|
|
263
|
+
children: React.ReactNode;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function TabButton({ active, onClick, children }: TabButtonProps) {
|
|
267
|
+
return (
|
|
268
|
+
<button
|
|
269
|
+
onClick={onClick}
|
|
270
|
+
className={`px-3 py-1.5 text-sm rounded transition-colors ${
|
|
271
|
+
active
|
|
272
|
+
? 'bg-accent text-white'
|
|
273
|
+
: 'text-text-muted hover:text-text-primary hover:bg-bg-primary'
|
|
274
|
+
}`}
|
|
275
|
+
>
|
|
276
|
+
{children}
|
|
277
|
+
</button>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
interface ConversationListProps {
|
|
282
|
+
conversations: Conversation[];
|
|
283
|
+
onConversationClick: (conv: Conversation) => void;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function ConversationList({ conversations, onConversationClick }: ConversationListProps) {
|
|
287
|
+
if (conversations.length === 0) {
|
|
288
|
+
return (
|
|
289
|
+
<div className="flex items-center justify-center h-full text-text-muted">
|
|
290
|
+
No conversations found
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div className="space-y-2">
|
|
297
|
+
{conversations.map((conv, index) => (
|
|
298
|
+
<div
|
|
299
|
+
key={index}
|
|
300
|
+
onClick={() => onConversationClick(conv)}
|
|
301
|
+
className="p-4 bg-bg-secondary rounded-lg cursor-pointer hover:bg-bg-tertiary transition-colors"
|
|
302
|
+
>
|
|
303
|
+
<div className="flex items-center justify-between mb-2">
|
|
304
|
+
<div className="flex items-center gap-2">
|
|
305
|
+
<span className="font-medium text-text-primary">
|
|
306
|
+
{conv.participants.join(' & ')}
|
|
307
|
+
</span>
|
|
308
|
+
<span className="text-xs px-2 py-0.5 bg-accent/10 text-accent rounded-full">
|
|
309
|
+
{conv.messageCount} messages
|
|
310
|
+
</span>
|
|
311
|
+
</div>
|
|
312
|
+
<span className="text-xs text-text-muted">
|
|
313
|
+
{formatRelativeTime(conv.lastTimestamp)}
|
|
314
|
+
</span>
|
|
315
|
+
</div>
|
|
316
|
+
<p className="text-sm text-text-muted truncate">{conv.lastMessage}</p>
|
|
317
|
+
</div>
|
|
318
|
+
))}
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
interface SessionListProps {
|
|
324
|
+
sessions: HistorySession[];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function SessionList({ sessions }: SessionListProps) {
|
|
328
|
+
if (sessions.length === 0) {
|
|
329
|
+
return (
|
|
330
|
+
<div className="flex items-center justify-center h-full text-text-muted">
|
|
331
|
+
No sessions found
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<div className="space-y-2">
|
|
338
|
+
{sessions.map((session) => (
|
|
339
|
+
<div
|
|
340
|
+
key={session.id}
|
|
341
|
+
className="p-4 bg-bg-secondary rounded-lg"
|
|
342
|
+
>
|
|
343
|
+
<div className="flex items-center justify-between mb-2">
|
|
344
|
+
<div className="flex items-center gap-2">
|
|
345
|
+
<span className="font-medium text-text-primary">{session.agentName}</span>
|
|
346
|
+
{session.cli && (
|
|
347
|
+
<span className="text-xs px-2 py-0.5 bg-bg-tertiary text-text-muted rounded">
|
|
348
|
+
{session.cli}
|
|
349
|
+
</span>
|
|
350
|
+
)}
|
|
351
|
+
<StatusBadge isActive={session.isActive} closedBy={session.closedBy} />
|
|
352
|
+
</div>
|
|
353
|
+
<span className="text-xs text-text-muted">{session.duration}</span>
|
|
354
|
+
</div>
|
|
355
|
+
<div className="flex items-center gap-4 text-sm text-text-muted">
|
|
356
|
+
<span>{formatDate(session.startedAt)}</span>
|
|
357
|
+
<span>{session.messageCount} messages</span>
|
|
358
|
+
</div>
|
|
359
|
+
{session.summary && (
|
|
360
|
+
<p className="mt-2 text-sm text-text-muted truncate">{session.summary}</p>
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
363
|
+
))}
|
|
364
|
+
</div>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
interface StatusBadgeProps {
|
|
369
|
+
isActive: boolean;
|
|
370
|
+
closedBy?: 'agent' | 'disconnect' | 'error';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function StatusBadge({ isActive, closedBy }: StatusBadgeProps) {
|
|
374
|
+
if (isActive) {
|
|
375
|
+
return (
|
|
376
|
+
<span className="text-xs px-2 py-0.5 bg-success/10 text-success rounded-full">
|
|
377
|
+
Active
|
|
378
|
+
</span>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const statusColors = {
|
|
383
|
+
agent: 'bg-text-muted/10 text-text-muted',
|
|
384
|
+
disconnect: 'bg-warning/10 text-warning',
|
|
385
|
+
error: 'bg-error/10 text-error',
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const statusText = {
|
|
389
|
+
agent: 'Closed',
|
|
390
|
+
disconnect: 'Disconnected',
|
|
391
|
+
error: 'Error',
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const color = closedBy ? statusColors[closedBy] : statusColors.agent;
|
|
395
|
+
const text = closedBy ? statusText[closedBy] : 'Ended';
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<span className={`text-xs px-2 py-0.5 rounded-full ${color}`}>
|
|
399
|
+
{text}
|
|
400
|
+
</span>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
interface MessageHistoryListProps {
|
|
405
|
+
messages: HistoryMessage[];
|
|
406
|
+
conversationTitle?: string;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function MessageHistoryList({ messages, conversationTitle }: MessageHistoryListProps) {
|
|
410
|
+
if (messages.length === 0) {
|
|
411
|
+
return (
|
|
412
|
+
<div className="flex items-center justify-center h-full text-text-muted">
|
|
413
|
+
No messages found
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return (
|
|
419
|
+
<div className="space-y-4">
|
|
420
|
+
{conversationTitle && (
|
|
421
|
+
<h3 className="text-lg font-medium text-text-primary mb-4">{conversationTitle}</h3>
|
|
422
|
+
)}
|
|
423
|
+
{messages.map((msg) => (
|
|
424
|
+
<div
|
|
425
|
+
key={msg.id}
|
|
426
|
+
className="p-4 bg-bg-secondary rounded-lg"
|
|
427
|
+
>
|
|
428
|
+
<div className="flex items-center justify-between mb-2">
|
|
429
|
+
<div className="flex items-center gap-2">
|
|
430
|
+
<span className="font-medium text-accent">{msg.from}</span>
|
|
431
|
+
<span className="text-text-muted">to</span>
|
|
432
|
+
<span className="font-medium text-text-primary">
|
|
433
|
+
{msg.to === '*' ? 'Everyone' : msg.to}
|
|
434
|
+
</span>
|
|
435
|
+
{msg.isBroadcast && (
|
|
436
|
+
<span className="text-xs px-2 py-0.5 bg-accent/10 text-accent rounded-full">
|
|
437
|
+
Broadcast
|
|
438
|
+
</span>
|
|
439
|
+
)}
|
|
440
|
+
{msg.isUrgent && (
|
|
441
|
+
<span className="text-xs px-2 py-0.5 bg-error/10 text-error rounded-full">
|
|
442
|
+
Urgent
|
|
443
|
+
</span>
|
|
444
|
+
)}
|
|
445
|
+
</div>
|
|
446
|
+
<span className="text-xs text-text-muted">
|
|
447
|
+
{formatRelativeTime(msg.timestamp)}
|
|
448
|
+
</span>
|
|
449
|
+
</div>
|
|
450
|
+
<p className="text-sm text-text-primary whitespace-pre-wrap">{msg.content}</p>
|
|
451
|
+
{msg.thread && (
|
|
452
|
+
<div className="mt-2 text-xs text-text-muted">
|
|
453
|
+
Thread: {msg.thread.slice(0, 8)}...
|
|
454
|
+
</div>
|
|
455
|
+
)}
|
|
456
|
+
</div>
|
|
457
|
+
))}
|
|
458
|
+
</div>
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Utility functions
|
|
463
|
+
|
|
464
|
+
function formatRelativeTime(timestamp: string): string {
|
|
465
|
+
const date = new Date(timestamp);
|
|
466
|
+
const now = new Date();
|
|
467
|
+
const diffMs = now.getTime() - date.getTime();
|
|
468
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
469
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
470
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
471
|
+
|
|
472
|
+
if (diffMins < 1) return 'just now';
|
|
473
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
474
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
475
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
476
|
+
|
|
477
|
+
return date.toLocaleDateString();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function formatDate(timestamp: string): string {
|
|
481
|
+
return new Date(timestamp).toLocaleString();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Icons
|
|
485
|
+
|
|
486
|
+
function CloseIcon() {
|
|
487
|
+
return (
|
|
488
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
489
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
490
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
491
|
+
</svg>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function BackIcon() {
|
|
496
|
+
return (
|
|
497
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
498
|
+
<polyline points="15 18 9 12 15 6" />
|
|
499
|
+
</svg>
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function LoadingSpinner() {
|
|
504
|
+
return (
|
|
505
|
+
<svg className="animate-spin text-accent" width="24" height="24" viewBox="0 0 24 24">
|
|
506
|
+
<circle
|
|
507
|
+
cx="12"
|
|
508
|
+
cy="12"
|
|
509
|
+
r="10"
|
|
510
|
+
stroke="currentColor"
|
|
511
|
+
strokeWidth="2"
|
|
512
|
+
fill="none"
|
|
513
|
+
strokeDasharray="32"
|
|
514
|
+
strokeLinecap="round"
|
|
515
|
+
/>
|
|
516
|
+
</svg>
|
|
517
|
+
);
|
|
518
|
+
}
|