Просмотр исходного кода

支持群聊添加远程节点智能体

LSamien 2 месяцев назад
Родитель
Сommit
7368c5d6aa
3 измененных файлов с 47 добавлено и 3 удалено
  1. 9 1
      src/client/stores/groups.ts
  2. 16 2
      src/client/views/GroupsView.vue
  3. 22 0
      tests/client/groupHost.test.ts

+ 9 - 1
src/client/stores/groups.ts

@@ -139,6 +139,14 @@ export const useGroupsStore = defineStore('groups', () => {
     pinnedTopics.value = (await getPinnedGroupTopics()).items
   }
 
+  async function refreshNodes(): Promise<void> {
+    if (!capabilities.value?.features?.includes('nodeRegistry')) {
+      nodes.value = []
+      return
+    }
+    nodes.value = await getGroupNodes()
+  }
+
   async function connectEvents(anchor: GroupCapabilities, expectedGeneration: number): Promise<void> {
     await socket.connect({
       epoch: anchor.journalEpoch,
@@ -749,7 +757,7 @@ export const useGroupsStore = defineStore('groups', () => {
     availability, capabilities, topicProtocol, hostProtocol, hostFlowProtocol, roomInstructionsProtocol, activityProtocol, rooms, selectedRoomId, selectedRoom, topics, topicsForRoom, loadRoomTopics, selectedTopicId, selectedTopic,
     messages, agents, pendingInteractions,
     connectionState, isLoading, isSending, error, hasMoreBefore,
-    pinnedTopics, nodes, start, stop, refresh, selectRoom, selectTopic, startNewTopic, createRoom, updateRoom, archiveRoom, restoreRoom, archiveTopic, restoreTopic, setTopicPinned, renameTopic, loadPinnedTopics, archivedRooms, archivedTopics, addAgent, updateAgent,
+    pinnedTopics, nodes, start, stop, refresh, refreshNodes, selectRoom, selectTopic, startNewTopic, createRoom, updateRoom, archiveRoom, restoreRoom, archiveTopic, restoreTopic, setTopicPinned, renameTopic, loadPinnedTopics, archivedRooms, archivedTopics, addAgent, updateAgent,
     removeAgent, sendMessage, loadOlder, interruptAgent, approveInteraction, clarifyInteraction,
   }
 })

+ 16 - 2
src/client/views/GroupsView.vue

@@ -284,6 +284,20 @@ async function openRoomManager(id: string) {
   if (topicFromSidebarItemId(id)) return
   await selectRoom(id)
   managerOpen.value = true
+  try { await groups.refreshNodes() }
+  catch { /* Keep the current node list when refreshing is unavailable. */ }
+}
+
+async function openSelectedRoomManager() {
+  managerOpen.value = true
+  try { await groups.refreshNodes() }
+  catch { /* Keep the current node list when refreshing is unavailable. */ }
+}
+
+async function openCreateGroup() {
+  try { await groups.refreshNodes() }
+  catch { /* A node refresh must not prevent creating a local-only group. */ }
+  createOpen.value = true
 }
 
 function openRoomActions(roomId: string, event: MouseEvent) {
@@ -587,7 +601,7 @@ watch(() => auth.activeProfile?.name, profile => { if (profile) restoreShowThink
 <template>
   <WorkspaceView sidebar-title="群聊" :sidebar-context-title="topicListRoom?.name" :sidebar-subtitle="sidebarSubtitle" :inspector-open="managerOpen && !!room" inspector-close-label="关闭群聊管理" @close-inspector="managerOpen = false">
     <template #sidebar-action>
-      <button class="sidebar-primary-action" type="button" :disabled="groups.availability !== 'available'" title="新建群聊" aria-label="新建群聊" @click="createOpen = true">
+      <button class="sidebar-primary-action" type="button" :disabled="groups.availability !== 'available'" title="新建群聊" aria-label="新建群聊" @click="openCreateGroup">
         <YaoYaoSidebarIcon name="add" />
         <span>新建群聊</span>
       </button>
@@ -649,7 +663,7 @@ watch(() => auth.activeProfile?.name, profile => { if (profile) restoreShowThink
           <div class="group-header-actions">
             <span v-if="hostAgent" class="group-host-chip" :title="`用户未明确 @ 时由 ${hostAgent.displayName || hostAgent.profile} 负责回应`">主持人 {{ hostAgent.displayName || hostAgent.profile }}</span>
             <div class="group-avatars" aria-label="群聊成员"><span v-for="agent in agents.slice(0, 4)" :key="agent.id" :title="agent.isHost ? `${agent.name} · 主持人` : agent.name" :class="{ host: agent.isHost }">{{ agent.name.slice(0, 1).toUpperCase() }}</span><em v-if="agents.length > 4">+{{ agents.length - 4 }}</em></div>
-            <button class="icon-button" type="button" title="管理群聊" aria-label="管理群聊" :disabled="!groups.selectedRoom" @click="managerOpen = true"><AppIcon name="dots" /></button>
+            <button class="icon-button" type="button" title="管理群聊" aria-label="管理群聊" :disabled="!groups.selectedRoom" @click="openSelectedRoomManager"><AppIcon name="dots" /></button>
           </div>
         </template>
       </MessageTimeline>

+ 22 - 0
tests/client/groupHost.test.ts

@@ -44,6 +44,28 @@ describe('group host controls', () => {
     wrapper.unmount()
   })
 
+  it('adds an Agent from a paired node to an existing group', async () => {
+    const local = agent('agent-local', 'default', '本机 Agent', true, true)
+    const remote = {
+      id: '11111111-1111-4111-8111-111111111111|reviewer',
+      profile: 'reviewer',
+      displayName: '远端评审',
+      nodeId: '11111111-1111-4111-8111-111111111111',
+      nodeLabel: '工作室 Mac',
+    }
+    const wrapper = mount(GroupManager, {
+      props: {
+        room: { id: 'room-1', name: '群聊', memberIds: [local.id], replyRounds: 3 },
+        agents: [local],
+        availableProfiles: [remote],
+      },
+    })
+
+    await wrapper.get<HTMLSelectElement>('select[aria-label="选择要添加的 Agent"]').setValue(remote.id)
+    expect(wrapper.emitted('addAgent')?.at(-1)).toEqual([remote.id])
+    wrapper.unmount()
+  })
+
   it('keeps one independent host selection when creating a v5 room', async () => {
     const wrapper = mount(CreateGroupDialog, {
       attachTo: document.body,