mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-31 05:10:14 +08:00
feat: ClawPanel v0.1.0 项目骨架
- Tauri v2 + Vanilla JS + Vite 技术栈 - 9 个页面: 仪表盘/服务管理/日志/模型配置/Agent配置/Gateway/MCP工具/记忆文件/部署 - Rust 后端: 配置读写/服务管理(launchd)/日志读取/记忆文件管理 - 暗色主题 + 玻璃拟态 UI - Mock 数据支持纯浏览器开发调试
This commit is contained in:
147
src/pages/models.js
Normal file
147
src/pages/models.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 模型配置页面
|
||||
*/
|
||||
import { api } from '../lib/tauri-api.js'
|
||||
import { toast } from '../components/toast.js'
|
||||
|
||||
export async function render() {
|
||||
const page = document.createElement('div')
|
||||
page.className = 'page'
|
||||
|
||||
page.innerHTML = `
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">模型配置</h1>
|
||||
<p class="page-desc">管理 AI 模型 Provider 和模型列表</p>
|
||||
</div>
|
||||
<div class="config-actions">
|
||||
<button class="btn btn-primary btn-sm" id="btn-add-provider">+ 添加 Provider</button>
|
||||
<button class="btn btn-secondary btn-sm" id="btn-save-models">保存配置</button>
|
||||
</div>
|
||||
<div id="providers-list">加载中...</div>
|
||||
`
|
||||
|
||||
const state = { config: null }
|
||||
await loadConfig(page, state)
|
||||
|
||||
page.querySelector('#btn-save-models').onclick = () => saveConfig(state)
|
||||
page.querySelector('#btn-add-provider').onclick = () => addProvider(page, state)
|
||||
|
||||
return page
|
||||
}
|
||||
|
||||
async function loadConfig(page, state) {
|
||||
try {
|
||||
state.config = await api.readOpenclawConfig()
|
||||
renderProviders(page, state)
|
||||
} catch (e) {
|
||||
toast('加载配置失败: ' + e, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
function renderProviders(page, state) {
|
||||
const listEl = page.querySelector('#providers-list')
|
||||
const providers = state.config?.models?.providers || {}
|
||||
const keys = Object.keys(providers)
|
||||
|
||||
if (!keys.length) {
|
||||
listEl.innerHTML = '<div style="color:var(--text-tertiary);padding:20px">暂无 Provider 配置,点击上方按钮添加</div>'
|
||||
return
|
||||
}
|
||||
|
||||
listEl.innerHTML = keys.map(key => {
|
||||
const p = providers[key]
|
||||
const models = p.models || []
|
||||
return `
|
||||
<div class="config-section" data-provider="${key}">
|
||||
<div class="config-section-title" style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span>${key}</span>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn btn-sm btn-secondary" data-action="toggle">展开/收起</button>
|
||||
<button class="btn btn-sm btn-danger" data-action="delete-provider">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="provider-meta" style="margin-bottom:12px">
|
||||
<div class="form-group" style="margin-bottom:8px">
|
||||
<label class="form-label">Base URL</label>
|
||||
<input class="form-input" data-field="baseUrl" value="${p.baseUrl || ''}">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:8px">
|
||||
<label class="form-label">API 类型</label>
|
||||
<select class="form-input" data-field="apiType">
|
||||
<option value="openai" ${p.apiType === 'openai' ? 'selected' : ''}>OpenAI</option>
|
||||
<option value="anthropic" ${p.apiType === 'anthropic' ? 'selected' : ''}>Anthropic</option>
|
||||
<option value="google" ${p.apiType === 'google' ? 'selected' : ''}>Google</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="provider-models" style="display:none">
|
||||
<div style="font-size:var(--font-size-sm);color:var(--text-secondary);margin-bottom:8px">
|
||||
模型列表 (${models.length})
|
||||
</div>
|
||||
${models.map((m, i) => `
|
||||
<div class="model-item" style="background:var(--bg-tertiary);padding:8px 12px;border-radius:var(--radius-sm);margin-bottom:6px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-family:var(--font-mono);font-size:var(--font-size-sm)">${m.id || m}</span>
|
||||
<button class="btn btn-sm btn-danger" data-action="delete-model" data-index="${i}">删除</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
<button class="btn btn-sm btn-secondary" data-action="add-model" style="margin-top:4px">+ 添加模型</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}).join('')
|
||||
|
||||
// 绑定事件
|
||||
listEl.querySelectorAll('[data-action]').forEach(btn => {
|
||||
btn.onclick = () => {
|
||||
const section = btn.closest('[data-provider]')
|
||||
const providerKey = section.dataset.provider
|
||||
const action = btn.dataset.action
|
||||
|
||||
if (action === 'toggle') {
|
||||
const models = section.querySelector('.provider-models')
|
||||
models.style.display = models.style.display === 'none' ? 'block' : 'none'
|
||||
} else if (action === 'delete-provider') {
|
||||
delete state.config.models.providers[providerKey]
|
||||
renderProviders(page, state)
|
||||
toast(`已删除 ${providerKey}`, 'info')
|
||||
} else if (action === 'delete-model') {
|
||||
const idx = parseInt(btn.dataset.index)
|
||||
state.config.models.providers[providerKey].models.splice(idx, 1)
|
||||
renderProviders(page, state)
|
||||
} else if (action === 'add-model') {
|
||||
const id = prompt('输入模型 ID:')
|
||||
if (id) {
|
||||
state.config.models.providers[providerKey].models.push({ id })
|
||||
renderProviders(page, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 输入框变更同步到 state
|
||||
listEl.querySelectorAll('[data-field]').forEach(input => {
|
||||
input.onchange = () => {
|
||||
const providerKey = input.closest('[data-provider]').dataset.provider
|
||||
state.config.models.providers[providerKey][input.dataset.field] = input.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addProvider(page, state) {
|
||||
const key = prompt('输入 Provider 名称 (如 openai, newapi):')
|
||||
if (!key) return
|
||||
if (!state.config.models) state.config.models = { mode: 'replace', providers: {} }
|
||||
if (!state.config.models.providers) state.config.models.providers = {}
|
||||
state.config.models.providers[key] = { baseUrl: '', apiType: 'openai', models: [] }
|
||||
renderProviders(page, state)
|
||||
toast(`已添加 ${key}`, 'success')
|
||||
}
|
||||
|
||||
async function saveConfig(state) {
|
||||
try {
|
||||
await api.writeOpenclawConfig(state.config)
|
||||
toast('配置已保存', 'success')
|
||||
} catch (e) {
|
||||
toast('保存失败: ' + e, 'error')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user