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") + } +}