mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 04:18:17 +08:00
fix(rbac): let viewers read active model so they can actually chat
This commit is contained in:
parent
7cf2508ded
commit
26df2c94d0
@ -64,23 +64,28 @@ public class ModelConfigController {
|
|||||||
return R.ok(modelProviderService.setEnabled(providerId, false));
|
return R.ok(modelProviderService.setEnabled(providerId, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Viewers need to know which models are available + which one is active so
|
||||||
|
// the chat runtime can render correctly. None of these endpoints return
|
||||||
|
// API keys or base URLs — those live on ProviderInfoDTO (GET /models)
|
||||||
|
// which stays admin-only.
|
||||||
|
|
||||||
@Operation(summary = "获取启用模型列表")
|
@Operation(summary = "获取启用模型列表")
|
||||||
@GetMapping("/enabled")
|
@GetMapping("/enabled")
|
||||||
@RequireWorkspaceRole("admin")
|
@RequireWorkspaceRole("viewer")
|
||||||
public R<List<ModelConfigEntity>> listEnabled() {
|
public R<List<ModelConfigEntity>> listEnabled() {
|
||||||
return R.ok(modelConfigService.listEnabledModels());
|
return R.ok(modelConfigService.listEnabledModels());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "获取默认模型")
|
@Operation(summary = "获取默认模型")
|
||||||
@GetMapping("/default")
|
@GetMapping("/default")
|
||||||
@RequireWorkspaceRole("admin")
|
@RequireWorkspaceRole("viewer")
|
||||||
public R<ModelConfigEntity> getDefaultModel() {
|
public R<ModelConfigEntity> getDefaultModel() {
|
||||||
return R.ok(modelConfigService.getDefaultModel());
|
return R.ok(modelConfigService.getDefaultModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "获取当前激活模型")
|
@Operation(summary = "获取当前激活模型")
|
||||||
@GetMapping("/active")
|
@GetMapping("/active")
|
||||||
@RequireWorkspaceRole("admin")
|
@RequireWorkspaceRole("viewer")
|
||||||
public R<ActiveModelsInfo> getActiveModel() {
|
public R<ActiveModelsInfo> getActiveModel() {
|
||||||
ModelConfigEntity model = modelConfigService.getDefaultModel();
|
ModelConfigEntity model = modelConfigService.getDefaultModel();
|
||||||
ActiveModelsInfo info = new ActiveModelsInfo();
|
ActiveModelsInfo info = new ActiveModelsInfo();
|
||||||
@ -230,7 +235,7 @@ public class ModelConfigController {
|
|||||||
|
|
||||||
@Operation(summary = "按类型筛选模型(chat / embedding),可选 modality 过滤")
|
@Operation(summary = "按类型筛选模型(chat / embedding),可选 modality 过滤")
|
||||||
@GetMapping("/by-type")
|
@GetMapping("/by-type")
|
||||||
@RequireWorkspaceRole("admin")
|
@RequireWorkspaceRole("member")
|
||||||
public R<List<ModelConfigEntity>> listByType(
|
public R<List<ModelConfigEntity>> listByType(
|
||||||
@RequestParam(defaultValue = "chat") String modelType,
|
@RequestParam(defaultValue = "chat") String modelType,
|
||||||
@RequestParam(required = false) String modality) {
|
@RequestParam(required = false) String modality) {
|
||||||
|
|||||||
@ -443,6 +443,10 @@ const recoverablePrompt = ref(false)
|
|||||||
const recoverableDismissed = ref(false)
|
const recoverableDismissed = ref(false)
|
||||||
const defaultModel = ref<ModelConfig | null>(null)
|
const defaultModel = ref<ModelConfig | null>(null)
|
||||||
const providers = ref<ProviderInfo[]>([])
|
const providers = ref<ProviderInfo[]>([])
|
||||||
|
// True when /models 403s for a viewer-level user. Provider config (API keys,
|
||||||
|
// base URLs, liveness) is admin-only, so viewers chat without it; the prompt
|
||||||
|
// flags fall back to "trust the active model" in that branch.
|
||||||
|
const providersUnavailable = ref(false)
|
||||||
const activeModels = ref<ActiveModelsInfo | null>(null)
|
const activeModels = ref<ActiveModelsInfo | null>(null)
|
||||||
const pendingAttachments = ref<ChatAttachment[]>([])
|
const pendingAttachments = ref<ChatAttachment[]>([])
|
||||||
const uploadingAttachment = ref(false)
|
const uploadingAttachment = ref(false)
|
||||||
@ -1180,21 +1184,36 @@ async function loadAgents() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadModelState() {
|
async function loadModelState() {
|
||||||
|
// /default + /active are viewer-accessible and required to chat. /models
|
||||||
|
// (provider list) is admin-only because it returns API keys + base URLs;
|
||||||
|
// viewers degrade to "trust the active model, skip the liveness banner".
|
||||||
try {
|
try {
|
||||||
const [defaultRes, providersRes, activeRes]: any = await Promise.all([
|
const [defaultRes, activeRes]: any = await Promise.all([
|
||||||
modelApi.getDefault(),
|
modelApi.getDefault(),
|
||||||
modelApi.listProviders(),
|
|
||||||
modelApi.getActive(),
|
modelApi.getActive(),
|
||||||
])
|
])
|
||||||
defaultModel.value = defaultRes.data || null
|
defaultModel.value = defaultRes.data || null
|
||||||
providers.value = providersRes.data || []
|
|
||||||
activeModels.value = activeRes.data || null
|
activeModels.value = activeRes.data || null
|
||||||
recomputePromptFlags()
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage.error(t('chat.loadModelFailed'))
|
ElMessage.error(t('chat.loadModelFailed'))
|
||||||
blockingPrompt.value = true
|
blockingPrompt.value = true
|
||||||
recoverablePrompt.value = false
|
recoverablePrompt.value = false
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const providersRes: any = await modelApi.listProviders()
|
||||||
|
providers.value = providersRes.data || []
|
||||||
|
providersUnavailable.value = false
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.response?.status === 403) {
|
||||||
|
providers.value = []
|
||||||
|
providersUnavailable.value = true
|
||||||
|
} else {
|
||||||
|
// Non-403 failure is still a real problem worth surfacing.
|
||||||
|
ElMessage.error(t('chat.loadModelFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recomputePromptFlags()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1213,6 +1232,16 @@ function recomputePromptFlags() {
|
|||||||
recoverableDismissed.value = false
|
recoverableDismissed.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Viewer-level users cannot read the provider list (admin-only because it
|
||||||
|
// returns API keys), so we can't compute liveness. Trust the active model
|
||||||
|
// and let the agent runtime surface any per-call failure instead of
|
||||||
|
// blocking the entire chat surface.
|
||||||
|
if (providersUnavailable.value) {
|
||||||
|
blockingPrompt.value = false
|
||||||
|
recoverablePrompt.value = false
|
||||||
|
recoverableDismissed.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
const ap = providers.value.find(p => p.id === active.providerId) || null
|
const ap = providers.value.find(p => p.id === active.providerId) || null
|
||||||
const apHasModels = ap
|
const apHasModels = ap
|
||||||
? ((ap.models?.length || 0) + (ap.extraModels?.length || 0)) > 0
|
? ((ap.models?.length || 0) + (ap.extraModels?.length || 0)) > 0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user