@swarmclawai/swarmclaw 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -22
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +36 -7
- package/src/app/api/agents/route.ts +12 -1
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/main-loop/route.ts +7 -88
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +18 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/route.ts +16 -0
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +4 -0
- package/src/cli/index.ts +3 -10
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +207 -16
- package/src/components/agents/inspector-panel.tsx +108 -48
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/chat/chat-area.tsx +29 -13
- package/src/components/chat/chat-card.tsx +4 -20
- package/src/components/chat/chat-header.tsx +255 -353
- package/src/components/chat/chat-list.tsx +7 -9
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +3 -1
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/layout/app-layout.tsx +383 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +245 -46
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +250 -61
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +45 -5
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +946 -110
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +59 -1
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +13 -39
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +27 -967
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +17 -6
- package/src/lib/server/orchestrator.ts +2 -2
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +822 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/queue.ts +3 -20
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +70 -32
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery.ts +22 -4
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +237 -24
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +56 -1
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +150 -7
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +86 -23
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +36 -3
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/subagent.ts +193 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +896 -100
- package/src/lib/server/storage.ts +226 -7
- package/src/lib/server/stream-agent-chat.ts +46 -21
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +44 -7
- package/src/lib/server/tool-capability-policy.ts +6 -0
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +7 -0
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +0 -6
- package/src/stores/use-chat-store.ts +31 -2
- package/src/types/index.ts +287 -44
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef, useCallback, useState } from 'react'
|
|
3
|
+
import { useEffect, useRef, useCallback, useState, useMemo } from 'react'
|
|
4
4
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useWs } from '@/hooks/use-ws'
|
|
@@ -41,7 +41,6 @@ function isAgentMuted(chatroom: Chatroom, agentId: string): boolean {
|
|
|
41
41
|
|
|
42
42
|
type MomentType = { kind: 'heartbeat' } | { kind: 'tool'; name: string; input: string }
|
|
43
43
|
|
|
44
|
-
/** Subscribe to a single agent heartbeat topic — one hook call per agent */
|
|
45
44
|
function useAgentHeartbeat(agentId: string, onPulse: (id: string) => void) {
|
|
46
45
|
const topic = agentId ? `heartbeat:agent:${agentId}` : ''
|
|
47
46
|
const onPulseRef = useRef(onPulse)
|
|
@@ -51,7 +50,6 @@ function useAgentHeartbeat(agentId: string, onPulse: (id: string) => void) {
|
|
|
51
50
|
useWs(topic, () => onPulseRef.current(agentId))
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
/** Subscribes up to 6 member agents to heartbeat topics */
|
|
55
53
|
function AgentHeartbeatListeners({ agentIds, onPulse }: { agentIds: string[]; onPulse: (id: string) => void }) {
|
|
56
54
|
useAgentHeartbeat(agentIds[0] || '', onPulse)
|
|
57
55
|
useAgentHeartbeat(agentIds[1] || '', onPulse)
|
|
@@ -62,7 +60,7 @@ function AgentHeartbeatListeners({ agentIds, onPulse }: { agentIds: string[]; on
|
|
|
62
60
|
return null
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
const GROUP_THRESHOLD_MS = 2 * 60 * 1000
|
|
63
|
+
const GROUP_THRESHOLD_MS = 2 * 60 * 1000
|
|
66
64
|
|
|
67
65
|
function dayLabel(ts: number): string {
|
|
68
66
|
const d = new Date(ts)
|
|
@@ -92,10 +90,11 @@ export function ChatroomView() {
|
|
|
92
90
|
const unmuteAgent = useChatroomStore((s) => s.unmuteAgent)
|
|
93
91
|
const setMemberRole = useChatroomStore((s) => s.setMemberRole)
|
|
94
92
|
const agents = useAppStore((s) => s.agents) as Record<string, Agent>
|
|
93
|
+
const lastReadTimestamps = useAppStore((s) => s.lastReadTimestamps)
|
|
94
|
+
const markChatRead = useAppStore((s) => s.markChatRead)
|
|
95
95
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
96
96
|
const [pinsExpanded, setPinsExpanded] = useState(false)
|
|
97
|
-
|
|
98
|
-
// Per-agent moment overlays (heartbeat or tool events)
|
|
97
|
+
const [isNearBottom, setIsNearBottom] = useState(true)
|
|
99
98
|
const [agentMoments, setAgentMoments] = useState<Record<string, MomentType>>({})
|
|
100
99
|
|
|
101
100
|
const handleHeartbeatPulse = useCallback((agentId: string) => {
|
|
@@ -111,13 +110,11 @@ export function ChatroomView() {
|
|
|
111
110
|
}, [])
|
|
112
111
|
|
|
113
112
|
const chatroom = currentChatroomId ? (chatrooms[currentChatroomId] as Chatroom | undefined) : null
|
|
114
|
-
|
|
115
|
-
// Detect notable tool events from chatroom messages
|
|
116
113
|
const chatroomMessages = chatroom?.messages
|
|
117
114
|
const prevToolKeysRef = useRef<Record<string, string>>({})
|
|
115
|
+
|
|
118
116
|
useEffect(() => {
|
|
119
117
|
if (!chatroomMessages?.length) return
|
|
120
|
-
// Find the last message from each agent and check for notable tools
|
|
121
118
|
const lastByAgent = new Map<string, ChatroomMessage>()
|
|
122
119
|
for (const msg of chatroomMessages) {
|
|
123
120
|
if (msg.senderId !== 'user' && msg.senderId !== 'system') {
|
|
@@ -142,32 +139,72 @@ export function ChatroomView() {
|
|
|
142
139
|
|
|
143
140
|
const refreshChatroom = useCallback(() => {
|
|
144
141
|
loadChatrooms()
|
|
145
|
-
|
|
146
|
-
}, [])
|
|
142
|
+
}, [loadChatrooms])
|
|
147
143
|
|
|
148
144
|
useWs(currentChatroomId ? `chatroom:${currentChatroomId}` : '', refreshChatroom)
|
|
149
145
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
146
|
+
const memberAgents = useMemo(() => (
|
|
147
|
+
chatroom
|
|
148
|
+
? (chatroom.agentIds.map((id) => agents[id]).filter(Boolean) as Agent[])
|
|
149
|
+
: []
|
|
150
|
+
), [agents, chatroom])
|
|
151
|
+
|
|
152
|
+
const streamingAgentIds = useMemo(() => new Set(streamingAgents.keys()), [streamingAgents])
|
|
153
|
+
const pinnedIds = chatroom?.pinnedMessageIds || []
|
|
154
|
+
const pinnedMessages = useMemo(() => (
|
|
155
|
+
chatroom
|
|
156
|
+
? (pinnedIds.map((pid) => chatroom.messages.find((m) => m.id === pid)).filter(Boolean) as ChatroomMessage[])
|
|
157
|
+
: []
|
|
158
|
+
), [chatroom, pinnedIds])
|
|
159
|
+
const memberAgentIds = chatroom?.agentIds.slice(0, 6) || []
|
|
160
|
+
const mutedCount = chatroom ? chatroom.agentIds.filter((agentId) => isAgentMuted(chatroom, agentId)).length : 0
|
|
161
|
+
const adminCount = chatroom ? chatroom.agentIds.filter((agentId) => getMemberRole(chatroom, agentId) === 'admin').length : 0
|
|
162
|
+
const lastReadAt = chatroom ? (lastReadTimestamps[chatroom.id] || 0) : 0
|
|
163
|
+
const unreadCount = useMemo(() => (
|
|
164
|
+
chatroom
|
|
165
|
+
? chatroom.messages.filter((msg) => msg.senderId !== 'user' && msg.senderId !== 'system' && (msg.time || 0) > lastReadAt).length
|
|
166
|
+
: 0
|
|
167
|
+
), [chatroom, lastReadAt])
|
|
168
|
+
|
|
169
|
+
const focusMessage = useCallback((messageId: string) => {
|
|
170
|
+
const el = document.getElementById(`chatroom-msg-${messageId}`)
|
|
171
|
+
if (el) {
|
|
172
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
173
|
+
el.classList.add('bg-accent-soft/20')
|
|
174
|
+
setTimeout(() => el.classList.remove('bg-accent-soft/20'), 2000)
|
|
154
175
|
}
|
|
155
|
-
}, [
|
|
176
|
+
}, [])
|
|
156
177
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
const scrollToLatest = useCallback((behavior: ScrollBehavior = 'smooth') => {
|
|
179
|
+
const node = scrollRef.current
|
|
180
|
+
if (!node || !chatroom) return
|
|
181
|
+
node.scrollTo({ top: node.scrollHeight, behavior })
|
|
182
|
+
markChatRead(chatroom.id)
|
|
183
|
+
}, [chatroom, markChatRead])
|
|
162
184
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
: []
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!chatroom) return
|
|
187
|
+
markChatRead(chatroom.id)
|
|
188
|
+
}, [chatroom?.id, markChatRead])
|
|
168
189
|
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const node = scrollRef.current
|
|
192
|
+
if (!node || !chatroom) return
|
|
193
|
+
const handleScroll = () => {
|
|
194
|
+
const nearBottom = node.scrollHeight - node.scrollTop - node.clientHeight < 120
|
|
195
|
+
setIsNearBottom(nearBottom)
|
|
196
|
+
if (nearBottom) markChatRead(chatroom.id)
|
|
197
|
+
}
|
|
198
|
+
handleScroll()
|
|
199
|
+
node.addEventListener('scroll', handleScroll)
|
|
200
|
+
return () => node.removeEventListener('scroll', handleScroll)
|
|
201
|
+
}, [chatroom?.id, markChatRead])
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
if (chatroom && isNearBottom) {
|
|
205
|
+
scrollToLatest(chatroom.messages.length <= 1 ? 'auto' : 'smooth')
|
|
206
|
+
}
|
|
207
|
+
}, [chatroom, isNearBottom, scrollToLatest, streamingAgents.size])
|
|
171
208
|
|
|
172
209
|
if (!chatroom) {
|
|
173
210
|
return (
|
|
@@ -185,7 +222,6 @@ export function ChatroomView() {
|
|
|
185
222
|
}
|
|
186
223
|
|
|
187
224
|
const handleTransfer = (messageId: string, targetAgentId: string) => {
|
|
188
|
-
if (!chatroom) return
|
|
189
225
|
const msg = chatroom.messages.find((m) => m.id === messageId)
|
|
190
226
|
const targetAgent = agents[targetAgentId]
|
|
191
227
|
if (!msg || !targetAgent) return
|
|
@@ -194,196 +230,302 @@ export function ChatroomView() {
|
|
|
194
230
|
}
|
|
195
231
|
|
|
196
232
|
return (
|
|
197
|
-
<div className="flex-1 flex
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
233
|
+
<div className="flex-1 flex min-h-0 min-w-0">
|
|
234
|
+
<div className="min-w-0 flex-1 flex flex-col h-full">
|
|
235
|
+
<div className="flex items-center gap-3 px-4 py-3 border-b border-white/[0.06] shrink-0">
|
|
236
|
+
<div className="w-8 h-8 rounded-full bg-accent-soft flex items-center justify-center shrink-0">
|
|
237
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-accent-bright">
|
|
238
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
239
|
+
</svg>
|
|
240
|
+
</div>
|
|
241
|
+
<div className="flex-1 min-w-0">
|
|
242
|
+
<h3 className="text-[14px] font-700 text-text truncate">{chatroom.name}</h3>
|
|
243
|
+
<div className="flex flex-wrap items-center gap-2 mt-1">
|
|
244
|
+
<p className="text-[11px] text-text-3 truncate">
|
|
245
|
+
{memberAgents.length} agent{memberAgents.length !== 1 ? 's' : ''}
|
|
246
|
+
{chatroom.description ? ` · ${chatroom.description}` : ''}
|
|
247
|
+
</p>
|
|
248
|
+
<span className="px-1.5 py-0.5 rounded-[5px] bg-white/[0.04] text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/70">
|
|
249
|
+
{chatroom.chatMode === 'parallel' ? 'Parallel' : 'Sequential'}
|
|
250
|
+
</span>
|
|
251
|
+
<span className={`px-1.5 py-0.5 rounded-[5px] text-[10px] font-700 uppercase tracking-[0.08em] ${
|
|
252
|
+
chatroom.autoAddress ? 'bg-emerald-500/10 text-emerald-400' : 'bg-white/[0.04] text-text-3/70'
|
|
253
|
+
}`}>
|
|
254
|
+
Auto-address {chatroom.autoAddress ? 'on' : 'off'}
|
|
255
|
+
</span>
|
|
256
|
+
{streamingAgents.size > 0 && (
|
|
257
|
+
<span className="px-1.5 py-0.5 rounded-[5px] bg-sky-500/10 text-[10px] font-700 uppercase tracking-[0.08em] text-sky-400">
|
|
258
|
+
{streamingAgents.size} active now
|
|
259
|
+
</span>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div className="flex -space-x-1.5 shrink-0">
|
|
265
|
+
{memberAgents.slice(0, 5).map((agent) => {
|
|
266
|
+
const role = getMemberRole(chatroom, agent.id)
|
|
267
|
+
const badge = getRoleBadge(role)
|
|
268
|
+
const muted = isAgentMuted(chatroom, agent.id)
|
|
269
|
+
return (
|
|
270
|
+
<Tooltip key={agent.id}>
|
|
271
|
+
<TooltipTrigger asChild>
|
|
272
|
+
<button
|
|
273
|
+
onClick={() => navigateToAgent(agent.id)}
|
|
274
|
+
className={`relative transition-all duration-200 hover:scale-110 hover:z-10 hover:-translate-y-0.5 cursor-pointer bg-transparent border-none p-0 ${muted ? 'opacity-40' : ''}`}
|
|
275
|
+
>
|
|
276
|
+
<AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={22} status={streamingAgents.has(agent.id) ? 'busy' : 'online'} />
|
|
277
|
+
{badge && (
|
|
278
|
+
<span className={`absolute -bottom-1 -right-1 text-[7px] font-700 px-0.5 rounded border ${badge.className}`}>
|
|
279
|
+
{badge.label[0]}
|
|
280
|
+
</span>
|
|
281
|
+
)}
|
|
282
|
+
</button>
|
|
283
|
+
</TooltipTrigger>
|
|
284
|
+
<TooltipContent side="bottom" sideOffset={6}>
|
|
285
|
+
<div className="flex items-center gap-1.5">
|
|
286
|
+
<span>{agent.name}</span>
|
|
287
|
+
{badge && <span className={`text-[9px] font-600 px-1 py-0.5 rounded border ${badge.className}`}>{badge.label}</span>}
|
|
288
|
+
{muted && <span className="text-[9px] text-red-400">Muted</span>}
|
|
289
|
+
</div>
|
|
290
|
+
</TooltipContent>
|
|
291
|
+
</Tooltip>
|
|
292
|
+
)
|
|
293
|
+
})}
|
|
294
|
+
{memberAgents.length > 5 && (
|
|
295
|
+
<div className="w-[22px] h-[22px] rounded-full bg-white/[0.08] flex items-center justify-center text-[9px] text-text-3">
|
|
296
|
+
+{memberAgents.length - 5}
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<button
|
|
302
|
+
onClick={() => {
|
|
303
|
+
setEditingChatroomId(chatroom.id)
|
|
304
|
+
setChatroomSheetOpen(true)
|
|
305
|
+
}}
|
|
306
|
+
className="shrink-0 w-7 h-7 rounded-full flex items-center justify-center hover:bg-white/[0.08] transition-all cursor-pointer"
|
|
307
|
+
>
|
|
308
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3">
|
|
309
|
+
<circle cx="12" cy="12" r="3" />
|
|
310
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
311
|
+
</svg>
|
|
312
|
+
</button>
|
|
211
313
|
</div>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
314
|
+
|
|
315
|
+
{pinnedMessages.length > 0 && (
|
|
316
|
+
<div className="border-b border-white/[0.06] shrink-0">
|
|
317
|
+
<button
|
|
318
|
+
onClick={() => setPinsExpanded(!pinsExpanded)}
|
|
319
|
+
className="w-full flex items-center gap-2 px-4 py-2 hover:bg-white/[0.02] transition-colors cursor-pointer bg-transparent border-none text-left"
|
|
320
|
+
style={{ fontFamily: 'inherit' }}
|
|
321
|
+
>
|
|
322
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-amber-400 shrink-0">
|
|
323
|
+
<path d="M12 17v5" />
|
|
324
|
+
<path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16h14v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 2-2H6a2 2 0 0 0 2 2 1 1 0 0 1 1 1z" />
|
|
325
|
+
</svg>
|
|
326
|
+
<span className="text-[12px] font-500 text-text-2">{pinnedMessages.length} pinned</span>
|
|
327
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className={`text-text-3 transition-transform ${pinsExpanded ? 'rotate-180' : ''}`}>
|
|
328
|
+
<polyline points="6 9 12 15 18 9" />
|
|
329
|
+
</svg>
|
|
330
|
+
</button>
|
|
331
|
+
{pinsExpanded && (
|
|
332
|
+
<div className="px-4 pb-2 flex flex-col gap-1">
|
|
333
|
+
{pinnedMessages.map((message) => (
|
|
221
334
|
<button
|
|
222
|
-
|
|
223
|
-
|
|
335
|
+
key={message.id}
|
|
336
|
+
onClick={() => focusMessage(message.id)}
|
|
337
|
+
className="flex items-center gap-2 px-2 py-1.5 rounded-[8px] hover:bg-white/[0.04] transition-colors cursor-pointer bg-transparent border-none text-left w-full"
|
|
338
|
+
style={{ fontFamily: 'inherit' }}
|
|
224
339
|
>
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
<span className={`absolute -bottom-1 -right-1 text-[7px] font-700 px-0.5 rounded border ${badge.className}`}>
|
|
228
|
-
{badge.label[0]}
|
|
229
|
-
</span>
|
|
230
|
-
)}
|
|
340
|
+
<span className="text-[11px] font-600 text-accent-bright shrink-0">{message.senderName}</span>
|
|
341
|
+
<span className="text-[11px] text-text-3 truncate flex-1">{message.text.slice(0, 80)}</span>
|
|
231
342
|
</button>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
343
|
+
))}
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
|
|
349
|
+
<AgentHeartbeatListeners agentIds={memberAgentIds} onPulse={handleHeartbeatPulse} />
|
|
350
|
+
|
|
351
|
+
<div className="relative flex-1 min-h-0">
|
|
352
|
+
<div ref={scrollRef} className="absolute inset-0 overflow-y-auto py-3">
|
|
353
|
+
{chatroom.messages.length === 0 ? (
|
|
354
|
+
<div className="flex items-center justify-center h-full px-6">
|
|
355
|
+
<div className="text-center">
|
|
356
|
+
<p className="text-[13px] text-text-3 mb-1">No messages yet</p>
|
|
357
|
+
<p className="text-[12px] text-text-3/60">Use @AgentName to mention specific agents, or @all for everyone</p>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
) : (
|
|
361
|
+
chatroom.messages.map((msg, i) => {
|
|
362
|
+
const prev = i > 0 ? chatroom.messages[i - 1] : null
|
|
363
|
+
const isGrouped = prev
|
|
364
|
+
? prev.senderId === msg.senderId && (msg.time - prev.time) < GROUP_THRESHOLD_MS
|
|
365
|
+
: false
|
|
366
|
+
const prevDay = prev ? new Date(prev.time).toDateString() : null
|
|
367
|
+
const msgDay = new Date(msg.time).toDateString()
|
|
368
|
+
const showDaySep = !prev || prevDay !== msgDay
|
|
369
|
+
|
|
370
|
+
const senderId = msg.senderId
|
|
371
|
+
const moment = agentMoments[senderId]
|
|
372
|
+
const isLastFromSender = !chatroom.messages.slice(i + 1).some((m) => m.senderId === senderId)
|
|
373
|
+
let momentOverlay: React.ReactNode = null
|
|
374
|
+
if (moment && isLastFromSender && senderId !== 'user' && senderId !== 'system') {
|
|
375
|
+
if (moment.kind === 'heartbeat') {
|
|
376
|
+
momentOverlay = <HeartbeatMoment onDismiss={() => clearAgentMoment(senderId)} />
|
|
377
|
+
} else {
|
|
378
|
+
momentOverlay = (
|
|
379
|
+
<ActivityMoment
|
|
380
|
+
key={`${moment.name}-${senderId}`}
|
|
381
|
+
toolName={moment.name}
|
|
382
|
+
toolInput={moment.input}
|
|
383
|
+
onDismiss={() => clearAgentMoment(senderId)}
|
|
384
|
+
/>
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<div key={msg.id}>
|
|
391
|
+
{showDaySep && (
|
|
392
|
+
<div className="flex items-center gap-3 px-4 py-3">
|
|
393
|
+
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
394
|
+
<span className="text-[10px] font-600 text-text-3 uppercase tracking-wider">{dayLabel(msg.time)}</span>
|
|
395
|
+
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
396
|
+
</div>
|
|
397
|
+
)}
|
|
398
|
+
<ChatroomMessageBubble
|
|
399
|
+
message={msg}
|
|
400
|
+
agents={agents}
|
|
401
|
+
onToggleReaction={toggleReaction}
|
|
402
|
+
onReply={(message: ChatroomMessage) => setReplyingTo(message)}
|
|
403
|
+
onTogglePin={togglePin}
|
|
404
|
+
onTransfer={handleTransfer}
|
|
405
|
+
onDeleteMessage={(messageId, targetAgentId) => deleteMessage(messageId, targetAgentId)}
|
|
406
|
+
onMuteAgent={(agentId) => muteAgent(agentId)}
|
|
407
|
+
onUnmuteAgent={(agentId) => unmuteAgent(agentId)}
|
|
408
|
+
onSetRole={(agentId, role) => setMemberRole(agentId, role)}
|
|
409
|
+
chatroom={chatroom}
|
|
410
|
+
pinnedMessageIds={pinnedIds}
|
|
411
|
+
streamingAgentIds={streamingAgentIds}
|
|
412
|
+
messages={chatroom.messages}
|
|
413
|
+
grouped={isGrouped && !showDaySep}
|
|
414
|
+
momentOverlay={momentOverlay}
|
|
415
|
+
/>
|
|
238
416
|
</div>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
417
|
+
)
|
|
418
|
+
})
|
|
419
|
+
)}
|
|
420
|
+
<ChatroomTypingBar streamingAgents={streamingAgents} />
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
{(!isNearBottom || unreadCount > 0) && (
|
|
424
|
+
<button
|
|
425
|
+
onClick={() => scrollToLatest('smooth')}
|
|
426
|
+
className="absolute bottom-4 right-4 px-3.5 py-2 rounded-[10px] bg-surface-2/95 backdrop-blur-xl border border-white/[0.1] text-[12px] font-700 text-text shadow-[0_8px_30px_rgba(0,0,0,0.4)] hover:bg-white/[0.08] transition-all cursor-pointer"
|
|
427
|
+
style={{ fontFamily: 'inherit' }}
|
|
428
|
+
>
|
|
429
|
+
Jump to latest{unreadCount > 0 ? ` · ${unreadCount} new` : ''}
|
|
430
|
+
</button>
|
|
247
431
|
)}
|
|
248
432
|
</div>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
>
|
|
256
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3">
|
|
257
|
-
<circle cx="12" cy="12" r="3" />
|
|
258
|
-
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
259
|
-
</svg>
|
|
260
|
-
</button>
|
|
433
|
+
|
|
434
|
+
<ChatroomInput
|
|
435
|
+
agents={memberAgents}
|
|
436
|
+
onSend={sendMessage}
|
|
437
|
+
disabled={streaming}
|
|
438
|
+
/>
|
|
261
439
|
</div>
|
|
262
440
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
<div
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
key={pm.id}
|
|
285
|
-
onClick={() => {
|
|
286
|
-
const el = document.getElementById(`chatroom-msg-${pm.id}`)
|
|
287
|
-
if (el) {
|
|
288
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
289
|
-
el.classList.add('bg-accent-soft/20')
|
|
290
|
-
setTimeout(() => el.classList.remove('bg-accent-soft/20'), 2000)
|
|
291
|
-
}
|
|
292
|
-
}}
|
|
293
|
-
className="flex items-center gap-2 px-2 py-1.5 rounded-[8px] hover:bg-white/[0.04] transition-colors cursor-pointer bg-transparent border-none text-left w-full"
|
|
294
|
-
style={{ fontFamily: 'inherit' }}
|
|
295
|
-
>
|
|
296
|
-
<span className="text-[11px] font-600 text-accent-bright shrink-0">{pm.senderName}</span>
|
|
297
|
-
<span className="text-[11px] text-text-3 truncate flex-1">{pm.text.slice(0, 80)}</span>
|
|
298
|
-
</button>
|
|
299
|
-
))}
|
|
300
|
-
</div>
|
|
301
|
-
)}
|
|
441
|
+
<aside className="hidden xl:flex xl:w-[300px] xl:flex-col xl:border-l xl:border-white/[0.06] bg-surface/30">
|
|
442
|
+
<div className="px-4 py-4 border-b border-white/[0.06]">
|
|
443
|
+
<h3 className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Room Status</h3>
|
|
444
|
+
<div className="grid grid-cols-2 gap-2 mt-3">
|
|
445
|
+
{[
|
|
446
|
+
{ label: 'Members', value: String(memberAgents.length), tone: 'text-text' },
|
|
447
|
+
{ label: 'Active', value: String(streamingAgents.size), tone: 'text-sky-400' },
|
|
448
|
+
{ label: 'Pinned', value: String(pinnedMessages.length), tone: 'text-amber-400' },
|
|
449
|
+
{ label: 'Muted', value: String(mutedCount), tone: 'text-rose-400' },
|
|
450
|
+
].map((item) => (
|
|
451
|
+
<div key={item.label} className="rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2.5">
|
|
452
|
+
<div className={`text-[18px] font-display font-700 tracking-[-0.02em] ${item.tone}`}>{item.value}</div>
|
|
453
|
+
<div className="text-[10px] text-text-3/50 uppercase tracking-[0.08em] mt-0.5">{item.label}</div>
|
|
454
|
+
</div>
|
|
455
|
+
))}
|
|
456
|
+
</div>
|
|
457
|
+
<div className="mt-3 space-y-1 text-[11px] text-text-3/65">
|
|
458
|
+
<div>Mode: {chatroom.chatMode === 'parallel' ? 'Parallel replies' : 'Sequential replies'}</div>
|
|
459
|
+
<div>Auto-address: {chatroom.autoAddress ? 'Enabled' : 'Off'}</div>
|
|
460
|
+
<div>Admins: {adminCount}</div>
|
|
461
|
+
</div>
|
|
302
462
|
</div>
|
|
303
|
-
)}
|
|
304
|
-
|
|
305
|
-
<AgentHeartbeatListeners agentIds={memberAgentIds} onPulse={handleHeartbeatPulse} />
|
|
306
463
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
<p className="text-[13px] text-text-3 mb-1">No messages yet</p>
|
|
313
|
-
<p className="text-[12px] text-text-3/60">Use @AgentName to mention specific agents, or @all for everyone</p>
|
|
464
|
+
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
|
|
465
|
+
<section>
|
|
466
|
+
<div className="flex items-center justify-between mb-2">
|
|
467
|
+
<h4 className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Members</h4>
|
|
468
|
+
<span className="text-[11px] text-text-3/40">{memberAgents.length}</span>
|
|
314
469
|
</div>
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
470
|
+
<div className="space-y-2">
|
|
471
|
+
{memberAgents.map((agent) => {
|
|
472
|
+
const role = getMemberRole(chatroom, agent.id)
|
|
473
|
+
const muted = isAgentMuted(chatroom, agent.id)
|
|
474
|
+
return (
|
|
475
|
+
<button
|
|
476
|
+
key={agent.id}
|
|
477
|
+
onClick={() => navigateToAgent(agent.id)}
|
|
478
|
+
className="w-full flex items-center gap-3 rounded-[12px] border border-white/[0.06] bg-white/[0.02] px-3 py-2.5 text-left hover:bg-white/[0.05] transition-all cursor-pointer"
|
|
479
|
+
style={{ fontFamily: 'inherit' }}
|
|
480
|
+
>
|
|
481
|
+
<AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={26} status={streamingAgents.has(agent.id) ? 'busy' : 'online'} />
|
|
482
|
+
<div className="min-w-0 flex-1">
|
|
483
|
+
<div className="text-[12px] font-600 text-text truncate">{agent.name}</div>
|
|
484
|
+
<div className="flex flex-wrap gap-1.5 mt-1">
|
|
485
|
+
<span className="px-1.5 py-0.5 rounded-[5px] bg-white/[0.04] text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/70">
|
|
486
|
+
{role}
|
|
487
|
+
</span>
|
|
488
|
+
{muted && (
|
|
489
|
+
<span className="px-1.5 py-0.5 rounded-[5px] bg-rose-500/10 text-[10px] font-700 uppercase tracking-[0.08em] text-rose-400">
|
|
490
|
+
Muted
|
|
491
|
+
</span>
|
|
492
|
+
)}
|
|
493
|
+
{streamingAgents.has(agent.id) && (
|
|
494
|
+
<span className="px-1.5 py-0.5 rounded-[5px] bg-sky-500/10 text-[10px] font-700 uppercase tracking-[0.08em] text-sky-400">
|
|
495
|
+
Active
|
|
496
|
+
</span>
|
|
497
|
+
)}
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
</button>
|
|
343
501
|
)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return (
|
|
348
|
-
<div key={msg.id}>
|
|
349
|
-
{showDaySep && (
|
|
350
|
-
<div className="flex items-center gap-3 px-4 py-3">
|
|
351
|
-
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
352
|
-
<span className="text-[10px] font-600 text-text-3 uppercase tracking-wider">{dayLabel(msg.time)}</span>
|
|
353
|
-
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
354
|
-
</div>
|
|
355
|
-
)}
|
|
356
|
-
<ChatroomMessageBubble
|
|
357
|
-
message={msg}
|
|
358
|
-
agents={agents}
|
|
359
|
-
onToggleReaction={toggleReaction}
|
|
360
|
-
onReply={(m: ChatroomMessage) => setReplyingTo(m)}
|
|
361
|
-
onTogglePin={togglePin}
|
|
362
|
-
onTransfer={handleTransfer}
|
|
363
|
-
onDeleteMessage={(messageId, targetAgentId) => deleteMessage(messageId, targetAgentId)}
|
|
364
|
-
onMuteAgent={(agentId) => muteAgent(agentId)}
|
|
365
|
-
onUnmuteAgent={(agentId) => unmuteAgent(agentId)}
|
|
366
|
-
onSetRole={(agentId, role) => setMemberRole(agentId, role)}
|
|
367
|
-
chatroom={chatroom}
|
|
368
|
-
pinnedMessageIds={pinnedIds}
|
|
369
|
-
streamingAgentIds={streamingAgentIds}
|
|
370
|
-
messages={chatroom.messages}
|
|
371
|
-
grouped={isGrouped && !showDaySep}
|
|
372
|
-
momentOverlay={momentOverlay}
|
|
373
|
-
/>
|
|
374
|
-
</div>
|
|
375
|
-
)
|
|
376
|
-
})
|
|
377
|
-
)}
|
|
378
|
-
<ChatroomTypingBar streamingAgents={streamingAgents} />
|
|
379
|
-
</div>
|
|
502
|
+
})}
|
|
503
|
+
</div>
|
|
504
|
+
</section>
|
|
380
505
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
506
|
+
{pinnedMessages.length > 0 && (
|
|
507
|
+
<section>
|
|
508
|
+
<div className="flex items-center justify-between mb-2">
|
|
509
|
+
<h4 className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Pinned</h4>
|
|
510
|
+
<span className="text-[11px] text-text-3/40">{pinnedMessages.length}</span>
|
|
511
|
+
</div>
|
|
512
|
+
<div className="space-y-2">
|
|
513
|
+
{pinnedMessages.slice(0, 4).map((message) => (
|
|
514
|
+
<button
|
|
515
|
+
key={message.id}
|
|
516
|
+
onClick={() => focusMessage(message.id)}
|
|
517
|
+
className="w-full rounded-[12px] border border-white/[0.06] bg-white/[0.02] px-3 py-2.5 text-left hover:bg-white/[0.05] transition-all cursor-pointer"
|
|
518
|
+
style={{ fontFamily: 'inherit' }}
|
|
519
|
+
>
|
|
520
|
+
<div className="text-[11px] font-700 text-accent-bright">{message.senderName}</div>
|
|
521
|
+
<div className="text-[12px] text-text-3 mt-1 line-clamp-2">{message.text}</div>
|
|
522
|
+
</button>
|
|
523
|
+
))}
|
|
524
|
+
</div>
|
|
525
|
+
</section>
|
|
526
|
+
)}
|
|
527
|
+
</div>
|
|
528
|
+
</aside>
|
|
387
529
|
</div>
|
|
388
530
|
)
|
|
389
531
|
}
|