From babe3f231528e9609f45a24e6491e537e078227c Mon Sep 17 00:00:00 2001 From: wizardchen Date: Wed, 24 Jun 2026 20:08:14 +0800 Subject: [PATCH] fix(integrations): polish agent filter UX and embed session rebind Add compact agent filter beside channel list titles with selected name display, fix embed localStorage agentId read on rebind, add IM agent rebind tests, and deep-link agent editor counts to the matching tab. --- .../src/components/AgentEmbedChannelPanel.vue | 97 +++++++-------- frontend/src/components/IMChannelPanel.vue | 80 +++++-------- .../components/IntegrationsAgentFilter.vue | 113 ++++++++++++++++++ frontend/src/composables/useEmbedBridge.ts | 7 +- frontend/src/i18n/locales/en-US.ts | 3 + frontend/src/i18n/locales/ko-KR.ts | 3 + frontend/src/i18n/locales/ru-RU.ts | 3 + frontend/src/i18n/locales/zh-CN.ts | 3 + frontend/src/views/agent/AgentEditorModal.vue | 34 ++++-- .../views/integrations/IntegrationsModal.vue | 16 ++- internal/im/service_agent_test.go | 70 +++++++++++ 11 files changed, 309 insertions(+), 120 deletions(-) create mode 100644 frontend/src/components/IntegrationsAgentFilter.vue create mode 100644 internal/im/service_agent_test.go diff --git a/frontend/src/components/AgentEmbedChannelPanel.vue b/frontend/src/components/AgentEmbedChannelPanel.vue index 89b079ef..c3baea30 100644 --- a/frontend/src/components/AgentEmbedChannelPanel.vue +++ b/frontend/src/components/AgentEmbedChannelPanel.vue @@ -3,6 +3,7 @@
{{ $t('embedPublish.channelsTitle') }} + {{ channels.length }}
@@ -29,7 +30,7 @@ {{ $t('embedPublish.disabled') }}
- + {{ agentDisplayName(ch) }} @@ -135,7 +136,7 @@

{{ $t('embedPublish.sectionChannel') }}

-
+
+ + diff --git a/frontend/src/composables/useEmbedBridge.ts b/frontend/src/composables/useEmbedBridge.ts index 5da91d4a..733114b6 100644 --- a/frontend/src/composables/useEmbedBridge.ts +++ b/frontend/src/composables/useEmbedBridge.ts @@ -33,7 +33,12 @@ function readStoredSession(channelId: string): StoredSession | null { if (!raw) return null const parsed = JSON.parse(raw) if (parsed && typeof parsed.id === 'string' && typeof parsed.sig === 'string' && parsed.id) { - return { id: parsed.id, sig: parsed.sig } + const agentId = typeof parsed.agentId === 'string' ? parsed.agentId.trim() : '' + return { + id: parsed.id, + sig: parsed.sig, + ...(agentId ? { agentId } : {}), + } } } catch { // Malformed / legacy (plain-string) entry: ignore so a fresh signed diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 9c7994ee..05f7ac83 100755 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -5615,6 +5615,9 @@ export default { selectAgentPlaceholder: 'Choose an agent', selectAgentHint: 'Please select an agent first', boundAgent: 'Bound agent', + filterByAgent: 'Filter by agent', + filterByAgentWithName: 'Filter by agent: {name}', + filterAllAgents: 'All agents', manageChannel: 'Manage channel', goToCenter: 'Open Integrations', imOverview: { diff --git a/frontend/src/i18n/locales/ko-KR.ts b/frontend/src/i18n/locales/ko-KR.ts index 1d0a1afd..f7e201ed 100755 --- a/frontend/src/i18n/locales/ko-KR.ts +++ b/frontend/src/i18n/locales/ko-KR.ts @@ -5626,6 +5626,9 @@ export default { selectAgentPlaceholder: "에이전트를 선택하세요", selectAgentHint: "먼저 에이전트를 선택하세요", boundAgent: "연결된 에이전트", + filterByAgent: "에이전트별 필터", + filterByAgentWithName: "에이전트별 필터: {name}", + filterAllAgents: "전체 에이전트", manageChannel: "채널 관리", goToCenter: "통합 센터로 이동", imOverview: { diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index ca90f62b..caa5619d 100755 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -5448,6 +5448,9 @@ export default { selectAgentPlaceholder: 'Выберите агента', selectAgentHint: 'Сначала выберите агента', boundAgent: 'Привязанный агент', + filterByAgent: 'Фильтр по агенту', + filterByAgentWithName: 'Фильтр по агенту: {name}', + filterAllAgents: 'Все агенты', manageChannel: 'Управлять каналом', goToCenter: 'Открыть интеграции', imOverview: { diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 38071239..affc406b 100755 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -5633,6 +5633,9 @@ export default { selectAgentPlaceholder: "请选择智能体", selectAgentHint: "请先选择一个智能体", boundAgent: "绑定智能体", + filterByAgent: "按智能体筛选", + filterByAgentWithName: "按智能体筛选:{name}", + filterAllAgents: "全部智能体", manageChannel: "管理渠道", goToCenter: "前往集成中心", imOverview: { diff --git a/frontend/src/views/agent/AgentEditorModal.vue b/frontend/src/views/agent/AgentEditorModal.vue index beee701f..0c88c7aa 100644 --- a/frontend/src/views/agent/AgentEditorModal.vue +++ b/frontend/src/views/agent/AgentEditorModal.vue @@ -79,15 +79,13 @@
- - {{ $t('integrations.tabs.im') }} · {{ agentIMChannelCount }} - + - - {{ $t('integrations.tabs.embed') }} · {{ agentEmbedChannelCount }} - -
@@ -2084,11 +2082,11 @@ async function loadAgentIntegrationCounts(agentId: string) { } } -function gotoIntegrations() { +function gotoIntegrations(tab: 'im' | 'embed') { const agentId = editorAgent.value?.id; if (!agentId) return; handleClose(); - router.push({ path: '/platform/integrations', query: { agentId } }); + router.push({ path: '/platform/integrations', query: { agentId, tab } }); } const filteredIntentPlaceholders = computed(() => { @@ -4285,6 +4283,22 @@ const handleSave = async () => { &__stat { font-size: 13px; color: var(--td-text-color-secondary); + + &.integration-inline__link { + display: inline-flex; + align-items: center; + gap: 2px; + padding: 0; + border: none; + background: transparent; + line-height: 1; + color: var(--td-brand-color); + cursor: pointer; + + &:hover { + opacity: 0.85; + } + } } &__sep { diff --git a/frontend/src/views/integrations/IntegrationsModal.vue b/frontend/src/views/integrations/IntegrationsModal.vue index b94bcf7e..8460c133 100644 --- a/frontend/src/views/integrations/IntegrationsModal.vue +++ b/frontend/src/views/integrations/IntegrationsModal.vue @@ -46,7 +46,7 @@

- +
@@ -54,7 +54,7 @@

{{ $t('agentEditor.embed.title') }}

{{ $t('agentEditor.embed.description') }}

- +
@@ -77,7 +77,7 @@ const route = useRoute(); const router = useRouter(); const currentSection = ref<'im' | 'embed'>('im'); -const initialAgentId = ref(''); +const filterAgentId = ref(''); const visible = computed(() => route.name === 'integrations'); @@ -91,13 +91,13 @@ function applyRouteQuery() { if (tab === 'im' || tab === 'embed') { currentSection.value = tab; } - initialAgentId.value = (route.query.agentId as string) || ''; + filterAgentId.value = (route.query.agentId as string) || ''; } function syncRouteQuery() { const query: Record = { tab: currentSection.value }; - if (initialAgentId.value) { - query.agentId = initialAgentId.value; + if (filterAgentId.value) { + query.agentId = filterAgentId.value; } router.replace({ path: route.path, query }); } @@ -119,6 +119,10 @@ watch(currentSection, () => { if (visible.value) syncRouteQuery(); }); +watch(filterAgentId, () => { + if (visible.value) syncRouteQuery(); +}); + watch( () => [route.query.tab, route.query.agentId], () => { diff --git a/internal/im/service_agent_test.go b/internal/im/service_agent_test.go new file mode 100644 index 00000000..dbc1e25a --- /dev/null +++ b/internal/im/service_agent_test.go @@ -0,0 +1,70 @@ +package im + +import ( + "context" + "testing" + + "github.com/Tencent/WeKnora/internal/types" + "github.com/Tencent/WeKnora/internal/types/interfaces" +) + +type stubIMAgentService struct { + interfaces.CustomAgentService + agent *types.CustomAgent +} + +func (s *stubIMAgentService) GetAgentByID(_ context.Context, id string) (*types.CustomAgent, error) { + if s.agent != nil && s.agent.ID == id { + return s.agent, nil + } + return nil, nil +} + +func TestSetChannelAgentID(t *testing.T) { + svc := &Service{ + agentService: &stubIMAgentService{ + agent: &types.CustomAgent{ID: "agent-new", TenantID: 42}, + }, + } + channel := &IMChannel{ + ID: "im-1", + TenantID: 42, + AgentID: "agent-old", + } + + if err := svc.SetChannelAgentID(context.Background(), channel, "agent-new"); err != nil { + t.Fatalf("SetChannelAgentID() error = %v", err) + } + if channel.AgentID != "agent-new" { + t.Fatalf("channel.AgentID = %q, want agent-new", channel.AgentID) + } +} + +func TestSetChannelAgentIDRejectsForeignTenant(t *testing.T) { + svc := &Service{ + agentService: &stubIMAgentService{ + agent: &types.CustomAgent{ID: "agent-new", TenantID: 99}, + }, + } + channel := &IMChannel{ + ID: "im-1", + TenantID: 42, + AgentID: "agent-old", + } + + if err := svc.SetChannelAgentID(context.Background(), channel, "agent-new"); err == nil { + t.Fatal("SetChannelAgentID() expected error for foreign tenant agent") + } + if channel.AgentID != "agent-old" { + t.Fatalf("channel.AgentID = %q, want unchanged agent-old", channel.AgentID) + } +} + +func TestSetChannelAgentIDRequiresAgentID(t *testing.T) { + svc := &Service{agentService: &stubIMAgentService{}} + channel := &IMChannel{ID: "im-1", TenantID: 42, AgentID: "agent-old"} + + if err := svc.SetChannelAgentID(context.Background(), channel, " "); err == nil { + t.Fatal("SetChannelAgentID() expected error for empty agent_id") + } +}