mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-31 13:20:10 +08:00
refactor(models): address PR #232 review comments
- i18n: Extract hardcoded Chinese strings to translation keys - fix(url): Restore full-width punctuation in URL regex - chore: Remove linux-schema.json (build artifact) - chore: Add linux-schema.json to .gitignore - refactor: Unify save strategy to use autoSave consistently - style: Add trailing newline to en.json Closes review items 1-6 from PR #232
This commit is contained in:
2
src/locales/en.json
Normal file → Executable file
2
src/locales/en.json
Normal file → Executable file
@@ -2201,4 +2201,4 @@
|
||||
"confirmOk": "OK",
|
||||
"confirmCancel": "Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
src/locales/modules/models.js
Normal file → Executable file
14
src/locales/modules/models.js
Normal file → Executable file
@@ -153,4 +153,18 @@ export default {
|
||||
restartFailed: _('重启失败', 'Restart failed', '重啟失敗'),
|
||||
configSavedGwFailed: _('配置已保存,但 Gateway 重启失败', 'Config saved, but Gateway restart failed', '設定已儲存,但 Gateway 重啟失敗'),
|
||||
visitSite: _('访问 {name}官网', 'Visit {name} website', '訪問 {name}官網'),
|
||||
// 瀑布流编辑器新增 keys
|
||||
systemModelTitle: _('系统主/备模型', 'System Primary/Backup Models', '系統主/備模型'),
|
||||
activeChainTitle: _('当前生效链 (支持拖拽排序)', 'Active Chain (drag to reorder)', '目前生效鏈 (支援拖曳排序)'),
|
||||
candidatePoolTitle: _('可用候选池 (按服务商分组)', 'Candidate Pool (grouped by provider)', '可用候選池 (按服務商分組)'),
|
||||
noCandidateModel: _('无可用候选模型', 'No candidate models available', '無可用候選模型'),
|
||||
bestPracticeHint: _('💡 最佳实践:建议备选模型保持在 2-3 款并分布在不同服务商,以平衡可用性与延迟。', '💡 Best practice: Keep 2-3 fallback models distributed across different providers to balance availability and latency.', '💡 最佳實踐:建議備選模型保持在 2-3 款並分布在不同服務商,以平衡可用性與延遲。'),
|
||||
setAsPrimarySuccess: _('已将 {model} 设为主模型', 'Set {model} as primary model', '已將 {model} 設為主模型'),
|
||||
noFallbackSelected: _('尚未选择备选模型', 'No fallback models selected', '尚未選擇備選模型'),
|
||||
cancel: _('取消', 'Cancel', '取消'),
|
||||
saveAndApply: _('保存并应用', 'Save and Apply', '儲存並套用'),
|
||||
nFallbacks: _('{count} 个备选', '{count} fallback(s)', '{count} 個備選'),
|
||||
setAsPrimary: _('设为主用', 'Set as Primary', '設為主用'),
|
||||
remove: _('移除', 'Remove', '移除'),
|
||||
add: _('加入', 'Add', '加入'),
|
||||
}
|
||||
|
||||
32
src/pages/models.js
Normal file → Executable file
32
src/pages/models.js
Normal file → Executable file
@@ -125,10 +125,10 @@ function renderDefaultBar(page, state) {
|
||||
<div class="config-section-title" id="system-model-title" style="display:flex; justify-content:space-between; align-items:center; cursor:pointer; user-select:none;">
|
||||
<div style="display:flex; align-items:center; gap:8px">
|
||||
<span style="display:inline-block;width:16px;font-size:12px;color:var(--text-tertiary)">${chevron}</span>
|
||||
<span>系统主/备模型</span>
|
||||
<span>${t('models.systemModelTitle')}</span>
|
||||
<div style="display:flex; gap:8px; margin-left: 12px; align-items: baseline;">
|
||||
<span style="color:var(--success); font-family:var(--font-mono); font-size: 0.9em; font-weight: 500;">${primary || '未配置'}</span>
|
||||
<span style="font-size: 11px; color: var(--text-tertiary); font-weight: normal;">${fallbacks.length} 个备选</span>
|
||||
<span style="color:var(--success); font-family:var(--font-mono); font-size: 0.9em; font-weight: 500;">${primary || t('models.notConfigured')}</span>
|
||||
<span style="font-size: 11px; color: var(--text-tertiary); font-weight: normal;">${t('models.nFallbacks', { count: fallbacks.length })}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -177,12 +177,12 @@ function renderFallbackWaterfall(state) {
|
||||
return `
|
||||
<div class="fallback-editor-panel" style="background: var(--bg-secondary); padding: 12px; border-radius: var(--radius-md);">
|
||||
<div style="margin-bottom: 12px; font-size: 11px; color: var(--text-secondary); background: var(--bg-info-subtle); padding: 6px 10px; border-radius: 4px; border-left: 3px solid var(--primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<strong>💡 最佳实践:</strong> 建议备选模型保持在 <strong>2-3 款</strong> 并分布在不同服务商,以平衡可用性与延迟。
|
||||
${t('models.bestPracticeHint')}
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1.2fr; gap: 24px;">
|
||||
<div>
|
||||
<div style="font-size: var(--font-size-xs); font-weight: bold; margin-bottom: 8px; color: var(--text-tertiary);">当前生效链 (支持拖拽排序)</div>
|
||||
<div style="font-size: var(--font-size-xs); font-weight: bold; margin-bottom: 8px; color: var(--text-tertiary);">${t('models.activeChainTitle')}</div>
|
||||
<div id="active-fallback-list" style="display: flex; flex-direction: column; gap: 4px; min-height: 50px;">
|
||||
${currentFallbacks.map((f, i) => `
|
||||
<div class="fallback-chain-item" data-id="${f}" style="display: flex; align-items: center; justify-content: space-between; background: var(--bg-primary); padding: 6px 10px; border-radius: 4px; border: 1px solid var(--border-color);">
|
||||
@@ -191,19 +191,19 @@ function renderFallbackWaterfall(state) {
|
||||
<span style="font-family: var(--font-mono); font-size: var(--font-size-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${i + 1}. ${f}</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 4px; flex-shrink: 0;">
|
||||
<button class="btn btn-xs btn-secondary btn-set-primary-from-fb" data-id="${f}" style="padding: 1px 4px; font-size: 10px;">设为主用</button>
|
||||
<button class="btn-icon btn-remove-fb" data-id="${f}" title="移除">${icon('x', 12)}</button>
|
||||
<button class="btn btn-xs btn-secondary btn-set-primary-from-fb" data-id="${f}" style="padding: 1px 4px; font-size: 10px;">${t('models.setAsPrimary')}</button>
|
||||
<button class="btn-icon btn-remove-fb" data-id="${f}" title="${t('models.remove')}">${icon('x', 12)}</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
${currentFallbacks.length === 0 ? `<div style="font-size: 12px; color: var(--text-tertiary); text-align: center; padding: 20px; border: 1px dashed var(--border-color); border-radius: 4px;">尚未选择备选模型</div>` : ''}
|
||||
${currentFallbacks.length === 0 ? `<div style="font-size: 12px; color: var(--text-tertiary); text-align: center; padding: 20px; border: 1px dashed var(--border-color); border-radius: 4px;">${t('models.noFallbackSelected')}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="font-size: var(--font-size-xs); font-weight: bold; margin-bottom: 8px; color: var(--text-tertiary);">可用候选池 (按服务商分组)</div>
|
||||
<div style="font-size: var(--font-size-xs); font-weight: bold; margin-bottom: 8px; color: var(--text-tertiary);">${t('models.candidatePoolTitle')}</div>
|
||||
<div id="candidate-model-pool" style="display: flex; flex-direction: column; gap: 6px; max-height: 300px; overflow-y: auto; padding-right: 4px;">
|
||||
${Object.keys(candidatesByProvider).length === 0 ? `<div style="font-size: 12px; color: var(--text-tertiary); text-align: center; padding: 20px;">无可用候选模型</div>` :
|
||||
${Object.keys(candidatesByProvider).length === 0 ? `<div style="font-size: 12px; color: var(--text-tertiary); text-align: center; padding: 20px;">${t('models.noCandidateModel')}</div>` :
|
||||
Object.keys(candidatesByProvider).map(pKey => {
|
||||
const collapsed = !!state._fallback_candidates_collapsed[pKey]
|
||||
const mIds = candidatesByProvider[pKey]
|
||||
@@ -218,7 +218,7 @@ function renderFallbackWaterfall(state) {
|
||||
${mIds.map(mId => `
|
||||
<div class="candidate-item" style="display: flex; align-items: center; justify-content: space-between; background: var(--bg-primary); padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border-color); opacity: 0.9;">
|
||||
<span style="font-family: var(--font-mono); font-size: 11px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${mId}</span>
|
||||
<button class="btn btn-xs btn-primary btn-add-fb" data-full="${pKey}/${mId}" style="padding: 1px 6px; font-size: 10px;">加入</button>
|
||||
<button class="btn btn-xs btn-primary btn-add-fb" data-full="${pKey}/${mId}" style="padding: 1px 6px; font-size: 10px;">${t('models.add')}</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -231,8 +231,8 @@ function renderFallbackWaterfall(state) {
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 16px; display: flex; justify-content: flex-end; gap: 8px;">
|
||||
<button class="btn btn-sm btn-secondary" id="btn-cancel-fallback">取消</button>
|
||||
<button class="btn btn-sm btn-primary" id="btn-save-fallback">保存并应用</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btn-cancel-fallback">${t('models.cancel')}</button>
|
||||
<button class="btn btn-sm btn-primary" id="btn-save-fallback">${t('models.saveAndApply')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -267,7 +267,7 @@ function bindWaterfallActions(page, state) {
|
||||
state.config.agents.defaults.model.fallbacks = newFallbacks
|
||||
|
||||
renderDefaultBar(page, state)
|
||||
toast(`已将 ${full} 设为主模型`, 'success')
|
||||
toast(t('models.setAsPrimarySuccess', { model: full }), 'success')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -380,7 +380,7 @@ function bindWaterfallActions(page, state) {
|
||||
// 保存
|
||||
container.querySelector('#btn-save-fallback').onclick = async () => {
|
||||
state.showFallbackEditor = false
|
||||
await doAutoSave(state)
|
||||
autoSave(state)
|
||||
renderDefaultBar(page, state)
|
||||
}
|
||||
}
|
||||
@@ -1657,7 +1657,7 @@ async function testModel(btn, state, providerKey, idx) {
|
||||
const summary = lines[0]
|
||||
const detail = lines.slice(1).join('\n').trim()
|
||||
if (detail) {
|
||||
const detailHtml = detail.replace(/</g, '<').replace(/(https?:\/\/[^\s,,。;))'"&]+)/g, '<a href="$1" target="_blank" style="color:var(--primary);text-decoration:underline">$1</a>')
|
||||
const detailHtml = detail.replace(/</g, '<').replace(/(https?:\/\/[^\s,。;))'"&]+)/g, '<a href="$1" target="_blank" style="color:var(--primary);text-decoration:underline">$1</a>')
|
||||
toast(`<strong>${modelId}</strong> ${summary.replace(/</g, '<')}<br><span style="font-size:11px;line-height:1.5;word-break:break-all">${detailHtml}</span>`, 'warning', { duration: 10000, html: true })
|
||||
} else {
|
||||
toast(`${modelId} ${summary}`, 'warning', { duration: 6000 })
|
||||
|
||||
Reference in New Issue
Block a user