diff --git a/frontend/src/components/AgentSelector.vue b/frontend/src/components/AgentSelector.vue
index 96281f3f..4c418111 100644
--- a/frontend/src/components/AgentSelector.vue
+++ b/frontend/src/components/AgentSelector.vue
@@ -41,7 +41,7 @@
{{ agent.name }}
@@ -66,7 +66,7 @@
{{ agent.name }}
@@ -95,7 +95,7 @@
class="agent-option-actions"
>
@@ -216,8 +216,9 @@ import {
resolveAgentNotReadyHighlight,
type AgentNotReadyReasonKey,
} from '@/utils/agent-readiness';
+import { formatLocalizedList } from '@/utils/format-list';
-const { t } = useI18n();
+const { t, locale } = useI18n();
const router = useRouter();
const orgStore = useOrganizationStore();
const settingsStore = useSettingsStore();
@@ -233,6 +234,7 @@ const props = defineProps<{
const emit = defineEmits<{
(e: 'close'): void;
(e: 'select', agent: CustomAgent, sourceTenantId?: string): void;
+ (e: 'not-ready', agent: CustomAgent, labels: string[], keys: AgentNotReadyReasonKey[], sourceTenantId?: string): void;
}>();
type AgentDetailTarget = {
@@ -347,6 +349,16 @@ const getAgentNotReadyLabels = (agent: CustomAgent, sourceTenantId?: string): st
);
};
+const formatNotReadyHint = (agent: CustomAgent, sourceTenantId?: string): string => {
+ return formatLocalizedList(getAgentNotReadyLabels(agent, sourceTenantId), locale.value);
+};
+
+const emitAgentNotReady = (agent: CustomAgent, sourceTenantId?: string) => {
+ const keys = getAgentNotReadyReasonKeysFor(agent, sourceTenantId);
+ const labels = formatAgentNotReadyReasons(keys, agent.is_builtin);
+ emit('not-ready', agent, labels, keys, sourceTenantId);
+};
+
const clearDetailHideTimer = () => {
if (detailHideTimer) {
clearTimeout(detailHideTimer);
@@ -418,13 +430,19 @@ const hideDetailPanel = () => {
};
const selectAgent = (agent: CustomAgent) => {
- if (getAgentNotReadyLabels(agent).length > 0) return;
+ if (getAgentNotReadyLabels(agent).length > 0) {
+ emitAgentNotReady(agent);
+ return;
+ }
emit('select', agent);
};
const selectSharedAgent = (shared: SharedAgentInfo) => {
const sourceTenantId = String(shared.source_tenant_id);
- if (getAgentNotReadyLabels(shared.agent, sourceTenantId).length > 0) return;
+ if (getAgentNotReadyLabels(shared.agent, sourceTenantId).length > 0) {
+ emitAgentNotReady(shared.agent as CustomAgent, sourceTenantId);
+ return;
+ }
emit('select', shared.agent as CustomAgent, sourceTenantId);
};
@@ -440,6 +458,7 @@ const goToSettings = (agent: CustomAgent, sourceTenantId?: string) => {
edit: agent.id,
section,
...(highlight ? { highlight } : {}),
+ ...(sourceTenantId ? { sourceTenantId } : {}),
},
});
};
diff --git a/frontend/src/components/Input-field.vue b/frontend/src/components/Input-field.vue
index 2e97c3c5..fb48c969 100644
--- a/frontend/src/components/Input-field.vue
+++ b/frontend/src/components/Input-field.vue
@@ -32,6 +32,7 @@ import {
resolveAgentNotReadyHighlight,
type AgentNotReadyReasonKey,
} from '@/utils/agent-readiness';
+import { formatLocalizedList } from '@/utils/format-list';
const route = useRoute();
const router = useRouter();
@@ -47,7 +48,7 @@ const {
chatModels: availableModels,
webSearchProviders,
} = storeToRefs(chatResources);
-const { t } = useI18n();
+const { t, locale } = useI18n();
let query = ref("");
const showKbSelector = ref(false);
@@ -1631,9 +1632,18 @@ const createSession = async (val: string) => {
actualAgent = builtin || agentToCheck;
}
const isAgentMode = actualAgent.config?.agent_mode === 'smart-reasoning';
- const { keys: notReadyKeys, labels: notReadyReasons } = collectAgentNotReadyReasons(actualAgent, isAgentMode);
+ const { keys: notReadyKeys, labels: notReadyReasons } = collectAgentNotReadyReasons(
+ actualAgent,
+ isAgentMode,
+ settingsStore.selectedAgentSourceTenantId ?? undefined,
+ );
if (notReadyReasons.length > 0) {
- showAgentNotReadyMessage(actualAgent, notReadyReasons, notReadyKeys);
+ showAgentNotReadyMessage(
+ actualAgent,
+ notReadyReasons,
+ notReadyKeys,
+ settingsStore.selectedAgentSourceTenantId ?? undefined,
+ );
return;
}
// 获取@提及的知识库和文件信息
@@ -1780,12 +1790,13 @@ const selectAgentMode = async (mode: 'quick-answer' | 'smart-reasoning') => {
const builtinAgent = agents.value.find(a => a.id === builtinAgentId);
if (builtinAgent) {
- const { labels: notReadyReasons } = collectAgentNotReadyReasons(
+ const { keys: notReadyKeys, labels: notReadyReasons } = collectAgentNotReadyReasons(
builtinAgent,
mode === 'smart-reasoning',
);
if (notReadyReasons.length > 0) {
showAgentModeSelector.value = false;
+ showAgentNotReadyMessage(builtinAgent, notReadyReasons, notReadyKeys);
return;
}
}
@@ -1801,6 +1812,15 @@ const selectAgentMode = async (mode: 'quick-answer' | 'smart-reasoning') => {
}
// 选择智能体(新版);sourceTenantId 为共享智能体时传入
+const handleAgentNotReady = (
+ agent: CustomAgent,
+ labels: string[],
+ keys: AgentNotReadyReasonKey[],
+ sourceTenantId?: string,
+) => {
+ showAgentNotReadyMessage(agent, labels, keys, sourceTenantId);
+};
+
const handleSelectAgent = async (agent: CustomAgent, sourceTenantId?: string) => {
if (!chatResources.isFresh('models')) {
await loadChatModels()
@@ -1814,7 +1834,7 @@ const handleSelectAgent = async (agent: CustomAgent, sourceTenantId?: string) =>
? (agents.value.find(a => a.id === agent.id) || agent)
: agent;
- const { labels: notReadyReasons } = collectAgentNotReadyReasons(
+ const { keys: notReadyKeys, labels: notReadyReasons } = collectAgentNotReadyReasons(
actualAgent,
isAgentType,
sourceTenantId,
@@ -1822,6 +1842,7 @@ const handleSelectAgent = async (agent: CustomAgent, sourceTenantId?: string) =>
if (notReadyReasons.length > 0) {
showAgentModeSelector.value = false;
+ showAgentNotReadyMessage(actualAgent, notReadyReasons, notReadyKeys, sourceTenantId);
return;
}
@@ -1990,7 +2011,7 @@ const collectAgentNotReadyReasons = (
isAgentMode: boolean,
sourceTenantId?: string,
): { keys: AgentNotReadyReasonKey[]; labels: string[] } => {
- const isSharedAgent = !!(sourceTenantId || settingsStore.selectedAgentSourceTenantId);
+ const isSharedAgent = !!sourceTenantId;
const keys = getAgentNotReadyReasonKeys(agent.config, allModels.value, {
isAgentMode,
isSharedAgent,
@@ -2005,6 +2026,7 @@ const goToAgentEditor = (
agent: CustomAgent,
section = 'model',
highlight?: AgentNotReadyReasonKey,
+ sourceTenantId?: string,
) => {
router.push({
path: '/platform/agents',
@@ -2012,6 +2034,7 @@ const goToAgentEditor = (
edit: agent.id,
section,
...(highlight ? { highlight } : {}),
+ ...(sourceTenantId ? { sourceTenantId } : {}),
},
});
};
@@ -2021,8 +2044,9 @@ const showAgentNotReadyMessage = (
agent: CustomAgent,
reasons: string[],
reasonKeys?: AgentNotReadyReasonKey[],
+ sourceTenantId?: string,
) => {
- const reasonsText = reasons.join('、')
+ const reasonsText = formatLocalizedList(reasons, locale.value)
const section = resolveAgentNotReadySection(reasonKeys || ['summary_model'])
const highlight = resolveAgentNotReadyHighlight(reasonKeys || ['summary_model'])
@@ -2032,7 +2056,7 @@ const showAgentNotReadyMessage = (
href: '#',
onClick: (e: Event) => {
e.preventDefault();
- goToAgentEditor(agent, section, highlight);
+ goToAgentEditor(agent, section, highlight, sourceTenantId);
},
style: 'color: var(--td-brand-color); text-decoration: none; font-weight: 500; cursor: pointer; align-self: flex-start;',
onMouseenter: (e: Event) => {
@@ -2210,7 +2234,8 @@ defineExpose({
+ @select="handleSelectAgent"
+ @not-ready="handleAgentNotReady" />
diff --git a/frontend/src/utils/agent-readiness.test.mjs b/frontend/src/utils/agent-readiness.test.mjs
index 9ebd75ba..a6f8924c 100644
--- a/frontend/src/utils/agent-readiness.test.mjs
+++ b/frontend/src/utils/agent-readiness.test.mjs
@@ -92,3 +92,11 @@ test('getAgentNotReadyReasonKeys requires rerank only in agent mode with KB sear
{ isAgentMode: true, isSharedAgent: false },
), ['summary_model', 'rerank_model'])
})
+
+test('getAgentNotReadyReasonKeys does not treat current shared context as shared without sourceTenantId', () => {
+ assert.deepEqual(getAgentNotReadyReasonKeys(
+ { model_id: 'deleted-chat' },
+ [{ id: 'chat-1', type: 'KnowledgeQA' }],
+ { isAgentMode: false, isSharedAgent: false },
+ ), ['summary_model'])
+})
diff --git a/frontend/src/utils/format-list.ts b/frontend/src/utils/format-list.ts
new file mode 100644
index 00000000..0f48e9d1
--- /dev/null
+++ b/frontend/src/utils/format-list.ts
@@ -0,0 +1,12 @@
+/** Locale-aware conjunction for short UI lists (e.g. missing config items). */
+export function formatLocalizedList(items: string[], locale = 'en-US'): string {
+ if (items.length === 0) return ''
+ if (typeof Intl !== 'undefined' && typeof Intl.ListFormat !== 'undefined') {
+ try {
+ return new Intl.ListFormat(locale, { style: 'long', type: 'conjunction' }).format(items)
+ } catch {
+ // Unsupported locale — fall through.
+ }
+ }
+ return items.join(', ')
+}
diff --git a/frontend/src/views/agent/AgentEditorModal.vue b/frontend/src/views/agent/AgentEditorModal.vue
index c9862c72..c19a9897 100644
--- a/frontend/src/views/agent/AgentEditorModal.vue
+++ b/frontend/src/views/agent/AgentEditorModal.vue
@@ -620,9 +620,9 @@
-
+
{
return kbSelectionMode.value !== 'none';
});
+const showRerankModelField = computed(() => {
+ if (!isAgentMode.value) return hasKnowledgeBase.value;
+ return hasKnowledgeBase.value || agentRequiresRerankModel(formData.value.config);
+});
+
// 当前配置下进入到智能体作用域的知识库列表
// 注意:用户可能选了 knowledge_bases(按库级),也可能选了 knowledge_ids(按文档级)
// 这里仅用于 UI 上的工具可用性判定,按库级来计算
diff --git a/frontend/src/views/agent/AgentList.vue b/frontend/src/views/agent/AgentList.vue
index be39c929..91f74dc9 100644
--- a/frontend/src/views/agent/AgentList.vue
+++ b/frontend/src/views/agent/AgentList.vue
@@ -1119,7 +1119,9 @@ const fetchList = (force = false) => {
return Promise.all([
chatResources.fetchAgentsForList({ creator: creatorFilter.value }, force).then(applyAgentListData),
orgStore.fetchOrganizations({ force }),
+ orgStore.fetchSharedAgents({ force }),
]).finally(() => { loading.value = false }).then(() => {
+ checkAndOpenEditModal()
// 各空间智能体数量已由 GET /organizations 的 resource_counts 带回,存于 orgStore.resourceCounts
const counts = orgStore.resourceCounts?.agents?.by_organization
if (counts) spaceAgentCountByOrg.value = { ...counts }
@@ -1127,9 +1129,22 @@ const fetchList = (force = false) => {
}
// 检查 URL 参数并打开编辑模态框
+const resolveAgentForEdit = (editId: string, sourceTenantId?: string): CustomAgent | null => {
+ const own = agents.value.find(a => a.id === editId)
+ if (own) return own
+ if (sourceTenantId) {
+ const shared = sharedAgents.value.find(
+ s => s.agent?.id === editId && String(s.source_tenant_id) === sourceTenantId,
+ )
+ if (shared?.agent) return shared.agent as CustomAgent
+ }
+ return null
+}
+
const checkAndOpenEditModal = () => {
const editId = route.query.edit as string
const section = route.query.section as string
+ const sourceTenantId = route.query.sourceTenantId as string | undefined
if (editId && (section === 'im' || section === 'embed' || section === 'integrations')) {
const tab = section === 'embed' ? 'embed' : 'im'
router.replace({
@@ -1139,7 +1154,7 @@ const checkAndOpenEditModal = () => {
return
}
if (editId) {
- const agent = agents.value.find(a => a.id === editId)
+ const agent = resolveAgentForEdit(editId, sourceTenantId)
if (agent) {
editingAgent.value = agent
editorMode.value = 'edit'
@@ -1149,7 +1164,7 @@ const checkAndOpenEditModal = () => {
}
// Drop the transient edit/section params but preserve other filter
// state (scope / creator / q) so refreshing doesn't reset the view.
- const { edit: _e, section: _s, highlight: _h, ...rest } = route.query
+ const { edit: _e, section: _s, highlight: _h, sourceTenantId: _st, ...rest } = route.query
router.replace({ path: route.path, query: rest })
}
}
@@ -1161,7 +1176,7 @@ const checkAndOpenEditModal = () => {
watch(
() => route.query.edit,
(v) => {
- if (v && agents.value.length > 0) {
+ if (v && (agents.value.length > 0 || sharedAgents.value.length > 0)) {
checkAndOpenEditModal()
}
},
diff --git a/internal/application/service/session_model_requirement_test.go b/internal/application/service/session_model_requirement_test.go
index 2702a9e0..ee8897cf 100644
--- a/internal/application/service/session_model_requirement_test.go
+++ b/internal/application/service/session_model_requirement_test.go
@@ -83,3 +83,67 @@ func TestResolveChatModelIDUsesValidConfiguredAgentModel(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "agent-chat", modelID)
}
+
+func TestResolveChatModelIDRejectsNonChatSummaryModelOverride(t *testing.T) {
+ svc := &sessionService{
+ modelService: &stubModelService{
+ modelsByID: map[string]*types.Model{
+ "agent-chat": {
+ ID: "agent-chat",
+ Type: types.ModelTypeKnowledgeQA,
+ },
+ "rerank-only": {
+ ID: "rerank-only",
+ Type: types.ModelTypeRerank,
+ },
+ },
+ },
+ }
+ req := &types.QARequest{
+ Session: &types.Session{},
+ CustomAgent: &types.CustomAgent{
+ ID: "agent-1",
+ Config: types.CustomAgentConfig{
+ ModelID: "agent-chat",
+ },
+ },
+ SummaryModelID: "rerank-only",
+ }
+
+ modelID, err := svc.resolveChatModelID(context.Background(), req, nil, nil)
+
+ require.NoError(t, err)
+ assert.Equal(t, "agent-chat", modelID)
+}
+
+func TestResolveChatModelIDUsesValidSummaryModelOverride(t *testing.T) {
+ svc := &sessionService{
+ modelService: &stubModelService{
+ modelsByID: map[string]*types.Model{
+ "agent-chat": {
+ ID: "agent-chat",
+ Type: types.ModelTypeKnowledgeQA,
+ },
+ "override-chat": {
+ ID: "override-chat",
+ Type: types.ModelTypeKnowledgeQA,
+ },
+ },
+ },
+ }
+ req := &types.QARequest{
+ Session: &types.Session{},
+ CustomAgent: &types.CustomAgent{
+ ID: "agent-1",
+ Config: types.CustomAgentConfig{
+ ModelID: "agent-chat",
+ },
+ },
+ SummaryModelID: "override-chat",
+ }
+
+ modelID, err := svc.resolveChatModelID(context.Background(), req, nil, nil)
+
+ require.NoError(t, err)
+ assert.Equal(t, "override-chat", modelID)
+}
diff --git a/internal/application/service/session_qa_helpers.go b/internal/application/service/session_qa_helpers.go
index e56d18a1..8f0cc0e7 100644
--- a/internal/application/service/session_qa_helpers.go
+++ b/internal/application/service/session_qa_helpers.go
@@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
+ "strings"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/types"
@@ -67,7 +68,7 @@ func (s *sessionService) resolveChatModelID(
session := req.Session
if customAgent != nil {
- configuredModelID := customAgent.Config.ModelID
+ configuredModelID := strings.TrimSpace(customAgent.Config.ModelID)
if configuredModelID == "" {
return "", fmt.Errorf("chat model is not configured: please set model_id on agent %s", customAgent.ID)
}
@@ -77,16 +78,18 @@ func (s *sessionService) resolveChatModelID(
}
}
+ summaryModelID = strings.TrimSpace(summaryModelID)
if summaryModelID != "" {
- if model, err := s.modelService.GetModelByID(ctx, summaryModelID); err == nil && model != nil {
+ if model, err := s.modelService.GetModelByID(ctx, summaryModelID); err == nil && model != nil &&
+ model.Type == types.ModelTypeKnowledgeQA {
logger.Infof(ctx, "Using request's summary model override: %s", summaryModelID)
return summaryModelID, nil
}
logger.Warnf(ctx, "Request provided invalid summary model ID %s, falling back", summaryModelID)
}
- if customAgent != nil && customAgent.Config.ModelID != "" {
- logger.Infof(ctx, "Using custom agent's model_id: %s", customAgent.Config.ModelID)
- return customAgent.Config.ModelID, nil
+ if customAgent != nil && strings.TrimSpace(customAgent.Config.ModelID) != "" {
+ logger.Infof(ctx, "Using custom agent's model_id: %s", strings.TrimSpace(customAgent.Config.ModelID))
+ return strings.TrimSpace(customAgent.Config.ModelID), nil
}
return s.selectChatModelID(ctx, session, knowledgeBaseIDs, knowledgeIDs)
}