mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
Redesign sensitive info protection settings
This commit is contained in:
@@ -236,6 +236,20 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 rounded-lg border border-border bg-muted/30 p-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<Label class="text-sm font-medium">敏感信息保护</Label>
|
||||
<Switch v-model="form.chat_pii_redaction_enabled" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<Label class="text-sm font-medium">占位符说明</Label>
|
||||
<Switch
|
||||
v-model="form.chat_pii_redaction_placeholder_notice"
|
||||
:disabled="!form.chat_pii_redaction_enabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 额度 -->
|
||||
<div class="space-y-2">
|
||||
<Label class="text-sm font-medium">额度</Label>
|
||||
@@ -311,6 +325,10 @@ import { getGlobalModels } from '@/api/global-models'
|
||||
import { adminApi } from '@/api/admin'
|
||||
import { log } from '@/utils/logger'
|
||||
import { parseNumberInput } from '@/utils/form'
|
||||
import {
|
||||
mergeChatPiiRedactionFeatureSettings,
|
||||
readChatPiiRedactionFeatureSettings,
|
||||
} from '@/utils/featureSettings'
|
||||
import type { ProviderWithEndpointsSummary, GlobalModelResponse } from '@/api/endpoints/types'
|
||||
|
||||
export interface StandaloneKeyFormData {
|
||||
@@ -326,6 +344,7 @@ export interface StandaloneKeyFormData {
|
||||
allowed_providers?: string[] | null
|
||||
allowed_api_formats?: string[] | null
|
||||
allowed_models?: string[] | null
|
||||
feature_settings?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
interface StandaloneKeyFormState {
|
||||
@@ -346,6 +365,8 @@ interface StandaloneKeyFormState {
|
||||
allowed_providers: string[]
|
||||
allowed_api_formats: string[]
|
||||
allowed_models: string[]
|
||||
chat_pii_redaction_enabled: boolean
|
||||
chat_pii_redaction_placeholder_notice: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -403,6 +424,8 @@ const form = ref<StandaloneKeyFormState>({
|
||||
allowed_providers: [],
|
||||
allowed_api_formats: [],
|
||||
allowed_models: [],
|
||||
chat_pii_redaction_enabled: false,
|
||||
chat_pii_redaction_placeholder_notice: true,
|
||||
})
|
||||
|
||||
function formatDateInputValue(date: Date): string {
|
||||
@@ -449,11 +472,14 @@ function resetForm() {
|
||||
allowed_providers: [],
|
||||
allowed_api_formats: [],
|
||||
allowed_models: [],
|
||||
chat_pii_redaction_enabled: false,
|
||||
chat_pii_redaction_placeholder_notice: true,
|
||||
} as typeof form.value
|
||||
}
|
||||
|
||||
function loadKeyData() {
|
||||
if (!props.apiKey) return
|
||||
const redactionFeature = readChatPiiRedactionFeatureSettings(props.apiKey.feature_settings)
|
||||
form.value = {
|
||||
id: props.apiKey.id,
|
||||
name: props.apiKey.name || '',
|
||||
@@ -472,6 +498,8 @@ function loadKeyData() {
|
||||
allowed_providers: props.apiKey.allowed_providers ? [...props.apiKey.allowed_providers] : [],
|
||||
allowed_api_formats: props.apiKey.allowed_api_formats ? [...props.apiKey.allowed_api_formats] : [],
|
||||
allowed_models: props.apiKey.allowed_models ? [...props.apiKey.allowed_models] : [],
|
||||
chat_pii_redaction_enabled: redactionFeature.enabled,
|
||||
chat_pii_redaction_placeholder_notice: redactionFeature.inject_model_instruction,
|
||||
} as typeof form.value
|
||||
}
|
||||
|
||||
@@ -520,6 +548,10 @@ function handleSubmit() {
|
||||
allowed_providers: form.value.provider_unrestricted ? null : [...form.value.allowed_providers],
|
||||
allowed_api_formats: form.value.api_format_unrestricted ? null : [...form.value.allowed_api_formats],
|
||||
allowed_models: form.value.model_unrestricted ? null : [...form.value.allowed_models],
|
||||
feature_settings: mergeChatPiiRedactionFeatureSettings(props.apiKey?.feature_settings, {
|
||||
enabled: form.value.chat_pii_redaction_enabled,
|
||||
inject_model_instruction: form.value.chat_pii_redaction_placeholder_notice,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -269,31 +269,12 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 p-3 border rounded-lg bg-muted/50"
|
||||
:class="redactionModuleScope === 'all_providers' ? 'border-primary/30 bg-primary/5' : ''"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-4 p-3 border rounded-lg bg-muted/50">
|
||||
<div class="space-y-0.5">
|
||||
<span class="text-sm font-medium">敏感信息替换保护</span>
|
||||
<span class="text-sm font-medium">敏感信息保护</span>
|
||||
<p class="text-xs text-muted-foreground leading-relaxed">
|
||||
{{ redactionHelperText }}
|
||||
请前往模块管理-敏感信息保护中配置详细规则。
|
||||
</p>
|
||||
<p
|
||||
v-if="!redactionModuleEnabled"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
模块总开关未开启,保存此供应商不会立即生效。
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-3">
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{{ redactionSwitchLabel }}
|
||||
</span>
|
||||
<Switch
|
||||
:model-value="redactionSwitchValue"
|
||||
:disabled="redactionModuleScope === 'all_providers' || redactionModuleLoading"
|
||||
@update:model-value="(v: boolean) => form.chat_pii_redaction_enabled = v"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -341,12 +322,9 @@ import {
|
||||
updateProvider,
|
||||
type ProviderWithEndpointsSummary,
|
||||
} from '@/api/endpoints'
|
||||
import { modulesApi, type ChatPiiRedactionProviderScope } from '@/api/modules'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { parseNumberInput } from '@/utils/form'
|
||||
import { dateTimeLocalToRfc3339, formatDateTimeLocalInput } from '@/utils/date'
|
||||
import { getProviderRedactionConfig, withProviderRedactionConfig } from '@/features/providers/utils/providerRedactionPayload'
|
||||
import { log } from '@/utils/logger'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -362,9 +340,6 @@ const emit = defineEmits<{
|
||||
|
||||
const { success, error: showError } = useToast()
|
||||
const loading = ref(false)
|
||||
const redactionModuleLoading = ref(false)
|
||||
const redactionModuleEnabled = ref(false)
|
||||
const redactionModuleScope = ref<ChatPiiRedactionProviderScope>('selected_providers')
|
||||
|
||||
// 内部状态
|
||||
const internalOpen = computed(() => props.modelValue)
|
||||
@@ -402,43 +377,8 @@ const form = ref({
|
||||
request_timeout: undefined as number | undefined,
|
||||
// 号池模式
|
||||
pool_mode_enabled: false,
|
||||
chat_pii_redaction_enabled: false,
|
||||
})
|
||||
|
||||
const redactionSwitchValue = computed(() => {
|
||||
if (redactionModuleScope.value === 'all_providers') {
|
||||
return redactionModuleEnabled.value
|
||||
}
|
||||
return form.value.chat_pii_redaction_enabled
|
||||
})
|
||||
|
||||
const redactionSwitchLabel = computed(() => {
|
||||
if (redactionModuleScope.value === 'all_providers') {
|
||||
return redactionModuleEnabled.value ? '继承开启' : '未生效'
|
||||
}
|
||||
return form.value.chat_pii_redaction_enabled ? '已开启' : '未开启'
|
||||
})
|
||||
|
||||
const redactionHelperText = computed(() => {
|
||||
if (redactionModuleScope.value === 'all_providers') {
|
||||
return '模块管理已设置为“全部供应商”,此供应商会自动执行替换保护。替换类型由模块管理中的“替换类型配置”决定。'
|
||||
}
|
||||
return '仅当“开启敏感信息替换保护”和此供应商开关都开启时生效。替换类型在模块管理的“替换类型配置”中统一选择,适用于所有已开启该功能的供应商。供应商只会看到占位符,客户端响应会自动还原。'
|
||||
})
|
||||
|
||||
async function loadRedactionModuleState() {
|
||||
redactionModuleLoading.value = true
|
||||
try {
|
||||
const config = await modulesApi.getChatPiiRedactionConfig()
|
||||
redactionModuleEnabled.value = config.enabled
|
||||
redactionModuleScope.value = config.provider_scope
|
||||
} catch (err) {
|
||||
log.warn('加载敏感信息替换保护模块配置失败', err)
|
||||
} finally {
|
||||
redactionModuleLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function resetForm() {
|
||||
form.value = {
|
||||
@@ -463,7 +403,6 @@ function resetForm() {
|
||||
request_timeout: undefined,
|
||||
// 号池模式
|
||||
pool_mode_enabled: false,
|
||||
chat_pii_redaction_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +433,6 @@ function loadProviderData() {
|
||||
request_timeout: props.provider.request_timeout ?? undefined,
|
||||
// 号池模式
|
||||
pool_mode_enabled: poolAdvanced !== null,
|
||||
chat_pii_redaction_enabled: getProviderRedactionConfig(props.provider).enabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,12 +446,6 @@ const { isEditMode, handleDialogUpdate, handleCancel } = useFormDialog({
|
||||
resetForm,
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (open) => {
|
||||
if (open) {
|
||||
loadRedactionModuleState()
|
||||
}
|
||||
})
|
||||
|
||||
// 新建模式下切换 provider_type 时不自动开启号池模式
|
||||
watch(() => form.value.provider_type, () => {
|
||||
if (!isEditMode.value) {
|
||||
@@ -548,7 +480,7 @@ const handleSubmit = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const currentPoolAdvanced = normalizePoolAdvancedConfig(props.provider?.pool_advanced)
|
||||
const basePayload = withProviderRedactionConfig({
|
||||
const basePayload = {
|
||||
name: form.value.name,
|
||||
provider_type: form.value.provider_type,
|
||||
description: form.value.description || undefined,
|
||||
@@ -568,7 +500,7 @@ const handleSubmit = async () => {
|
||||
pool_advanced: form.value.pool_mode_enabled
|
||||
? (currentPoolAdvanced ?? {})
|
||||
: null,
|
||||
}, form.value.chat_pii_redaction_enabled)
|
||||
}
|
||||
|
||||
if (isEditMode.value && props.provider) {
|
||||
// 更新提供商
|
||||
|
||||
@@ -209,6 +209,20 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-border bg-muted/30 p-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<Label class="text-sm font-medium">敏感信息保护</Label>
|
||||
<Switch v-model="form.chat_pii_redaction_enabled" />
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-between gap-3">
|
||||
<Label class="text-sm font-medium">占位符说明</Label>
|
||||
<Switch
|
||||
v-model="form.chat_pii_redaction_placeholder_notice"
|
||||
:disabled="!form.chat_pii_redaction_enabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -252,6 +266,10 @@ import { MultiSelect } from '@/components/common'
|
||||
import { adminApi } from '@/api/admin'
|
||||
import { log } from '@/utils/logger'
|
||||
import { parseNumberInput } from '@/utils/form'
|
||||
import {
|
||||
mergeChatPiiRedactionFeatureSettings,
|
||||
readChatPiiRedactionFeatureSettings,
|
||||
} from '@/utils/featureSettings'
|
||||
import {
|
||||
getPasswordPolicyHint,
|
||||
getPasswordPolicyPlaceholder,
|
||||
@@ -270,6 +288,7 @@ export interface UserFormData {
|
||||
role: 'admin' | 'user'
|
||||
is_active?: boolean
|
||||
group_ids?: string[]
|
||||
feature_settings?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -299,6 +318,8 @@ const form = ref({
|
||||
unlimited: false,
|
||||
is_active: true,
|
||||
group_ids: [] as string[],
|
||||
chat_pii_redaction_enabled: false,
|
||||
chat_pii_redaction_placeholder_notice: true,
|
||||
})
|
||||
|
||||
const groupOptions = computed(() => (props.groups || []).map((group) => ({
|
||||
@@ -322,12 +343,15 @@ function resetForm() {
|
||||
unlimited: false,
|
||||
is_active: true,
|
||||
group_ids: [],
|
||||
chat_pii_redaction_enabled: false,
|
||||
chat_pii_redaction_placeholder_notice: true,
|
||||
}
|
||||
}
|
||||
|
||||
function loadUserData() {
|
||||
if (!props.user) return
|
||||
formNonce.value = createFieldNonce()
|
||||
const redactionFeature = readChatPiiRedactionFeatureSettings(props.user.feature_settings)
|
||||
// 创建数组副本,避免与 props 数据共享引用
|
||||
form.value = {
|
||||
username: props.user.username,
|
||||
@@ -339,6 +363,8 @@ function loadUserData() {
|
||||
unlimited: props.user.unlimited ?? false,
|
||||
is_active: props.user.is_active ?? true,
|
||||
group_ids: props.user.group_ids ? [...props.user.group_ids] : [],
|
||||
chat_pii_redaction_enabled: redactionFeature.enabled,
|
||||
chat_pii_redaction_placeholder_notice: redactionFeature.inject_model_instruction,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,6 +439,10 @@ async function handleSubmit() {
|
||||
unlimited: form.value.unlimited,
|
||||
role: form.value.role,
|
||||
group_ids: [...form.value.group_ids],
|
||||
feature_settings: mergeChatPiiRedactionFeatureSettings(props.user?.feature_settings, {
|
||||
enabled: form.value.chat_pii_redaction_enabled,
|
||||
inject_model_instruction: form.value.chat_pii_redaction_placeholder_notice,
|
||||
}),
|
||||
}
|
||||
|
||||
if (isEditMode.value && props.user?.id) {
|
||||
|
||||
Reference in New Issue
Block a user