优化: 设置页UX与AI自动执行三档

This commit is contained in:
2026-06-24 00:27:08 +08:00
parent 78b48d871b
commit 0a8db6504a
7 changed files with 180 additions and 38 deletions

View File

@@ -1,18 +1,21 @@
<template>
<!-- 高级设置(AI 自动执行 / 日志级别) -->
<!-- 高级设置(AI 自动执行范围 / 日志级别) -->
<section class="panel">
<div class="panel-header">
<h2>{{ $t('settings.sectionAdvanced') }}</h2>
</div>
<div class="general-settings">
<!-- F-#97:三档 low/medium/alllow=仅低风险自动(等价旧 autoExecute=false);
medium=含中风险自动;all=全自动无审批(完全 AI 接管,选时二次确认) -->
<SettingRow ref="rowAutoExecute" :label="$t('settings.labelAutoExecute')" :desc="$t('settings.descAutoExecute')">
<label class="toggle">
<input type="checkbox" v-model="settings.autoExecute" @change="markSaved('autoExecute')" />
<span class="toggle-slider"></span>
</label>
<select v-model="settings.autoExecuteMode" class="setting-select" @change="onAutoModeChange">
<option value="low">{{ $t('settings.autoModeLow') }}</option>
<option value="medium">{{ $t('settings.autoModeMedium') }}</option>
<option value="all">{{ $t('settings.autoModeAll') }}</option>
</select>
</SettingRow>
<SettingRow ref="rowLogLevel" :label="$t('settings.labelLogLevel')" :desc="$t('settings.descLogLevel')">
<select v-model="settings.logLevel" class="setting-select" @change="markSaved('logLevel')">
<select v-model="settings.logLevel" class="setting-select" @change="markSaved">
<option value="error">Error</option>
<option value="warn">Warning</option>
<option value="info">Info</option>
@@ -20,35 +23,130 @@
</select>
</SettingRow>
</div>
<!-- all 二次确认弹层(独立,不与 Settings 壳层共用:壳层按钮写死删除不适合确认接管) -->
<Transition name="confirm">
<div v-if="confirmState.visible" class="confirm-mask" @click.self="answerConfirm(false)">
<div class="confirm-box">
<div class="confirm-msg">{{ confirmState.msg }}</div>
<div class="confirm-actions">
<button class="btn btn-ghost btn-sm" @click="answerConfirm(false)">{{ $t('common.cancel') }}</button>
<button class="btn btn-danger btn-sm" @click="answerConfirm(true)">{{ confirmState.dangerLabel || $t('common.confirm') }}</button>
</div>
</div>
</div>
</Transition>
</section>
</template>
<script setup lang="ts">
import { reactive, useTemplateRef } from 'vue'
import { reactive, watch, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { useConfirm } from '@/composables/useConfirm'
import { useAppSettingsStore } from '@/stores/appSettings'
import SettingRow from './SettingRow.vue'
// ============================================================
// 高级域 — AI 自动执行开关 / 日志级别
// 拆自 GeneralPanel,忠实迁移原逻辑:原 GeneralPanel 这两项为 session 内 reactive
// (无 appSettings 持久化/watch),此处保持一致(无 watch/IPC),避免阶段2 引入行为变更
// 阶段5:SettingRow 封装行布局,控件变更后调 markSaved 显"已保存"
// 后续若需持久化,单独建改进项
// 高级域 — AI 自动执行范围(三档) / 日志级别
// F-#97(2026-06-22):原 autoExecute boolean checkbox → autoExecuteMode select 三档
// (low/medium/all),KV 持久化 df-ai-auto-execute-mode,默认 low
// low 必须等价旧行为(autoExecute=false 仅 Low 风险自动):后端 audit/mod.rs 按 mode
// 判定 Med/High 是否走自动分支,low 时 Med/High 仍进审批块,行为与旧完全一致
// all(完全接管)选时二次确认:防误选致无审批全自动执行高危操作。
// ============================================================
const { t } = useI18n()
const appSettings = useAppSettingsStore()
// useConfirm 真实 API:返回 { confirmState, confirmDialog(msg, dangerLabel), answerConfirm(ok) }
// confirmDialog 返回 Promise<boolean>(true=确认,false=取消),无 title/content/confirmText/cancelText
// 字段。此处用 msg 拼标题+内容,dangerLabel 作确认按钮文案,自渲染独立弹层(上方模板)。
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
// 三档合法值 + 脏值白名单兜底:KV 被外部改库为非法值(空串/非三档)时回退 low,
// 防 <select v-model> 无匹配 option 致控件空白(后端 audit 脏值 fail-safe 降级审批,前端需对齐可视)。
const VALID_MODES = ['low', 'medium', 'all'] as const
type AutoMode = (typeof VALID_MODES)[number]
function clampMode(raw: string): AutoMode {
return (VALID_MODES as readonly string[]).includes(raw) ? (raw as AutoMode) : 'low'
}
const settings = reactive({
autoExecute: false,
// 三档 low/medium/all,默认 low(=旧 autoExecute=false 现状),KV 持久化 df-ai-auto-execute-mode。
// clampMode 兜底脏值。
autoExecuteMode: clampMode(appSettings.get<string>('df-ai-auto-execute-mode', 'low')),
logLevel: 'info',
})
const rowAutoExecute = useTemplateRef<InstanceType<typeof SettingRow>>('rowAutoExecute')
const rowLogLevel = useTemplateRef<InstanceType<typeof SettingRow>>('rowLogLevel')
function markSaved(key: 'autoExecute' | 'logLevel') {
const map = { autoExecute: rowAutoExecute.value, logLevel: rowLogLevel.value }
map[key]?.markSaved()
// 上一次确认生效的档位:all 二次确认取消时恢复此值(非写死 low),避免 medium 用户误选 all
// 取消后被强制降级 low。初始化 = 当前有效值;每次确认成功(含非 all 切换)更新。
let lastConfirmedMode: AutoMode = settings.autoExecuteMode
// all(完全接管)二次确认:防误选致无审批全自动执行高危操作(删文件/执行任意命令)
// 选 all → 弹确认 → 确认落 KV + markSaved + 更新 lastConfirmedMode;取消恢复 lastConfirmedMode(不落 KV)。
// 非 all 切换:直接落 KV + 更新 lastConfirmedMode(watch 兜底同值无副作用)。
async function onAutoModeChange() {
if (settings.autoExecuteMode === 'all') {
const msg = t('settings.autoModeAllConfirmTitle') + '\n\n' + t('settings.autoModeAllConfirmContent')
const ok = await confirmDialog(msg, t('common.confirm'))
if (!ok) {
settings.autoExecuteMode = lastConfirmedMode // 取消恢复原档位(非写死 low)
return
}
}
appSettings.set('df-ai-auto-execute-mode', settings.autoExecuteMode)
lastConfirmedMode = settings.autoExecuteMode
rowAutoExecute.value?.markSaved()
}
// watch 兜底:非 all 切换直接落 KV + 更新 lastConfirmedMode(all 走 onAutoModeChange 确认后落,
// 避免 watch 抢先持久化未确认的 all 值)。onAutoModeChange 已落 KV 时此 watch 重复 set 同值无副作用。
watch(
() => settings.autoExecuteMode,
(v) => {
if (v !== 'all') {
appSettings.set('df-ai-auto-execute-mode', v)
lastConfirmedMode = v
}
},
)
// logLevel 行即时反馈(无参:logLevel 仅 session 内 reactive 无持久化,markSaved 只显「已保存」反馈)
function markSaved() {
rowLogLevel.value?.markSaved()
}
</script>
<style scoped>
/* 阶段5 UX 重构:复用 settings.css 全局 .panel/.panel-header/.general-settings/
.setting-select/.toggle/.toggle-slider + SettingRow(封装 .setting-row/.setting-info/
.setting-label/.setting-desc/.setting-control)。本 Section 无特有 scoped 样式。 */
.setting-select + SettingRow(封装 .setting-row/.setting-info/.setting-label/
.setting-desc/.setting-control)。本 Section 无特有 scoped 样式。
F-#97 all 二次确认弹层:复用 Settings.vue 壳层 confirm 弹层同款样式(mask/box/msg/
actions/transition),此处独立持有 useConfirm 状态(不与壳层共用,壳层按钮写死「删除」)。 */
.confirm-mask {
position: fixed;
inset: 0;
z-index: 1100;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
}
.confirm-box {
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-lg);
padding: 20px;
min-width: 280px;
max-width: 420px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.confirm-msg { font-size: 13px; color: var(--df-text); line-height: 1.5; margin-bottom: 16px; white-space: pre-line; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
.btn-danger { background: var(--df-danger); color: #fff; }
.btn-danger:hover { filter: brightness(1.1); }
.confirm-enter-active, .confirm-leave-active { transition: opacity 0.15s; }
.confirm-enter-from, .confirm-leave-to { opacity: 0; }
</style>

View File

@@ -54,7 +54,7 @@
// knowledge 知识库(auto_extract/trigger_mode/embedding_*)
// performance 性能(global/per-conv concurrency/max-iterations/max-retries)
// security 安全(allowed_dirs)
// advanced 高级(autoExecute/logLevel + 连接)
// advanced 高级(autoExecuteMode/logLevel + 连接)
// ============================================================
import { computed, ref, watch } from 'vue'

View File

@@ -42,7 +42,7 @@ export interface SettingIndexEntry {
* performance 性能(global-concurrency/per-conv-concurrency/
* max-iterations/max-retries)
* security 安全(allowed_dirs)
* advanced 高级(autoExecute/logLevel + 连接)
* advanced 高级(autoExecuteMode/logLevel + 连接)
*/
export const SETTINGS_INDEX: SettingIndexEntry[] = [
// ===== 外观 =====
@@ -82,7 +82,8 @@ export const SETTINGS_INDEX: SettingIndexEntry[] = [
{ category: 'security', key: 'allowedDirs', labelKey: 'settings.panelAllowedDirs', descKey: 'settings.descAllowedDirs' },
// ===== 高级 =====
{ category: 'advanced', key: 'autoExecute', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' },
// F-#97:autoExecute(boolean) → autoExecuteMode(low/medium/all 三档),key 同步更名
{ category: 'advanced', key: 'autoExecuteMode', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' },
{ category: 'advanced', key: 'logLevel', labelKey: 'settings.labelLogLevel', descKey: 'settings.descLogLevel' },
{ category: 'advanced', key: 'connection', labelKey: 'settings.panelConnection', descKey: 'settings.emptyConnection' },
]