优化: Settings 前端(Provider 模型表格化手动标注 + 布局重构 + 连接密码字段)
This commit is contained in:
@@ -6,30 +6,49 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="general-settings">
|
<div class="general-settings">
|
||||||
<!-- ═══ 应用数据目录(运行期确定,跨平台) ═══ -->
|
<!-- ═══ 应用数据目录(运行期确定,跨平台) ═══ -->
|
||||||
<SettingRow :label="$t('settings.labelDataDir')" :desc="$t('settings.descDataDir')">
|
<!-- 布局与 select 行统一:label 左 + 路径居右同一行,desc 在 label 下方 -->
|
||||||
|
<div class="setting-row inline-control-row">
|
||||||
|
<div class="inline-head">
|
||||||
|
<span class="setting-label">{{ $t('settings.labelDataDir') }}</span>
|
||||||
<span class="data-dir-path" :title="dataDir">{{ dataDir || $t('settings.loading') }}</span>
|
<span class="data-dir-path" :title="dataDir">{{ dataDir || $t('settings.loading') }}</span>
|
||||||
</SettingRow>
|
</div>
|
||||||
|
<span class="setting-desc">{{ $t('settings.descDataDir') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- F-#97:三档 low/medium/all。low=仅低风险自动(等价旧 autoExecute=false);
|
<!-- F-#97:三档 low/medium/all。low=仅低风险自动(等价旧 autoExecute=false);
|
||||||
medium=含中风险自动;all=全自动无审批(完全 AI 接管,选时二次确认) -->
|
medium=含中风险自动;all=全自动无审批(完全 AI 接管,选时二次确认) -->
|
||||||
<SettingRow ref="rowAutoExecute" :label="$t('settings.labelAutoExecute')" :desc="$t('settings.descAutoExecute')">
|
<!-- inline-control-row:自定义布局——label 与 select 同一行(靠左紧挨,顶对齐),
|
||||||
|
desc 换行到整行下方。不复用 SettingRow(grid/contents 方案渲染错乱)。 -->
|
||||||
|
<div class="setting-row inline-control-row">
|
||||||
|
<div class="inline-head">
|
||||||
|
<span class="setting-label">{{ $t('settings.labelAutoExecute') }}</span>
|
||||||
<select v-model="settings.autoExecuteMode" class="setting-select" @change="onAutoModeChange">
|
<select v-model="settings.autoExecuteMode" class="setting-select" @change="onAutoModeChange">
|
||||||
<option value="low">{{ $t('settings.autoModeLow') }}</option>
|
<option value="low">{{ $t('settings.autoModeLow') }}</option>
|
||||||
<option value="medium">{{ $t('settings.autoModeMedium') }}</option>
|
<option value="medium">{{ $t('settings.autoModeMedium') }}</option>
|
||||||
<option value="all">{{ $t('settings.autoModeAll') }}</option>
|
<option value="all">{{ $t('settings.autoModeAll') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</SettingRow>
|
<span v-if="savedVisible.autoExecute" class="saved-hint">{{ $t('settings.savedHint') }}</span>
|
||||||
<SettingRow ref="rowLogLevel" :label="$t('settings.labelLogLevel')" :desc="$t('settings.descLogLevel')">
|
</div>
|
||||||
|
<span class="setting-desc">{{ $t('settings.descAutoExecute') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row inline-control-row">
|
||||||
|
<div class="inline-head">
|
||||||
|
<span class="setting-label">{{ $t('settings.labelLogLevel') }}</span>
|
||||||
<select v-model="settings.logLevel" class="setting-select" @change="onLogLevelChange">
|
<select v-model="settings.logLevel" class="setting-select" @change="onLogLevelChange">
|
||||||
<option value="error">Error</option>
|
<option value="error">Error</option>
|
||||||
<option value="warn">Warning</option>
|
<option value="warn">Warning</option>
|
||||||
<option value="info">Info</option>
|
<option value="info">Info</option>
|
||||||
<option value="debug">Debug</option>
|
<option value="debug">Debug</option>
|
||||||
</select>
|
</select>
|
||||||
</SettingRow>
|
<span v-if="savedVisible.logLevel" class="saved-hint">{{ $t('settings.savedHint') }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="setting-desc">{{ $t('settings.descLogLevel') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 审批超时:用户未处理审批弹窗时的自动拒绝阅值;0 表示不限时(对齐旧默认) -->
|
<!-- 审批超时:用户未处理审批弹窗时的自动拒绝阅值;0 表示不限时(对齐旧默认) -->
|
||||||
<SettingRow ref="rowApprovalTimeout" :label="$t('settings.labelApprovalTimeout')" :desc="$t('settings.descApprovalTimeout')">
|
<div class="setting-row inline-control-row">
|
||||||
|
<div class="inline-head">
|
||||||
|
<span class="setting-label">{{ $t('settings.labelApprovalTimeout') }}</span>
|
||||||
<select v-model="settings.approvalTimeout" class="setting-select" @change="onApprovalTimeoutChange">
|
<select v-model="settings.approvalTimeout" class="setting-select" @change="onApprovalTimeoutChange">
|
||||||
<option :value="0">{{ $t('settings.approvalTimeoutUnlimited') }}</option>
|
<option :value="0">{{ $t('settings.approvalTimeoutUnlimited') }}</option>
|
||||||
<option :value="300000">{{ $t('settings.approvalTimeout5m') }}</option>
|
<option :value="300000">{{ $t('settings.approvalTimeout5m') }}</option>
|
||||||
@@ -37,9 +56,13 @@
|
|||||||
<option :value="1800000">{{ $t('settings.approvalTimeout30m') }}</option>
|
<option :value="1800000">{{ $t('settings.approvalTimeout30m') }}</option>
|
||||||
<option :value="3600000">{{ $t('settings.approvalTimeout60m') }}</option>
|
<option :value="3600000">{{ $t('settings.approvalTimeout60m') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</SettingRow>
|
<span v-if="savedVisible.approvalTimeout" class="saved-hint">{{ $t('settings.savedHint') }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="setting-desc">{{ $t('settings.descApprovalTimeout') }}</span>
|
||||||
|
</div>
|
||||||
<!-- 自定义提示词:注入到 AI 系统 prompt 末尾,用户可自定义 AI 行为偏好 -->
|
<!-- 自定义提示词:注入到 AI 系统 prompt 末尾,用户可自定义 AI 行为偏好 -->
|
||||||
<SettingRow ref="rowCustomPrompt" :label="$t('settings.labelCustomPrompt')" :desc="$t('settings.descCustomPrompt')">
|
<!-- 布局:整行堆叠——label/desc 在上,textarea 独立一行从左到右拉满(而非 label 左 + 框右) -->
|
||||||
|
<SettingRow ref="rowCustomPrompt" class="prompt-row" :label="$t('settings.labelCustomPrompt')" :desc="$t('settings.descCustomPrompt')">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="customPrompt"
|
v-model="customPrompt"
|
||||||
class="setting-textarea"
|
class="setting-textarea"
|
||||||
@@ -130,10 +153,22 @@ const settings = reactive({
|
|||||||
})
|
})
|
||||||
const customPrompt = ref(appSettings.get<string>('custom_prompt', ''))
|
const customPrompt = ref(appSettings.get<string>('custom_prompt', ''))
|
||||||
|
|
||||||
const rowAutoExecute = useTemplateRef<InstanceType<typeof SettingRow>>('rowAutoExecute')
|
// 三个 select 行改为自定义布局(不复用 SettingRow),saved-hint 由本地 savedVisible 状态驱动
|
||||||
const rowLogLevel = useTemplateRef<InstanceType<typeof SettingRow>>('rowLogLevel')
|
|
||||||
const rowCustomPrompt = useTemplateRef<InstanceType<typeof SettingRow>>('rowCustomPrompt')
|
const rowCustomPrompt = useTemplateRef<InstanceType<typeof SettingRow>>('rowCustomPrompt')
|
||||||
const rowApprovalTimeout = useTemplateRef<InstanceType<typeof SettingRow>>('rowApprovalTimeout')
|
|
||||||
|
// 「已保存」提示可见性(2s 自动隐藏),key 对齐模板三行
|
||||||
|
const savedVisible = reactive<Record<string, boolean>>({
|
||||||
|
autoExecute: false,
|
||||||
|
logLevel: false,
|
||||||
|
approvalTimeout: false,
|
||||||
|
})
|
||||||
|
const savedTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||||
|
function flashSaved(key: string) {
|
||||||
|
savedVisible[key] = true
|
||||||
|
const prev = savedTimers.get(key)
|
||||||
|
if (prev) clearTimeout(prev)
|
||||||
|
savedTimers.set(key, setTimeout(() => { savedVisible[key] = false }, 2000))
|
||||||
|
}
|
||||||
|
|
||||||
// 上一次确认生效的档位:all 二次确认取消时恢复此值(非写死 low),避免 medium 用户误选 all
|
// 上一次确认生效的档位:all 二次确认取消时恢复此值(非写死 low),避免 medium 用户误选 all
|
||||||
// 取消后被强制降级 low。初始化 = 当前有效值;每次确认成功(含非 all 切换)更新。
|
// 取消后被强制降级 low。初始化 = 当前有效值;每次确认成功(含非 all 切换)更新。
|
||||||
@@ -153,7 +188,7 @@ async function onAutoModeChange() {
|
|||||||
}
|
}
|
||||||
appSettings.set('df-ai-auto-execute-mode', settings.autoExecuteMode)
|
appSettings.set('df-ai-auto-execute-mode', settings.autoExecuteMode)
|
||||||
lastConfirmedMode = settings.autoExecuteMode
|
lastConfirmedMode = settings.autoExecuteMode
|
||||||
rowAutoExecute.value?.markSaved()
|
flashSaved('autoExecute')
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch 兜底:非 all 切换直接落 KV + 更新 lastConfirmedMode(all 走 onAutoModeChange 确认后落,
|
// watch 兜底:非 all 切换直接落 KV + 更新 lastConfirmedMode(all 走 onAutoModeChange 确认后落,
|
||||||
@@ -174,7 +209,7 @@ watch(
|
|||||||
// (本任务范围只修「假保存」,运行时生效链路另行评估,不擅自扩展)。
|
// (本任务范围只修「假保存」,运行时生效链路另行评估,不擅自扩展)。
|
||||||
function onLogLevelChange() {
|
function onLogLevelChange() {
|
||||||
void appSettings.set('df-log-level', settings.logLevel)
|
void appSettings.set('df-log-level', settings.logLevel)
|
||||||
rowLogLevel.value?.markSaved()
|
flashSaved('logLevel')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下一笔发起的审批即生效(aiShared.startApprovalTimer 每次实时读),已在跑的计时器沿用旧值。
|
// 下一笔发起的审批即生效(aiShared.startApprovalTimer 每次实时读),已在跑的计时器沿用旧值。
|
||||||
@@ -185,7 +220,7 @@ function onCustomPromptChange() {
|
|||||||
|
|
||||||
function onApprovalTimeoutChange() {
|
function onApprovalTimeoutChange() {
|
||||||
appSettings.set('df-approval-timeout', settings.approvalTimeout)
|
appSettings.set('df-approval-timeout', settings.approvalTimeout)
|
||||||
rowApprovalTimeout.value?.markSaved()
|
flashSaved('approvalTimeout')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -197,9 +232,47 @@ function onApprovalTimeoutChange() {
|
|||||||
F-#97 all 二次确认弹层:复用 Settings.vue 壳层 confirm 弹层同款样式(mask/box/msg/
|
F-#97 all 二次确认弹层:复用 Settings.vue 壳层 confirm 弹层同款样式(mask/box/msg/
|
||||||
actions/transition),此处独立持有 useConfirm 状态(不与壳层共用,壳层按钮写死「删除」)。 */
|
actions/transition),此处独立持有 useConfirm 状态(不与壳层共用,壳层按钮写死「删除」)。 */
|
||||||
.data-dir-path {
|
.data-dir-path {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 24px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-family: var(--df-font-mono);
|
||||||
color: var(--df-text-dim);
|
color: var(--df-text-dim);
|
||||||
|
text-align: right;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
/* 自定义提示词行:整行堆叠,textarea 独立一行从左到右拉满。
|
||||||
|
覆盖 SettingRow 默认横排(label 左 + control 右)。 */
|
||||||
|
.prompt-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.prompt-row .setting-info { align-items: flex-start; }
|
||||||
|
.prompt-row .setting-control { align-items: flex-start; }
|
||||||
|
/* select 类设置行(自定义布局,不复用 SettingRow):label 与 select 同一行靠左顶对齐,
|
||||||
|
desc 换行到整行下方。
|
||||||
|
┌───────────────────────────────┐
|
||||||
|
│ label [select] ✓已保存 │
|
||||||
|
│ desc(介绍信息,整行) │
|
||||||
|
└───────────────────────────────┘ */
|
||||||
|
.inline-control-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.inline-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.inline-head .setting-label { flex: 0 0 auto; }
|
||||||
|
.inline-head .setting-select { flex: 0 0 auto; margin-left: auto; }
|
||||||
|
.saved-hint {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--df-accent);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.confirm-mask {
|
.confirm-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -43,25 +43,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label class="form-label">{{ $t('settings.labelType') }}</label>
|
<label class="form-label">{{ $t('settings.labelType') }}</label>
|
||||||
<select v-model="connForm.type" class="setting-select">
|
<select v-model="connForm.type" class="setting-select" @change="onTypeChange">
|
||||||
<option value="mysql">MySQL</option>
|
<option value="mysql">MySQL</option>
|
||||||
<option value="ssh">SSH</option>
|
<option value="ssh">SSH</option>
|
||||||
<option value="redis">Redis</option>
|
<option value="redis">Redis</option>
|
||||||
<option value="mongo">MongoDB</option>
|
<option value="mongo">MongoDB</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<!-- host + port 同一行(端口跟在 host 后面,符合 host:port 地址习惯) -->
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field form-field--host">
|
||||||
<label class="form-label">{{ $t('settings.labelHost') }}</label>
|
<label class="form-label">{{ $t('settings.labelHost') }}</label>
|
||||||
<input v-model="connForm.host" class="setting-input" :placeholder="$t('settings.phHost')" />
|
<input v-model="connForm.host" class="setting-input" :placeholder="$t('settings.phHost')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="form-field form-field--port">
|
||||||
<label class="form-label">{{ $t('settings.labelPort') }}</label>
|
<label class="form-label">{{ $t('settings.labelPort') }}</label>
|
||||||
<input v-model.number="connForm.port" class="setting-input" type="number" min="1" max="65535" :placeholder="$t('settings.phPort')" />
|
<input v-model.number="connForm.port" class="setting-number" type="number" min="1" max="65535" :placeholder="$t('settings.phPort')" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label class="form-label">{{ $t('settings.labelUser') }}</label>
|
<label class="form-label">{{ $t('settings.labelUser') }}</label>
|
||||||
<input v-model="connForm.user" class="setting-input" :placeholder="$t('settings.phUser')" />
|
<input v-model="connForm.user" class="setting-input" :placeholder="$t('settings.phUser')" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">{{ $t('settings.labelPassword') }}</label>
|
||||||
|
<input v-model="connForm.password" class="setting-input" type="password" :placeholder="$t('settings.phPassword')" autocomplete="new-password" />
|
||||||
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="btn btn-primary" :disabled="submitting" @click="saveConn">{{ $t('common.save') }}</button>
|
<button class="btn btn-primary" :disabled="submitting" @click="saveConn">{{ $t('common.save') }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,18 +103,23 @@ interface ConnRecord {
|
|||||||
host: string
|
host: string
|
||||||
port: number
|
port: number
|
||||||
user: string
|
user: string
|
||||||
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const connections = ref<ConnRecord[]>([])
|
const connections = ref<ConnRecord[]>([])
|
||||||
|
|
||||||
|
// 各类型连接的默认端口(用户切换类型时自动带入,单一来源)
|
||||||
|
const DEFAULT_PORTS: Record<string, number> = { mysql: 3306, ssh: 22, redis: 6379, mongo: 27017 }
|
||||||
|
|
||||||
const connForm = reactive({
|
const connForm = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
editId: '' as string,
|
editId: '' as string,
|
||||||
name: '',
|
name: '',
|
||||||
type: 'mysql',
|
type: 'mysql',
|
||||||
host: '',
|
host: '',
|
||||||
port: 3306,
|
port: DEFAULT_PORTS.mysql,
|
||||||
user: '',
|
user: '',
|
||||||
|
password: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 异步操作禁用态(防双击重复提交):saveConn 保存连接期间禁用按钮
|
// 异步操作禁用态(防双击重复提交):saveConn 保存连接期间禁用按钮
|
||||||
@@ -142,17 +154,25 @@ function openConnForm(conn?: ConnRecord) {
|
|||||||
connForm.host = conn.host
|
connForm.host = conn.host
|
||||||
connForm.port = conn.port
|
connForm.port = conn.port
|
||||||
connForm.user = conn.user
|
connForm.user = conn.user
|
||||||
|
connForm.password = conn.password ?? ''
|
||||||
} else {
|
} else {
|
||||||
connForm.editId = ''
|
connForm.editId = ''
|
||||||
connForm.name = ''
|
connForm.name = ''
|
||||||
connForm.type = 'mysql'
|
connForm.type = 'mysql'
|
||||||
connForm.host = ''
|
connForm.host = ''
|
||||||
connForm.port = 3306
|
connForm.port = DEFAULT_PORTS[connForm.type] ?? 3306
|
||||||
connForm.user = ''
|
connForm.user = ''
|
||||||
|
connForm.password = ''
|
||||||
}
|
}
|
||||||
connForm.visible = true
|
connForm.visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 用户手动切换连接类型时,端口自动重置为该类型默认端口。
|
||||||
|
* 用 @change(仅用户交互触发)而非 watch:避免编辑已有连接回填类型时误覆盖已存端口。 */
|
||||||
|
function onTypeChange(): void {
|
||||||
|
connForm.port = DEFAULT_PORTS[connForm.type] ?? 3306
|
||||||
|
}
|
||||||
|
|
||||||
function saveConn() {
|
function saveConn() {
|
||||||
if (!connForm.name || !connForm.host) {
|
if (!connForm.name || !connForm.host) {
|
||||||
emit('toast', t('settings.toastConnIncomplete'), 'warning')
|
emit('toast', t('settings.toastConnIncomplete'), 'warning')
|
||||||
@@ -173,6 +193,7 @@ function saveConn() {
|
|||||||
host: connForm.host,
|
host: connForm.host,
|
||||||
port: connForm.port,
|
port: connForm.port,
|
||||||
user: connForm.user,
|
user: connForm.user,
|
||||||
|
password: connForm.password,
|
||||||
}
|
}
|
||||||
if (connForm.editId) {
|
if (connForm.editId) {
|
||||||
const idx = connections.value.findIndex(c => c.id === connForm.editId)
|
const idx = connections.value.findIndex(c => c.id === connForm.editId)
|
||||||
@@ -269,10 +290,18 @@ onMounted(loadConnections)
|
|||||||
|
|
||||||
/* 表单(本面板特有输入控件样式;.setting-select 走全局) */
|
/* 表单(本面板特有输入控件样式;.setting-select 走全局) */
|
||||||
.form-grid { display: flex; flex-direction: column; gap: 14px; }
|
.form-grid { display: flex; flex-direction: column; gap: 14px; }
|
||||||
.form-field { display: flex; flex-direction: column; gap: 4px; }
|
.form-field { display: flex; flex-direction: row; align-items: center; gap: 10px; }
|
||||||
.form-label { font-size: 12px; font-weight: 500; color: var(--df-text-secondary); }
|
.form-label { flex: 0 0 44px; font-size: 12px; font-weight: 500; color: var(--df-text-secondary); text-align: right; }
|
||||||
.form-actions { display: flex; justify-content: flex-end; padding-top: 4px; }
|
.form-actions { display: flex; justify-content: flex-end; padding-top: 4px; }
|
||||||
|
|
||||||
.setting-input { padding: 6px 12px; background: var(--df-bg); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); color: var(--df-text); font-size: 13px; outline: none; min-width: 260px; }
|
.setting-input { flex: 1; min-width: 0; padding: 6px 12px; background: var(--df-bg); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); color: var(--df-text); font-size: 13px; outline: none; }
|
||||||
.setting-input:focus { border-color: var(--df-accent); }
|
.setting-input:focus { border-color: var(--df-accent); }
|
||||||
|
.form-field .setting-select { flex: 1; min-width: 0; }
|
||||||
|
/* host+port 组合行(端口跟在 host 后;min-width:0 允许收缩防窄面板溢出,label 已缩窄控制距离) */
|
||||||
|
.form-row { display: flex; gap: 6px; }
|
||||||
|
.form-row .form-field { min-width: 0; }
|
||||||
|
.form-row .form-field--host { flex: 3; }
|
||||||
|
.form-row .form-field--port { flex: 2; }
|
||||||
|
/* port 数字框复用 .setting-number(隐藏原生箭头);在 form-row 内覆盖 flex 填满列宽 */
|
||||||
|
.form-row .setting-number { flex: 1; min-width: 0; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -119,26 +119,56 @@
|
|||||||
<!-- ═══ 拉取到的模型列表(F-01 阶段6)═══ -->
|
<!-- ═══ 拉取到的模型列表(F-01 阶段6)═══ -->
|
||||||
<div class="form-field form-field--full" v-if="providerForm.models.length > 0">
|
<div class="form-field form-field--full" v-if="providerForm.models.length > 0">
|
||||||
<label class="form-label">{{ $t('settings.modelListTitle', { count: providerForm.models.length }) }}</label>
|
<label class="form-label">{{ $t('settings.modelListTitle', { count: providerForm.models.length }) }}</label>
|
||||||
<div class="model-list">
|
<table class="model-table">
|
||||||
<div class="model-row" v-for="m in providerForm.models" :key="m.model_id">
|
<thead>
|
||||||
<div class="model-row-main">
|
<tr>
|
||||||
|
<th class="col-enabled">{{ $t('settings.colEnabled') }}</th>
|
||||||
|
<th class="col-model">{{ $t('settings.colModel') }}</th>
|
||||||
|
<th class="col-ability">{{ $t('settings.colAbility') }}</th>
|
||||||
|
<th class="col-weight">{{ $t('settings.labelWeight') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="m in providerForm.models" :key="m.model_id">
|
||||||
|
<td class="col-enabled">
|
||||||
<label class="toggle toggle--sm">
|
<label class="toggle toggle--sm">
|
||||||
<input type="checkbox" v-model="m.enabled" />
|
<input type="checkbox" v-model="m.enabled" />
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
|
</td>
|
||||||
|
<td class="col-model">
|
||||||
<span class="model-id">{{ m.model_id }}</span>
|
<span class="model-id">{{ m.model_id }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="col-ability">
|
||||||
|
<!-- 能力/模态标签(可编辑:点 × 删除;点 + 添加;手动改后标为「手动」) -->
|
||||||
<div class="model-tags">
|
<div class="model-tags">
|
||||||
<span v-for="md in m.modalities" :key="'md-' + md" class="tag tag-modality">{{ $t('settings.tagModality.' + md) }}</span>
|
<span v-for="md in m.modalities" v-show="!redundantTags.modalities.has(md)" :key="'md-' + md"
|
||||||
<span v-for="cap in m.capabilities" :key="'cap-' + cap" class="tag tag-capability">{{ $t('settings.tagCapability.' + cap) }}</span>
|
class="tag tag-modality" :title="$t('settings.tagTooltipModality')">
|
||||||
<span v-if="m.probe_source" class="tag tag-source">{{ $t('settings.tagSource.' + m.probe_source) }}</span>
|
{{ $t('settings.tagModality.' + md) }}<span class="tag-remove" @click.stop="removeModality(m, md)">×</span>
|
||||||
|
</span>
|
||||||
|
<span v-for="cap in m.capabilities" v-show="!redundantTags.capabilities.has(cap)" :key="'cap-' + cap"
|
||||||
|
class="tag tag-capability" :title="$t('settings.tagTooltipCapability')">
|
||||||
|
{{ $t('settings.tagCapability.' + cap) }}<span class="tag-remove" @click.stop="removeCapability(m, cap)">×</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="m.probe_source && m.probe_source !== 'user_set'" v-show="!redundantTags.sources.has(m.probe_source)"
|
||||||
|
class="tag tag-source" :title="$t('settings.tagTooltipSource')">{{ $t('settings.tagSource.' + m.probe_source) }}</span>
|
||||||
|
<span v-if="m.probe_source === 'user_set'" class="tag tag-source" :title="$t('settings.tagTooltipSource')">{{ $t('settings.tagSource.user_set') }}</span>
|
||||||
|
<span class="tag-add" :title="$t('settings.addTagTitle')" @click.stop="startAddTag(m)">+</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 添加能力内联菜单(点+展开;选未有的能力/模态即添加) -->
|
||||||
|
<div v-if="addingTagModel === m.model_id" class="add-tag-menu">
|
||||||
|
<span v-for="opt in addableTags(m)" :key="opt.kind + '-' + opt.key"
|
||||||
|
class="tag" :class="opt.kind === 'modality' ? 'tag-modality' : 'tag-capability'"
|
||||||
|
@click.stop="addTag(m, opt.key, opt.kind)">{{ opt.label }}</span>
|
||||||
|
<span v-if="addableTags(m).length === 0" class="add-tag-empty">{{ $t('settings.addTagNone') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-row-side">
|
</td>
|
||||||
<label class="model-weight-label">{{ $t('settings.labelWeight') }}</label>
|
<td class="col-weight">
|
||||||
<input type="number" class="setting-input setting-input--sm" min="0" max="100" v-model.number="m.weight" />
|
<input type="number" class="setting-input setting-input--sm" min="0" max="100" v-model.number="m.weight" />
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
<div class="model-list-hint">{{ $t('settings.modelListHint') }}</div>
|
<div class="model-list-hint">{{ $t('settings.modelListHint') }}</div>
|
||||||
<!-- M32: 模型列表 enabled/weight 改了未保存时显提示(关闭/切走将丢弃) -->
|
<!-- M32: 模型列表 enabled/weight 改了未保存时显提示(关闭/切走将丢弃) -->
|
||||||
<div v-if="isFormDirty" class="model-list-unsaved-hint">{{ $t('settings.modelListUnsavedHint') }}</div>
|
<div v-if="isFormDirty" class="model-list-unsaved-hint">{{ $t('settings.modelListUnsavedHint') }}</div>
|
||||||
@@ -151,7 +181,7 @@
|
|||||||
import { reactive, ref, computed, onMounted } from 'vue'
|
import { reactive, ref, computed, onMounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { aiApi } from '@/api'
|
import { aiApi } from '@/api'
|
||||||
import type { AiProviderConfig, ModelConfig } from '@/api/types'
|
import type { AiProviderConfig, ModelConfig, Modality, Capability, ProbeSource } from '@/api/types'
|
||||||
import { PROVIDER_PRESETS, CUSTOM_PRESET_ID, findPreset } from './providerPresets'
|
import { PROVIDER_PRESETS, CUSTOM_PRESET_ID, findPreset } from './providerPresets'
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -208,6 +238,35 @@ const providerForm = reactive({
|
|||||||
savedSnapshot: '' as string,
|
savedSnapshot: '' as string,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冗余标签判定:全模型共有的 modality/capability/probe_source 无区分度,
|
||||||
|
* 每行都渲染会刷屏(如全部命中预设表 → 全是「预设」;对话模型默认 ToolUse → 全是「工具调用」)。
|
||||||
|
* 计算「所有模型都含」的值集合,模板渲染时跳过 → 只显示有区分度的标签。
|
||||||
|
*/
|
||||||
|
const redundantTags = computed(() => {
|
||||||
|
const models = providerForm.models
|
||||||
|
if (models.length === 0) return { modalities: new Set<string>(), capabilities: new Set<string>(), sources: new Set<string>() }
|
||||||
|
const common = (pick: (m: ModelConfig) => string[]) => {
|
||||||
|
const sets = models.map(m => new Set(pick(m)))
|
||||||
|
const first = sets[0]
|
||||||
|
const result = new Set<string>()
|
||||||
|
for (const v of first) {
|
||||||
|
if (sets.every(s => s.has(v))) result.add(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modalities: common(m => m.modalities),
|
||||||
|
capabilities: common(m => m.capabilities),
|
||||||
|
// 探测来源:全模型相同的值也冗余(如全 PresetTable)
|
||||||
|
sources: (() => {
|
||||||
|
const vals = models.map(m => m.probe_source).filter((v): v is ProbeSource => !!v)
|
||||||
|
if (vals.length < 2) return new Set<string>()
|
||||||
|
return new Set([...new Set(vals)].filter(v => vals.every(x => x === v)))
|
||||||
|
})(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* L19/M32: 表单脏判定——当前表单状态与已落库快照不一致即脏。
|
* L19/M32: 表单脏判定——当前表单状态与已落库快照不一致即脏。
|
||||||
* 触发场景:① 取消/关闭有改动的表单 → 二次确认放弃(L19);
|
* 触发场景:① 取消/关闭有改动的表单 → 二次确认放弃(L19);
|
||||||
@@ -231,10 +290,67 @@ function formBusinessSnapshot(): string {
|
|||||||
model_id: m.model_id,
|
model_id: m.model_id,
|
||||||
enabled: m.enabled,
|
enabled: m.enabled,
|
||||||
weight: m.weight,
|
weight: m.weight,
|
||||||
|
modalities: [...m.modalities],
|
||||||
|
capabilities: [...m.capabilities],
|
||||||
|
probe_source: m.probe_source,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 模型能力手动标注(表格内编辑 tags;改动即标 UserSet,手动优先)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/** 当前展开「添加能力」菜单的模型名;null=无展开 */
|
||||||
|
const addingTagModel = ref<string | null>(null)
|
||||||
|
|
||||||
|
/** 可添加的模态(与 tagModality 对齐;text 是对话基础模态,允许删但一般不主动加) */
|
||||||
|
const ADDABLE_MODALITIES: Modality[] = ['text', 'vision', 'audio', 'video']
|
||||||
|
/** 可添加的能力 */
|
||||||
|
const ADDABLE_CAPABILITIES: Capability[] = ['tool_use', 'embedding', 'code_gen']
|
||||||
|
|
||||||
|
/** 某模型可添加的可选 tag(模态 + 能力,排除已选的) */
|
||||||
|
function addableTags(m: ModelConfig): { key: string; kind: 'modality' | 'capability'; label: string }[] {
|
||||||
|
const mods = ADDABLE_MODALITIES
|
||||||
|
.filter(x => !m.modalities.includes(x))
|
||||||
|
.map(x => ({ key: x, kind: 'modality' as const, label: t('settings.tagModality.' + x) }))
|
||||||
|
const caps = ADDABLE_CAPABILITIES
|
||||||
|
.filter(x => !m.capabilities.includes(x))
|
||||||
|
.map(x => ({ key: x, kind: 'capability' as const, label: t('settings.tagCapability.' + x) }))
|
||||||
|
return [...mods, ...caps]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 标记模型为「手动标注」(probe_source=user_set),下次探测不覆盖 */
|
||||||
|
function markManual(m: ModelConfig) {
|
||||||
|
m.probe_source = 'user_set'
|
||||||
|
addingTagModel.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeModality(m: ModelConfig, md: Modality) {
|
||||||
|
m.modalities = m.modalities.filter(x => x !== md)
|
||||||
|
markManual(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCapability(m: ModelConfig, cap: Capability) {
|
||||||
|
m.capabilities = m.capabilities.filter(x => x !== cap)
|
||||||
|
markManual(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTag(m: ModelConfig, key: string, kind: 'modality' | 'capability') {
|
||||||
|
if (kind === 'modality') {
|
||||||
|
const md = key as Modality
|
||||||
|
if (!m.modalities.includes(md)) m.modalities = [...m.modalities, md]
|
||||||
|
} else {
|
||||||
|
const cap = key as Capability
|
||||||
|
if (!m.capabilities.includes(cap)) m.capabilities = [...m.capabilities, cap]
|
||||||
|
}
|
||||||
|
markManual(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAddTag(m: ModelConfig) {
|
||||||
|
addingTagModel.value = addingTagModel.value === m.model_id ? null : m.model_id
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* L19: 关闭表单入口——脏表单二次确认,清表单后关闭。
|
* L19: 关闭表单入口——脏表单二次确认,清表单后关闭。
|
||||||
* 用于模板「取消」按钮 + saveProvider 成功后(无脏直接关)。
|
* 用于模板「取消」按钮 + saveProvider 成功后(无脏直接关)。
|
||||||
@@ -343,7 +459,18 @@ async function fetchModels() {
|
|||||||
apiKey: providerForm.apiKey,
|
apiKey: providerForm.apiKey,
|
||||||
providerId: providerForm.editId || null,
|
providerId: providerForm.editId || null,
|
||||||
})
|
})
|
||||||
providerForm.models = models.map(m => ({ ...m, modalities: [...m.modalities], capabilities: [...m.capabilities] }))
|
// 手动优先:探测结果合并已存在的 UserSet 标注——用户手动改过的能力不被「测试连接」覆盖
|
||||||
|
// (对齐后端 ai_fetch_models 合并逻辑:probe_source==UserSet 保留手动能力维度)。
|
||||||
|
providerForm.models = models.map(m => {
|
||||||
|
const old = providerForm.models.find(o => o.model_id === m.model_id)
|
||||||
|
const isManual = old?.probe_source === 'user_set'
|
||||||
|
return {
|
||||||
|
...m,
|
||||||
|
modalities: isManual ? [...old.modalities] : [...m.modalities],
|
||||||
|
capabilities: isManual ? [...old.capabilities] : [...m.capabilities],
|
||||||
|
probe_source: isManual ? 'user_set' : m.probe_source,
|
||||||
|
}
|
||||||
|
})
|
||||||
emit('toast', t('settings.toastFetchOk', { count: models.length }), 'info')
|
emit('toast', t('settings.toastFetchOk', { count: models.length }), 'info')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = errMsg(e)
|
const msg = errMsg(e)
|
||||||
@@ -584,11 +711,11 @@ onMounted(loadProviders)
|
|||||||
|
|
||||||
/* 表单(本面板特有输入控件样式;.setting-select 走全局) */
|
/* 表单(本面板特有输入控件样式;.setting-select 走全局) */
|
||||||
.form-grid { display: flex; flex-direction: column; gap: 14px; }
|
.form-grid { display: flex; flex-direction: column; gap: 14px; }
|
||||||
.form-field { display: flex; flex-direction: column; gap: 4px; }
|
.form-field { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
||||||
.form-label { font-size: 12px; font-weight: 500; color: var(--df-text-secondary); }
|
.form-label { flex: 0 0 72px; font-size: 12px; font-weight: 500; color: var(--df-text-secondary); text-align: right; }
|
||||||
/* 字段级辅助说明(默认模型提示等):弱化色 + 小字,与 label/input 区分层级。
|
/* 字段级辅助说明(默认模型提示等):弱化色 + 小字,与 label/input 区分层级。
|
||||||
复用现有 --df-text-dim token(对齐 .empty-hint / .pool-weight-hint 风格)。 */
|
横向布局下占满整行换到 label+input 下方。 */
|
||||||
.form-field-hint { font-size: 11px; color: var(--df-text-dim); line-height: 1.5; }
|
.form-field-hint { flex-basis: 100%; margin-left: 82px; font-size: 11px; color: var(--df-text-dim); line-height: 1.5; }
|
||||||
.form-actions { display: flex; justify-content: flex-end; padding-top: 4px; }
|
.form-actions { display: flex; justify-content: flex-end; padding-top: 4px; }
|
||||||
|
|
||||||
/* P0-2: 保存失败持久错误横幅(常驻 form-actions 上方,醒目于自隐 toast) */
|
/* P0-2: 保存失败持久错误横幅(常驻 form-actions 上方,醒目于自隐 toast) */
|
||||||
@@ -608,9 +735,14 @@ onMounted(loadProviders)
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-input { padding: 6px 12px; background: var(--df-bg); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); color: var(--df-text); font-size: 13px; outline: none; min-width: 260px; }
|
/* 模型列表区:占整行,恢复纵向布局(label 上 + 列表下),不受 .form-field 横向化影响 */
|
||||||
|
.form-field--full { flex-direction: column; align-items: stretch; gap: 4px; }
|
||||||
|
.form-field--full .form-label { flex: none; text-align: left; margin-bottom: 4px; }
|
||||||
|
|
||||||
|
.setting-input { flex: 1; min-width: 0; padding: 6px 12px; background: var(--df-bg); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); color: var(--df-text); font-size: 13px; outline: none; }
|
||||||
.setting-input:focus { border-color: var(--df-accent); }
|
.setting-input:focus { border-color: var(--df-accent); }
|
||||||
.setting-input--sm { min-width: 0; width: 70px; padding: 4px 8px; font-size: 12px; }
|
.form-field .setting-select { flex: 1; min-width: 0; }
|
||||||
|
.setting-input--sm { flex: 0 0 auto; min-width: 0; width: 70px; padding: 4px 8px; font-size: 12px; }
|
||||||
|
|
||||||
/* Toggle 小号变体(本面板负载均衡池用;.toggle 基础走全局) */
|
/* Toggle 小号变体(本面板负载均衡池用;.toggle 基础走全局) */
|
||||||
.toggle--sm { width: 32px; height: 18px; }
|
.toggle--sm { width: 32px; height: 18px; }
|
||||||
@@ -624,4 +756,91 @@ onMounted(loadProviders)
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--df-warning);
|
color: var(--df-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 模型列表(表格形式:表头 + 数据行) =====
|
||||||
|
原 model-list/model-row 等无任何样式定义(裸渲染),本批补齐并改为表格。
|
||||||
|
风格对齐 provider-card(浅底 / 圆角 / 0.5px 边框)。 */
|
||||||
|
.model-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 0.5px solid var(--df-border);
|
||||||
|
border-radius: var(--df-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.model-table th,
|
||||||
|
.model-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.model-table thead th {
|
||||||
|
background: var(--df-bg);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--df-text-dim);
|
||||||
|
font-size: 11px;
|
||||||
|
border-bottom: 0.5px solid var(--df-border);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.model-table tbody tr { border-bottom: 0.5px solid var(--df-border); }
|
||||||
|
.model-table tbody tr:last-child { border-bottom: none; }
|
||||||
|
.model-table tbody tr:hover { background: var(--df-accent-bg); }
|
||||||
|
/* 列宽:启用窄、模型/能力弹性、权重窄 */
|
||||||
|
.model-table .col-enabled { width: 56px; text-align: center; }
|
||||||
|
.model-table .col-weight { width: 110px; }
|
||||||
|
.model-table .col-model { min-width: 140px; }
|
||||||
|
.col-enabled .toggle--sm { display: inline-block; }
|
||||||
|
.model-id {
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: var(--df-font-mono);
|
||||||
|
color: var(--df-text);
|
||||||
|
font-weight: 500;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
/* 标签列:小圆角 chip,三类语义色区分 */
|
||||||
|
.model-tags { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||||
|
.tag {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: var(--df-radius-xs);
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tag-modality { background: rgba(100,181,246,0.15); color: var(--df-info); }
|
||||||
|
.tag-capability { background: rgba(108,99,255,0.15); color: var(--df-accent); }
|
||||||
|
.tag-source { background: rgba(90,99,128,0.2); color: var(--df-text-dim); }
|
||||||
|
/* 可编辑 tag:×删除按钮(悬停显红)+ 添加按钮 */
|
||||||
|
.tag-remove { margin-left: 3px; cursor: pointer; opacity: 0.6; font-weight: 700; }
|
||||||
|
.tag-remove:hover { opacity: 1; color: var(--df-danger); }
|
||||||
|
.tag-add {
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px dashed var(--df-border);
|
||||||
|
color: var(--df-text-dim);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.tag-add:hover { border-color: var(--df-accent); color: var(--df-accent); }
|
||||||
|
/* 添加能力内联菜单(点+展开):浅底容器包可选 tag */
|
||||||
|
.add-tag-menu {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
background: var(--df-bg);
|
||||||
|
border: 0.5px solid var(--df-border);
|
||||||
|
border-radius: var(--df-radius-sm);
|
||||||
|
}
|
||||||
|
.add-tag-menu .tag { cursor: pointer; }
|
||||||
|
.add-tag-menu .tag:hover { filter: brightness(1.2); }
|
||||||
|
.add-tag-empty { font-size: 10px; color: var(--df-text-dim); padding: 2px 4px; }
|
||||||
|
/* 权重输入框:表格内固定窄宽 */
|
||||||
|
.model-table .setting-input--sm { width: 64px; }
|
||||||
|
.model-list-hint {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--df-text-dim);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -62,6 +62,10 @@
|
|||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
.setting-number:focus { border-color: var(--df-accent); }
|
.setting-number:focus { border-color: var(--df-accent); }
|
||||||
|
/* 隐藏原生数字加减箭头(与文本框视觉统一;注释声明无 spinner 但此前漏了实现) */
|
||||||
|
.setting-number::-webkit-outer-spin-button,
|
||||||
|
.setting-number::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||||
|
.setting-number { -moz-appearance: textfield; appearance: textfield; }
|
||||||
|
|
||||||
/* text input:通用单行文本(目录/模型名/脚本名单) */
|
/* text input:通用单行文本(目录/模型名/脚本名单) */
|
||||||
.setting-input {
|
.setting-input {
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ export default {
|
|||||||
fetchHintSaveFirst: 'Save the provider first, then fetch models',
|
fetchHintSaveFirst: 'Save the provider first, then fetch models',
|
||||||
labelWeight: 'Weight',
|
labelWeight: 'Weight',
|
||||||
modelListTitle: 'Fetched models ({count})',
|
modelListTitle: 'Fetched models ({count})',
|
||||||
|
colEnabled: 'Enabled',
|
||||||
|
colModel: 'Model',
|
||||||
|
colAbility: 'Capability',
|
||||||
modelListHint: 'After toggling enabled / weight, click "Save" below to write them into the provider config; the next "Test connection" overwrites with fresh probe results',
|
modelListHint: 'After toggling enabled / weight, click "Save" below to write them into the provider config; the next "Test connection" overwrites with fresh probe results',
|
||||||
saveModels: 'Save model config',
|
saveModels: 'Save model config',
|
||||||
modelListUnsavedHint: 'Model list has unsaved changes (enabled / weight) — closing or navigating away will discard them',
|
modelListUnsavedHint: 'Model list has unsaved changes (enabled / weight) — closing or navigating away will discard them',
|
||||||
@@ -81,6 +84,13 @@ export default {
|
|||||||
tagModality: { text: 'Text', vision: 'Vision', audio: 'Audio', video: 'Video' },
|
tagModality: { text: 'Text', vision: 'Vision', audio: 'Audio', video: 'Video' },
|
||||||
tagCapability: { tool_use: 'Tool use', embedding: 'Embedding', code_gen: 'Code' },
|
tagCapability: { tool_use: 'Tool use', embedding: 'Embedding', code_gen: 'Code' },
|
||||||
tagSource: { user_set: 'Manual', preset_table: 'Preset', heuristic: 'Heuristic', default: 'Default' },
|
tagSource: { user_set: 'Manual', preset_table: 'Preset', heuristic: 'Heuristic', default: 'Default' },
|
||||||
|
// Tag tooltips: explain meaning & source reliability (manual = user-edited, highest priority)
|
||||||
|
tagTooltipModality: 'Modality: input types a model accepts (Vision = can read images). From preset table / name heuristic; editable',
|
||||||
|
tagTooltipCapability: 'Capability: what a model can do (Tool use = can call AI tools to act). From preset table / name heuristic; editable',
|
||||||
|
tagTooltipSource: 'Source: Preset = matched built-in table (reliable); Heuristic = guessed from name (approx); Manual = your override (priority)',
|
||||||
|
// In-table manual capability tagging (× to remove, + to add)
|
||||||
|
addTagTitle: 'Add capability / modality',
|
||||||
|
addTagNone: 'All optional capabilities included',
|
||||||
|
|
||||||
// ===== Connection panel =====
|
// ===== Connection panel =====
|
||||||
panelConnection: '🔗 Connections',
|
panelConnection: '🔗 Connections',
|
||||||
@@ -92,10 +102,12 @@ export default {
|
|||||||
labelHost: 'Host',
|
labelHost: 'Host',
|
||||||
labelPort: 'Port',
|
labelPort: 'Port',
|
||||||
labelUser: 'Username',
|
labelUser: 'Username',
|
||||||
|
labelPassword: 'Password',
|
||||||
phConnName: 'flux_dev',
|
phConnName: 'flux_dev',
|
||||||
phHost: '127.0.0.1',
|
phHost: '127.0.0.1',
|
||||||
phPort: '3306',
|
phPort: '3306',
|
||||||
phUser: 'root',
|
phUser: 'root',
|
||||||
|
phPassword: '••••••••',
|
||||||
|
|
||||||
// ===== General settings panel =====
|
// ===== General settings panel =====
|
||||||
panelGeneral: '🎨 General',
|
panelGeneral: '🎨 General',
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ export default {
|
|||||||
fetchHintSaveFirst: '请先保存 Provider 后再拉取模型',
|
fetchHintSaveFirst: '请先保存 Provider 后再拉取模型',
|
||||||
labelWeight: '权重',
|
labelWeight: '权重',
|
||||||
modelListTitle: '已拉取模型({count} 个)',
|
modelListTitle: '已拉取模型({count} 个)',
|
||||||
|
colEnabled: '启用',
|
||||||
|
colModel: '模型',
|
||||||
|
colAbility: '能力',
|
||||||
modelListHint: '勾选 / 权重调整后,请点下方「保存」一并写入 Provider 配置;下次「测试连接」会以最新探测结果覆盖',
|
modelListHint: '勾选 / 权重调整后,请点下方「保存」一并写入 Provider 配置;下次「测试连接」会以最新探测结果覆盖',
|
||||||
saveModels: '保存模型配置',
|
saveModels: '保存模型配置',
|
||||||
modelListUnsavedHint: '模型列表有未保存的调整(启用 / 权重),关闭或切走将丢弃',
|
modelListUnsavedHint: '模型列表有未保存的调整(启用 / 权重),关闭或切走将丢弃',
|
||||||
@@ -80,6 +83,13 @@ export default {
|
|||||||
tagModality: { text: '文本', vision: '视觉', audio: '音频', video: '视频' },
|
tagModality: { text: '文本', vision: '视觉', audio: '音频', video: '视频' },
|
||||||
tagCapability: { tool_use: '工具调用', embedding: '向量', code_gen: '代码' },
|
tagCapability: { tool_use: '工具调用', embedding: '向量', code_gen: '代码' },
|
||||||
tagSource: { user_set: '手动', preset_table: '预设', heuristic: '推断', default: '默认' },
|
tagSource: { user_set: '手动', preset_table: '预设', heuristic: '推断', default: '默认' },
|
||||||
|
// 标签 tooltip:说明含义与来源可信度(手动标注 = 用户改过,优先级最高)
|
||||||
|
tagTooltipModality: '模态:模型能接收的输入类型(视觉=可看图)。来自预设表/按名推断,可手动修改',
|
||||||
|
tagTooltipCapability: '能力:模型能做什么(工具调用=可调用 AI 工具执行操作)。来自预设表/按名推断,可手动修改',
|
||||||
|
tagTooltipSource: '来源:预设=命中内置模型表(较可靠);推断=按模型名猜测(仅供参考);手动=你自定义(优先)',
|
||||||
|
// 表格内手动标注能力(点×删、点+加)
|
||||||
|
addTagTitle: '添加能力/模态',
|
||||||
|
addTagNone: '已含全部可选能力',
|
||||||
|
|
||||||
// ===== 连接管理面板 =====
|
// ===== 连接管理面板 =====
|
||||||
panelConnection: '🔗 连接管理',
|
panelConnection: '🔗 连接管理',
|
||||||
@@ -91,10 +101,12 @@ export default {
|
|||||||
labelHost: 'Host',
|
labelHost: 'Host',
|
||||||
labelPort: 'Port',
|
labelPort: 'Port',
|
||||||
labelUser: '用户名',
|
labelUser: '用户名',
|
||||||
|
labelPassword: '密码',
|
||||||
phConnName: 'flux_dev',
|
phConnName: 'flux_dev',
|
||||||
phHost: '127.0.0.1',
|
phHost: '127.0.0.1',
|
||||||
phPort: '3306',
|
phPort: '3306',
|
||||||
phUser: 'root',
|
phUser: 'root',
|
||||||
|
phPassword: '••••••••',
|
||||||
|
|
||||||
// ===== 通用设置面板 =====
|
// ===== 通用设置面板 =====
|
||||||
panelGeneral: '🎨 通用设置',
|
panelGeneral: '🎨 通用设置',
|
||||||
|
|||||||
Reference in New Issue
Block a user