@agent-relay/dashboard 2.0.82 → 2.0.84
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/1028-da5d75e35d1420f1.js +1 -0
- package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
- package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
- package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
- package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
- package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
- package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
- package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
- package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
- package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
- package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
- package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
- package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
- package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
- package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
- package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
- package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
- package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
- package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
- package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
- package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
- package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
- package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
- package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
- package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
- package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
- package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
- package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
- package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
- package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
- package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
- package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
- package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
- package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
- package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
- package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
- package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
- package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
- package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
- package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
- package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
- package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
- package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
- package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
- package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
- package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
- package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
- package/out/about.html +2 -2
- package/out/about.txt +2 -2
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +2 -2
- 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 +3 -3
- 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 +2 -2
- package/out/changelog.html +2 -2
- package/out/changelog.txt +2 -2
- 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 +2 -2
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +2 -2
- package/out/contact.html +2 -2
- package/out/contact.txt +2 -2
- package/out/dev/cli-tools.html +1 -0
- package/out/dev/cli-tools.txt +7 -0
- package/out/dev/log-viewer.html +23 -0
- package/out/dev/log-viewer.txt +7 -0
- package/out/docs.html +2 -2
- package/out/docs.txt +2 -2
- 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 +2 -2
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +2 -2
- package/out/privacy.html +2 -2
- package/out/privacy.txt +2 -2
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +2 -2
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +2 -2
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +2 -2
- package/out/providers.html +1 -1
- package/out/providers.txt +2 -2
- package/out/security.html +2 -2
- package/out/security.txt +2 -2
- package/out/signup.html +2 -2
- package/out/signup.txt +2 -2
- package/out/terms.html +2 -2
- package/out/terms.txt +2 -2
- package/package.json +5 -1
- package/src/adapters/DashboardConfigProvider.tsx +56 -0
- package/src/adapters/cloudFetchAdapter.ts +278 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/types.ts +508 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
- package/src/app/app/onboarding/page.tsx +870 -170
- package/src/app/cloud/link/page.tsx +14 -6
- package/src/app/connect-repos/page.tsx +9 -3
- package/src/app/dev/cli-tools/page.tsx +130 -0
- package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
- package/src/app/dev/log-viewer/fixtures.ts +110 -0
- package/src/app/dev/log-viewer/page.tsx +288 -0
- package/src/app/history/page.tsx +28 -12
- package/src/app/page.tsx +1 -1
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
- package/src/components/AgentCard.tsx +4 -4
- package/src/components/AgentLogPreview.tsx +2 -38
- package/src/components/App.tsx +441 -2624
- package/src/components/CliToolHarness.test.tsx +83 -0
- package/src/components/CliToolHarness.tsx +292 -0
- package/src/components/CoordinatorPanel.tsx +13 -6
- package/src/components/LogViewer.tsx +2 -42
- package/src/components/ProviderAuthFlow.tsx +201 -81
- package/src/components/ProvisioningProgress.tsx +1 -1
- package/src/components/ReactionChips.tsx +2 -1
- package/src/components/SpawnModal.test.tsx +51 -18
- package/src/components/SpawnModal.tsx +175 -207
- package/src/components/TerminalProviderSetup.tsx +1 -1
- package/src/components/ThreadPanel.tsx +2 -0
- package/src/components/WorkspaceContext.tsx +7 -19
- package/src/components/XTermLogViewer.tsx +190 -27
- package/src/components/channels/ChannelMessageList.tsx +94 -4
- package/src/components/channels/ChannelViewV1.tsx +35 -11
- package/src/components/channels/api.ts +21 -20
- package/src/components/channels/types.ts +16 -0
- package/src/components/hooks/index.ts +0 -19
- package/src/components/hooks/useMessages.test.ts +80 -0
- package/src/components/hooks/useMessages.ts +13 -4
- package/src/components/hooks/useOrchestrator.ts +1 -1
- package/src/components/hooks/usePresence.ts +45 -6
- package/src/components/hooks/useThread.ts +83 -46
- package/src/components/hooks/useTrajectory.ts +62 -5
- package/src/components/hooks/useWebSocket.test.ts +358 -0
- package/src/components/hooks/useWebSocket.ts +243 -5
- package/src/components/index.ts +2 -14
- package/src/components/layout/Header.tsx +9 -15
- package/src/components/layout/Sidebar.tsx +1 -8
- package/src/components/settings/SettingsPage.tsx +108 -47
- package/src/components/settings/index.ts +0 -3
- package/src/landing/blogData.ts +1 -1
- package/src/lib/agent-merge.test.ts +2 -2
- package/src/lib/api.ts +8 -38
- package/src/lib/identity.test.ts +139 -0
- package/src/lib/identity.ts +48 -0
- package/src/lib/relaycastMessageAdapters.test.ts +182 -0
- package/src/lib/relaycastMessageAdapters.ts +105 -0
- package/src/lib/sanitize-logs.test.ts +227 -0
- package/src/lib/sanitize-logs.ts +202 -0
- package/src/providers/AgentProvider.tsx +799 -0
- package/src/providers/ChannelProvider.tsx +528 -0
- package/src/providers/CloudWorkspaceProvider.tsx +402 -0
- package/src/providers/MessageProvider.tsx +875 -0
- package/src/providers/RelayConfigProvider.tsx +94 -0
- package/src/providers/SendProvider.tsx +497 -0
- package/src/providers/SettingsProvider.tsx +247 -0
- package/src/providers/index.ts +26 -0
- package/src/types/index.ts +10 -10
- package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
- package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
- package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
- package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
- package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
- package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
- package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
- package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
- package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
- package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
- package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
- package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
- package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
- package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
- package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
- package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
- package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
- package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
- package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
- package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
- package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
- package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
- package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
- package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
- package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
- package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
- package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
- package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
- package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
- package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
- package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
- package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
- package/src/components/BillingResult.tsx +0 -447
- package/src/components/CloudSessionProvider.tsx +0 -130
- package/src/components/SessionExpiredModal.tsx +0 -128
- package/src/components/WorkspaceStatusIndicator.tsx +0 -396
- package/src/components/hooks/useSession.ts +0 -209
- package/src/components/hooks/useWorkspaceMembers.ts +0 -132
- package/src/components/hooks/useWorkspaceStatus.ts +0 -237
- package/src/components/settings/BillingSettingsPanel.tsx +0 -564
- package/src/components/settings/TeamSettingsPanel.tsx +0 -560
- package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
- package/src/lib/cloudApi.ts +0 -893
- /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
|
@@ -23,6 +23,19 @@ export interface XTermLogViewerProps {
|
|
|
23
23
|
onClose?: () => void;
|
|
24
24
|
/** Custom class name */
|
|
25
25
|
className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Mock mode: provide lines directly instead of connecting to WebSocket.
|
|
28
|
+
* Each entry has `content` (raw PTY text) and an optional `delay` (ms)
|
|
29
|
+
* for streaming simulation.
|
|
30
|
+
*/
|
|
31
|
+
mockData?: { content: string; delay?: number }[];
|
|
32
|
+
/** When true, feed mockData lines one at a time with their delays */
|
|
33
|
+
mockStreaming?: boolean;
|
|
34
|
+
/** Legacy flag kept for compatibility; real streams are now rendered losslessly. */
|
|
35
|
+
suppressNoisyOutput?: boolean;
|
|
36
|
+
// Accept legacy/extra props for isolated test harnesses while preserving
|
|
37
|
+
// compatibility across in-progress refactors.
|
|
38
|
+
[key: string]: unknown;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
// Theme matching the dashboard dark theme
|
|
@@ -59,7 +72,11 @@ export function XTermLogViewer({
|
|
|
59
72
|
showHeader = true,
|
|
60
73
|
onClose,
|
|
61
74
|
className = '',
|
|
75
|
+
mockData,
|
|
76
|
+
mockStreaming = false,
|
|
77
|
+
suppressNoisyOutput = false,
|
|
62
78
|
}: XTermLogViewerProps) {
|
|
79
|
+
const isMockMode = !!mockData;
|
|
63
80
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
64
81
|
const terminalRef = useRef<Terminal | null>(null);
|
|
65
82
|
const fitAddonRef = useRef<FitAddon | null>(null);
|
|
@@ -67,6 +84,10 @@ export function XTermLogViewer({
|
|
|
67
84
|
const wsRef = useRef<WebSocket | null>(null);
|
|
68
85
|
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
69
86
|
const reconnectAttemptsRef = useRef(0);
|
|
87
|
+
const lastSeqRef = useRef<number | null>(null);
|
|
88
|
+
const hasConnectedBeforeRef = useRef(false);
|
|
89
|
+
const serverSupportsReplayRef = useRef(false);
|
|
90
|
+
const skipHistoryOnceRef = useRef(false);
|
|
70
91
|
|
|
71
92
|
const [isConnected, setIsConnected] = useState(false);
|
|
72
93
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
@@ -78,10 +99,47 @@ export function XTermLogViewer({
|
|
|
78
99
|
|
|
79
100
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
80
101
|
const colors = getAgentColor(agentName);
|
|
102
|
+
const shouldFilterMockOutput = Boolean(suppressNoisyOutput && isMockMode);
|
|
81
103
|
|
|
82
104
|
// Get WebSocket URL from workspace context (handles cloud vs local mode)
|
|
83
105
|
const logStreamUrl = useWorkspaceWsUrl(`/ws/logs/${encodeURIComponent(agentName)}`);
|
|
84
106
|
|
|
107
|
+
const fitTerminal = useCallback(() => {
|
|
108
|
+
const terminal = terminalRef.current;
|
|
109
|
+
const fitAddon = fitAddonRef.current;
|
|
110
|
+
if (!terminal || !fitAddon) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
fitAddon.fit();
|
|
116
|
+
if (terminal.cols < 80) {
|
|
117
|
+
terminal.resize(80, terminal.rows);
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Ignore transient layout timing issues.
|
|
121
|
+
}
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
const enqueueLine = useCallback((line: string, appendNewline = false) => {
|
|
125
|
+
const terminal = terminalRef.current;
|
|
126
|
+
if (!terminal) return;
|
|
127
|
+
|
|
128
|
+
const content = appendNewline && !line.endsWith('\n') ? `${line}\n` : line;
|
|
129
|
+
|
|
130
|
+
// Filtering is intentionally disabled for real streams to keep xterm lossless.
|
|
131
|
+
// Preserve prop plumbing so dev/mock harnesses can keep passing the flag.
|
|
132
|
+
if (shouldFilterMockOutput) {
|
|
133
|
+
// no-op: raw stream is rendered directly.
|
|
134
|
+
}
|
|
135
|
+
terminal.write(content);
|
|
136
|
+
|
|
137
|
+
const newlineCount = (content.match(/\n/g) || []).length;
|
|
138
|
+
if (newlineCount > 0) {
|
|
139
|
+
setLineCount((count) => count + newlineCount);
|
|
140
|
+
}
|
|
141
|
+
}, [shouldFilterMockOutput]);
|
|
142
|
+
|
|
85
143
|
// Initialize terminal
|
|
86
144
|
useEffect(() => {
|
|
87
145
|
if (!containerRef.current) return;
|
|
@@ -106,7 +164,13 @@ export function XTermLogViewer({
|
|
|
106
164
|
terminal.loadAddon(searchAddon);
|
|
107
165
|
|
|
108
166
|
terminal.open(containerRef.current);
|
|
109
|
-
|
|
167
|
+
fitTerminal();
|
|
168
|
+
requestAnimationFrame(() => {
|
|
169
|
+
fitTerminal();
|
|
170
|
+
requestAnimationFrame(() => {
|
|
171
|
+
fitTerminal();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
110
174
|
|
|
111
175
|
terminalRef.current = terminal;
|
|
112
176
|
fitAddonRef.current = fitAddon;
|
|
@@ -114,20 +178,28 @@ export function XTermLogViewer({
|
|
|
114
178
|
setIsTerminalReady(true);
|
|
115
179
|
|
|
116
180
|
// Handle resize
|
|
181
|
+
const scheduleFit = () => {
|
|
182
|
+
requestAnimationFrame(() => {
|
|
183
|
+
fitTerminal();
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
117
187
|
const resizeObserver = new ResizeObserver(() => {
|
|
118
|
-
|
|
188
|
+
scheduleFit();
|
|
119
189
|
});
|
|
120
190
|
resizeObserver.observe(containerRef.current);
|
|
191
|
+
window.addEventListener('resize', scheduleFit);
|
|
121
192
|
|
|
122
193
|
return () => {
|
|
123
194
|
resizeObserver.disconnect();
|
|
195
|
+
window.removeEventListener('resize', scheduleFit);
|
|
124
196
|
terminal.dispose();
|
|
125
197
|
terminalRef.current = null;
|
|
126
198
|
fitAddonRef.current = null;
|
|
127
199
|
searchAddonRef.current = null;
|
|
128
200
|
setIsTerminalReady(false);
|
|
129
201
|
};
|
|
130
|
-
}, []);
|
|
202
|
+
}, [fitTerminal]);
|
|
131
203
|
|
|
132
204
|
// Mobile touch scrolling - attach handlers to container, not viewport
|
|
133
205
|
// xterm.js renders to a canvas which intercepts events; we need to handle
|
|
@@ -211,6 +283,8 @@ export function XTermLogViewer({
|
|
|
211
283
|
|
|
212
284
|
// Connect to WebSocket
|
|
213
285
|
const connect = useCallback(() => {
|
|
286
|
+
if (isMockMode) return;
|
|
287
|
+
|
|
214
288
|
if (wsRef.current?.readyState === WebSocket.OPEN ||
|
|
215
289
|
wsRef.current?.readyState === WebSocket.CONNECTING) {
|
|
216
290
|
return;
|
|
@@ -228,13 +302,29 @@ export function XTermLogViewer({
|
|
|
228
302
|
setError(null);
|
|
229
303
|
reconnectAttemptsRef.current = 0;
|
|
230
304
|
|
|
231
|
-
|
|
305
|
+
enqueueLine(`\x1b[90m[Connected to ${agentName} log stream]\x1b[0m`, true);
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
hasConnectedBeforeRef.current &&
|
|
309
|
+
serverSupportsReplayRef.current &&
|
|
310
|
+
lastSeqRef.current !== null
|
|
311
|
+
) {
|
|
312
|
+
skipHistoryOnceRef.current = true;
|
|
313
|
+
ws.send(JSON.stringify({
|
|
314
|
+
type: 'replay',
|
|
315
|
+
agent: agentName,
|
|
316
|
+
lastSequenceId: lastSeqRef.current,
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
hasConnectedBeforeRef.current = true;
|
|
232
321
|
};
|
|
233
322
|
|
|
234
323
|
ws.onclose = (event) => {
|
|
235
324
|
setIsConnected(false);
|
|
236
325
|
setIsConnecting(false);
|
|
237
326
|
wsRef.current = null;
|
|
327
|
+
skipHistoryOnceRef.current = false;
|
|
238
328
|
|
|
239
329
|
if (reconnectTimeoutRef.current) {
|
|
240
330
|
clearTimeout(reconnectTimeoutRef.current);
|
|
@@ -243,7 +333,7 @@ export function XTermLogViewer({
|
|
|
243
333
|
|
|
244
334
|
// Don't reconnect for agent not found
|
|
245
335
|
if (event.code === 4404) {
|
|
246
|
-
|
|
336
|
+
enqueueLine(`\x1b[31m[Agent not found]\x1b[0m`);
|
|
247
337
|
return;
|
|
248
338
|
}
|
|
249
339
|
|
|
@@ -251,7 +341,7 @@ export function XTermLogViewer({
|
|
|
251
341
|
const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000);
|
|
252
342
|
reconnectAttemptsRef.current++;
|
|
253
343
|
|
|
254
|
-
|
|
344
|
+
enqueueLine(`\x1b[90m[Disconnected. Reconnecting in ${delay / 1000}s...]\x1b[0m`, true);
|
|
255
345
|
|
|
256
346
|
reconnectTimeoutRef.current = setTimeout(() => {
|
|
257
347
|
connect();
|
|
@@ -261,15 +351,28 @@ export function XTermLogViewer({
|
|
|
261
351
|
ws.onerror = () => {
|
|
262
352
|
setError(new Error('WebSocket connection error'));
|
|
263
353
|
setIsConnecting(false);
|
|
354
|
+
skipHistoryOnceRef.current = false;
|
|
264
355
|
};
|
|
265
356
|
|
|
266
357
|
ws.onmessage = (event) => {
|
|
267
358
|
try {
|
|
268
359
|
const data = JSON.parse(event.data);
|
|
269
360
|
|
|
361
|
+
if (data.type === 'sync') {
|
|
362
|
+
if (typeof data.sequenceId === 'number') {
|
|
363
|
+
serverSupportsReplayRef.current = true;
|
|
364
|
+
if (lastSeqRef.current === null) {
|
|
365
|
+
lastSeqRef.current = data.sequenceId;
|
|
366
|
+
} else {
|
|
367
|
+
lastSeqRef.current = Math.max(lastSeqRef.current, data.sequenceId);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
270
373
|
// Handle different message types
|
|
271
374
|
if (data.type === 'error') {
|
|
272
|
-
|
|
375
|
+
enqueueLine(`\x1b[31mError: ${data.error}\x1b[0m`);
|
|
273
376
|
return;
|
|
274
377
|
}
|
|
275
378
|
|
|
@@ -279,24 +382,46 @@ export function XTermLogViewer({
|
|
|
279
382
|
|
|
280
383
|
// Handle history (initial log dump)
|
|
281
384
|
if (data.type === 'history' && Array.isArray(data.lines)) {
|
|
385
|
+
if (skipHistoryOnceRef.current) {
|
|
386
|
+
skipHistoryOnceRef.current = false;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
282
389
|
data.lines.forEach((line: string) => {
|
|
283
|
-
|
|
284
|
-
setLineCount((c) => c + 1);
|
|
390
|
+
enqueueLine(line, true);
|
|
285
391
|
});
|
|
286
392
|
return;
|
|
287
393
|
}
|
|
288
394
|
|
|
395
|
+
if (data.type === 'replay') {
|
|
396
|
+
skipHistoryOnceRef.current = false;
|
|
397
|
+
if (Array.isArray(data.messages)) {
|
|
398
|
+
data.messages.forEach((entry: { seq?: number; content?: string; data?: string; message?: string }) => {
|
|
399
|
+
if (typeof entry.seq === 'number') {
|
|
400
|
+
lastSeqRef.current = Math.max(lastSeqRef.current ?? 0, entry.seq);
|
|
401
|
+
}
|
|
402
|
+
const content = entry.content || entry.data || entry.message || '';
|
|
403
|
+
if (content) {
|
|
404
|
+
enqueueLine(content, false);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
} else if (Array.isArray(data.entries)) {
|
|
408
|
+
data.entries.forEach((entry: { content?: string }) => {
|
|
409
|
+
if (entry.content) {
|
|
410
|
+
enqueueLine(entry.content, false);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
289
417
|
// Handle live output
|
|
290
418
|
if (data.type === 'log' || data.type === 'output') {
|
|
419
|
+
if (typeof data.seq === 'number') {
|
|
420
|
+
lastSeqRef.current = Math.max(lastSeqRef.current ?? 0, data.seq);
|
|
421
|
+
}
|
|
291
422
|
const content = data.content || data.data || data.message || '';
|
|
292
423
|
if (content) {
|
|
293
|
-
|
|
294
|
-
terminalRef.current?.write(content);
|
|
295
|
-
// Count newlines for line count
|
|
296
|
-
const newlines = (content.match(/\n/g) || []).length;
|
|
297
|
-
if (newlines > 0) {
|
|
298
|
-
setLineCount((c) => c + newlines);
|
|
299
|
-
}
|
|
424
|
+
enqueueLine(content, false);
|
|
300
425
|
}
|
|
301
426
|
return;
|
|
302
427
|
}
|
|
@@ -305,22 +430,17 @@ export function XTermLogViewer({
|
|
|
305
430
|
if (data.lines && Array.isArray(data.lines)) {
|
|
306
431
|
data.lines.forEach((line: string | { content: string }) => {
|
|
307
432
|
const content = typeof line === 'string' ? line : line.content;
|
|
308
|
-
|
|
309
|
-
setLineCount((c) => c + 1);
|
|
433
|
+
enqueueLine(content, true);
|
|
310
434
|
});
|
|
311
435
|
}
|
|
312
436
|
} catch {
|
|
313
437
|
// Plain text message
|
|
314
438
|
if (typeof event.data === 'string') {
|
|
315
|
-
|
|
316
|
-
const newlines = (event.data.match(/\n/g) || []).length;
|
|
317
|
-
if (newlines > 0) {
|
|
318
|
-
setLineCount((c) => c + newlines);
|
|
319
|
-
}
|
|
439
|
+
enqueueLine(event.data, false);
|
|
320
440
|
}
|
|
321
441
|
}
|
|
322
442
|
};
|
|
323
|
-
}, [logStreamUrl, agentName]);
|
|
443
|
+
}, [logStreamUrl, agentName, isMockMode, enqueueLine]);
|
|
324
444
|
|
|
325
445
|
// Disconnect from WebSocket
|
|
326
446
|
const disconnect = useCallback(() => {
|
|
@@ -366,11 +486,50 @@ export function XTermLogViewer({
|
|
|
366
486
|
|
|
367
487
|
// Auto-connect on mount
|
|
368
488
|
useEffect(() => {
|
|
489
|
+
if (isMockMode) return;
|
|
490
|
+
|
|
369
491
|
connect();
|
|
370
492
|
return () => {
|
|
371
493
|
disconnect();
|
|
372
494
|
};
|
|
373
|
-
}, [connect, disconnect]);
|
|
495
|
+
}, [connect, disconnect, isMockMode]);
|
|
496
|
+
|
|
497
|
+
// Mock mode: write fixture data directly to terminal
|
|
498
|
+
useEffect(() => {
|
|
499
|
+
if (!isMockMode || !isTerminalReady || !terminalRef.current || !mockData) return;
|
|
500
|
+
|
|
501
|
+
const terminal = terminalRef.current;
|
|
502
|
+
terminal.clear();
|
|
503
|
+
setLineCount(0);
|
|
504
|
+
let cancelled = false;
|
|
505
|
+
|
|
506
|
+
(async () => {
|
|
507
|
+
if (!mockStreaming) {
|
|
508
|
+
for (const line of mockData) {
|
|
509
|
+
if (line.content) {
|
|
510
|
+
enqueueLine(line.content, true);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for (const line of mockData) {
|
|
517
|
+
if (cancelled) break;
|
|
518
|
+
|
|
519
|
+
if (line.delay) {
|
|
520
|
+
await new Promise((resolve) => setTimeout(resolve, line.delay));
|
|
521
|
+
}
|
|
522
|
+
if (cancelled) break;
|
|
523
|
+
if (line.content) {
|
|
524
|
+
enqueueLine(line.content, true);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
})();
|
|
528
|
+
|
|
529
|
+
return () => {
|
|
530
|
+
cancelled = true;
|
|
531
|
+
};
|
|
532
|
+
}, [isMockMode, isTerminalReady, mockData, mockStreaming, enqueueLine]);
|
|
374
533
|
|
|
375
534
|
// Keyboard shortcuts
|
|
376
535
|
useEffect(() => {
|
|
@@ -416,10 +575,13 @@ export function XTermLogViewer({
|
|
|
416
575
|
<style>{`
|
|
417
576
|
.xterm-log-viewer .xterm {
|
|
418
577
|
height: 100%;
|
|
578
|
+
width: 100%;
|
|
419
579
|
}
|
|
420
580
|
.xterm-log-viewer .xterm-viewport {
|
|
421
581
|
height: 100%;
|
|
422
582
|
max-height: 100%;
|
|
583
|
+
width: 100%;
|
|
584
|
+
max-width: 100%;
|
|
423
585
|
overscroll-behavior: contain;
|
|
424
586
|
}
|
|
425
587
|
/* On touch devices, disable browser touch handling so our JS handler works */
|
|
@@ -427,7 +589,8 @@ export function XTermLogViewer({
|
|
|
427
589
|
.xterm-log-viewer .xterm,
|
|
428
590
|
.xterm-log-viewer .xterm-viewport,
|
|
429
591
|
.xterm-log-viewer .xterm-screen,
|
|
430
|
-
.xterm-log-viewer .xterm-screen canvas
|
|
592
|
+
.xterm-log-viewer .xterm-screen canvas,
|
|
593
|
+
.xterm-log-viewer .xterm-rows {
|
|
431
594
|
touch-action: none;
|
|
432
595
|
}
|
|
433
596
|
}
|
|
@@ -560,7 +723,7 @@ export function XTermLogViewer({
|
|
|
560
723
|
|
|
561
724
|
{/* Terminal container - touch handlers attached via useEffect */}
|
|
562
725
|
<div
|
|
563
|
-
className="flex-1 min-h-0 overflow-hidden"
|
|
726
|
+
className="flex-1 min-h-0 min-w-0 overflow-hidden"
|
|
564
727
|
style={{ height: maxHeight, maxHeight, minHeight: '200px' }}
|
|
565
728
|
>
|
|
566
729
|
<div
|
|
@@ -10,17 +10,36 @@
|
|
|
10
10
|
|
|
11
11
|
import React, { useRef, useEffect, useCallback, useState, useMemo } from 'react';
|
|
12
12
|
import type { ChannelMessage, ChannelMessageListProps, UnreadState } from './types';
|
|
13
|
+
import type { Reaction } from '../../types';
|
|
14
|
+
import { ReactionChips } from '../ReactionChips';
|
|
15
|
+
import { ThinkingIndicator } from '../ThinkingIndicator';
|
|
13
16
|
import { formatMessageBody } from '../utils/messageFormatting';
|
|
17
|
+
import { formatRelayReplyCountLabel } from '../../lib/relaycastMessageAdapters';
|
|
18
|
+
|
|
19
|
+
/** Convert channel `Record<string, string[]>` reactions to `Reaction[]` */
|
|
20
|
+
function channelReactionsToArray(reactions?: Record<string, string[]>): Reaction[] {
|
|
21
|
+
if (!reactions) return [];
|
|
22
|
+
return Object.entries(reactions).map(([emoji, agents]) => ({
|
|
23
|
+
emoji,
|
|
24
|
+
count: agents.length,
|
|
25
|
+
agents,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
14
28
|
|
|
15
29
|
export function ChannelMessageList({
|
|
16
30
|
messages,
|
|
17
31
|
unreadState,
|
|
18
32
|
currentUser,
|
|
33
|
+
currentUserInfo,
|
|
34
|
+
onlineUsers = [],
|
|
35
|
+
agents = [],
|
|
36
|
+
humanUsers = [],
|
|
19
37
|
isLoadingMore = false,
|
|
20
38
|
hasMore = false,
|
|
21
39
|
onLoadMore,
|
|
22
40
|
onThreadClick,
|
|
23
41
|
onMemberClick,
|
|
42
|
+
onReaction,
|
|
24
43
|
}: ChannelMessageListProps) {
|
|
25
44
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
26
45
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
@@ -146,6 +165,12 @@ export function ChannelMessageList({
|
|
|
146
165
|
isOwn={message.from === currentUser}
|
|
147
166
|
onThreadClick={onThreadClick}
|
|
148
167
|
onMemberClick={onMemberClick}
|
|
168
|
+
onReaction={onReaction}
|
|
169
|
+
currentUser={currentUser}
|
|
170
|
+
currentUserInfo={currentUserInfo}
|
|
171
|
+
onlineUsers={onlineUsers}
|
|
172
|
+
agents={agents}
|
|
173
|
+
humanUsers={humanUsers}
|
|
149
174
|
showAvatar={shouldShowAvatar(dateMessages, index)}
|
|
150
175
|
/>
|
|
151
176
|
</React.Fragment>
|
|
@@ -154,6 +179,26 @@ export function ChannelMessageList({
|
|
|
154
179
|
</div>
|
|
155
180
|
))}
|
|
156
181
|
|
|
182
|
+
{/* Thinking indicators for processing agents */}
|
|
183
|
+
{agents.filter((a) => a.isProcessing).map((agent) => (
|
|
184
|
+
<div key={`thinking-${agent.name}`} className="flex gap-3 py-1 mt-3">
|
|
185
|
+
<div className="w-9 flex-shrink-0">
|
|
186
|
+
<Avatar name={agent.name} avatarUrl={agent.avatarUrl} entityType="agent" />
|
|
187
|
+
</div>
|
|
188
|
+
<div className="flex-1 min-w-0">
|
|
189
|
+
<div className="flex items-center gap-2 mb-0.5">
|
|
190
|
+
<span className="text-sm font-semibold text-text-primary">{agent.name}</span>
|
|
191
|
+
</div>
|
|
192
|
+
<ThinkingIndicator
|
|
193
|
+
isProcessing={true}
|
|
194
|
+
processingStartedAt={agent.processingStartedAt}
|
|
195
|
+
size="small"
|
|
196
|
+
showLabel={true}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
))}
|
|
201
|
+
|
|
157
202
|
{/* Scroll anchor */}
|
|
158
203
|
<div ref={bottomRef} />
|
|
159
204
|
</div>
|
|
@@ -181,6 +226,24 @@ interface MessageItemProps {
|
|
|
181
226
|
isOwn: boolean;
|
|
182
227
|
onThreadClick?: (messageId: string) => void;
|
|
183
228
|
onMemberClick?: (memberId: string, entityType: 'user' | 'agent') => void;
|
|
229
|
+
onReaction?: (messageId: string, emoji: string, hasReacted: boolean) => void;
|
|
230
|
+
currentUser?: string;
|
|
231
|
+
currentUserInfo?: {
|
|
232
|
+
displayName: string;
|
|
233
|
+
avatarUrl?: string;
|
|
234
|
+
};
|
|
235
|
+
onlineUsers?: Array<{
|
|
236
|
+
username: string;
|
|
237
|
+
avatarUrl?: string;
|
|
238
|
+
}>;
|
|
239
|
+
agents?: Array<{
|
|
240
|
+
name: string;
|
|
241
|
+
avatarUrl?: string;
|
|
242
|
+
}>;
|
|
243
|
+
humanUsers?: Array<{
|
|
244
|
+
username: string;
|
|
245
|
+
avatarUrl?: string;
|
|
246
|
+
}>;
|
|
184
247
|
showAvatar: boolean;
|
|
185
248
|
}
|
|
186
249
|
|
|
@@ -189,9 +252,24 @@ function MessageItem({
|
|
|
189
252
|
isOwn,
|
|
190
253
|
onThreadClick,
|
|
191
254
|
onMemberClick,
|
|
255
|
+
onReaction,
|
|
256
|
+
currentUser,
|
|
257
|
+
currentUserInfo,
|
|
258
|
+
onlineUsers = [],
|
|
259
|
+
agents = [],
|
|
260
|
+
humanUsers = [],
|
|
192
261
|
showAvatar,
|
|
193
262
|
}: MessageItemProps) {
|
|
194
263
|
const hasThread = message.threadSummary && message.threadSummary.replyCount > 0;
|
|
264
|
+
const replyCount = message.threadSummary?.replyCount ?? 0;
|
|
265
|
+
const replyLabel = formatRelayReplyCountLabel(replyCount);
|
|
266
|
+
const normalizedSender = message.from.toLowerCase();
|
|
267
|
+
|
|
268
|
+
const avatarUrl = message.fromAvatarUrl
|
|
269
|
+
|| (isOwn ? currentUserInfo?.avatarUrl : undefined)
|
|
270
|
+
|| onlineUsers.find((user) => user.username.toLowerCase() === normalizedSender)?.avatarUrl
|
|
271
|
+
|| humanUsers.find((user) => user.username.toLowerCase() === normalizedSender)?.avatarUrl
|
|
272
|
+
|| agents.find((agent) => agent.name.toLowerCase() === normalizedSender)?.avatarUrl;
|
|
195
273
|
|
|
196
274
|
return (
|
|
197
275
|
<div className={`group relative py-1 ${showAvatar ? 'mt-3' : ''}`}>
|
|
@@ -201,8 +279,8 @@ function MessageItem({
|
|
|
201
279
|
{showAvatar && (
|
|
202
280
|
<Avatar
|
|
203
281
|
name={message.from}
|
|
204
|
-
avatarUrl={
|
|
205
|
-
entityType={message.fromEntityType}
|
|
282
|
+
avatarUrl={avatarUrl}
|
|
283
|
+
entityType={message.fromEntityType || 'agent'}
|
|
206
284
|
/>
|
|
207
285
|
)}
|
|
208
286
|
</div>
|
|
@@ -242,11 +320,13 @@ function MessageItem({
|
|
|
242
320
|
: 'text-text-muted bg-transparent opacity-0 group-hover:opacity-100 hover:text-accent-cyan hover:bg-accent-cyan/10'}
|
|
243
321
|
`}
|
|
244
322
|
onClick={() => onThreadClick?.(message.threadId || message.id)}
|
|
245
|
-
title={message.threadId ?
|
|
323
|
+
title={message.threadId ? 'View thread' : (hasThread ? replyLabel : 'Reply in thread')}
|
|
246
324
|
>
|
|
247
325
|
<ThreadIcon className="w-3.5 h-3.5" />
|
|
248
326
|
{hasThread && (
|
|
249
|
-
<span className="text-xs font-medium">
|
|
327
|
+
<span className="text-xs font-medium">
|
|
328
|
+
{replyLabel}
|
|
329
|
+
</span>
|
|
250
330
|
)}
|
|
251
331
|
</button>
|
|
252
332
|
</div>
|
|
@@ -265,6 +345,16 @@ function MessageItem({
|
|
|
265
345
|
))}
|
|
266
346
|
</div>
|
|
267
347
|
)}
|
|
348
|
+
|
|
349
|
+
{/* Reactions */}
|
|
350
|
+
{onReaction && (
|
|
351
|
+
<ReactionChips
|
|
352
|
+
reactions={channelReactionsToArray(message.reactions)}
|
|
353
|
+
messageId={message.id}
|
|
354
|
+
currentUser={currentUser}
|
|
355
|
+
onToggleReaction={onReaction}
|
|
356
|
+
/>
|
|
357
|
+
)}
|
|
268
358
|
</div>
|
|
269
359
|
</div>
|
|
270
360
|
</div>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Composed channel view that combines:
|
|
5
5
|
* - ChannelHeader
|
|
6
6
|
* - ChannelMessageList
|
|
7
|
-
* -
|
|
7
|
+
* - MessageComposer
|
|
8
8
|
*
|
|
9
9
|
* This is the main view component for displaying a channel's content.
|
|
10
10
|
*/
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
import React, { useCallback, useMemo } from 'react';
|
|
13
13
|
import { ChannelHeader } from './ChannelHeader';
|
|
14
14
|
import { ChannelMessageList } from './ChannelMessageList';
|
|
15
|
-
import {
|
|
15
|
+
import { MessageComposer } from '../MessageComposer';
|
|
16
|
+
import type { Agent } from '../../types';
|
|
17
|
+
import type { HumanUser } from '../MentionAutocomplete';
|
|
18
|
+
import type { UserPresence } from '../hooks/usePresence';
|
|
16
19
|
import type {
|
|
17
20
|
Channel,
|
|
18
21
|
ChannelMember,
|
|
@@ -37,12 +40,21 @@ export interface ChannelViewV1Props {
|
|
|
37
40
|
isLoadingMore?: boolean;
|
|
38
41
|
/** Whether there are more messages to load */
|
|
39
42
|
hasMoreMessages?: boolean;
|
|
40
|
-
/**
|
|
41
|
-
|
|
43
|
+
/** Agents available for @-mentions */
|
|
44
|
+
agents?: Agent[];
|
|
45
|
+
/** Human users available for @-mentions */
|
|
46
|
+
humanUsers?: HumanUser[];
|
|
47
|
+
/** Current user profile for avatar fallback */
|
|
48
|
+
currentUserInfo?: {
|
|
49
|
+
displayName: string;
|
|
50
|
+
avatarUrl?: string;
|
|
51
|
+
};
|
|
52
|
+
/** Online users for avatar fallback */
|
|
53
|
+
onlineUsers?: UserPresence[];
|
|
42
54
|
/** Callback to load more messages */
|
|
43
55
|
onLoadMore?: () => void;
|
|
44
56
|
/** Callback to send a message */
|
|
45
|
-
onSendMessage: (content: string) =>
|
|
57
|
+
onSendMessage: (content: string, attachmentIds?: string[]) => Promise<boolean>;
|
|
46
58
|
/** Callback when editing channel settings */
|
|
47
59
|
onEditChannel?: () => void;
|
|
48
60
|
/** Callback to show member list */
|
|
@@ -59,6 +71,8 @@ export interface ChannelViewV1Props {
|
|
|
59
71
|
onMarkRead?: (upToTimestamp: string) => void;
|
|
60
72
|
/** Callback when clicking on a member name (for DM navigation) */
|
|
61
73
|
onMemberClick?: (memberId: string, entityType: 'user' | 'agent') => void;
|
|
74
|
+
/** Callback when toggling a reaction on a message */
|
|
75
|
+
onReaction?: (messageId: string, emoji: string, hasReacted: boolean) => void;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
export function ChannelViewV1({
|
|
@@ -70,7 +84,10 @@ export function ChannelViewV1({
|
|
|
70
84
|
canEditChannel = false,
|
|
71
85
|
isLoadingMore = false,
|
|
72
86
|
hasMoreMessages = false,
|
|
73
|
-
|
|
87
|
+
agents = [],
|
|
88
|
+
humanUsers = [],
|
|
89
|
+
currentUserInfo,
|
|
90
|
+
onlineUsers = [],
|
|
74
91
|
onLoadMore,
|
|
75
92
|
onSendMessage,
|
|
76
93
|
onEditChannel,
|
|
@@ -81,10 +98,11 @@ export function ChannelViewV1({
|
|
|
81
98
|
onTyping,
|
|
82
99
|
onMarkRead,
|
|
83
100
|
onMemberClick,
|
|
101
|
+
onReaction,
|
|
84
102
|
}: ChannelViewV1Props) {
|
|
85
103
|
// Handle send
|
|
86
|
-
const handleSend = useCallback((content: string) => {
|
|
87
|
-
onSendMessage(content);
|
|
104
|
+
const handleSend = useCallback((content: string, attachmentIds?: string[]) => {
|
|
105
|
+
return onSendMessage(content, attachmentIds);
|
|
88
106
|
}, [onSendMessage]);
|
|
89
107
|
|
|
90
108
|
// Get placeholder text based on channel type
|
|
@@ -116,11 +134,16 @@ export function ChannelViewV1({
|
|
|
116
134
|
messages={messages}
|
|
117
135
|
unreadState={unreadState}
|
|
118
136
|
currentUser={currentUser}
|
|
137
|
+
currentUserInfo={currentUserInfo}
|
|
138
|
+
onlineUsers={onlineUsers}
|
|
139
|
+
agents={agents}
|
|
140
|
+
humanUsers={humanUsers}
|
|
119
141
|
isLoadingMore={isLoadingMore}
|
|
120
142
|
hasMore={hasMoreMessages}
|
|
121
143
|
onLoadMore={onLoadMore}
|
|
122
144
|
onThreadClick={onThreadClick}
|
|
123
145
|
onMemberClick={onMemberClick}
|
|
146
|
+
onReaction={onReaction}
|
|
124
147
|
/>
|
|
125
148
|
|
|
126
149
|
{/* Message Input */}
|
|
@@ -131,12 +154,13 @@ export function ChannelViewV1({
|
|
|
131
154
|
</p>
|
|
132
155
|
</div>
|
|
133
156
|
) : (
|
|
134
|
-
<
|
|
135
|
-
channelId={channel.id}
|
|
157
|
+
<MessageComposer
|
|
136
158
|
placeholder={inputPlaceholder}
|
|
137
159
|
onSend={handleSend}
|
|
138
160
|
onTyping={onTyping}
|
|
139
|
-
|
|
161
|
+
agents={agents}
|
|
162
|
+
humanUsers={humanUsers}
|
|
163
|
+
enableFileAutocomplete
|
|
140
164
|
/>
|
|
141
165
|
)}
|
|
142
166
|
</div>
|