Files
AI-Chat/public/assets/js/install.js
2026-05-06 18:25:44 +08:00

202 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const InstallWizard = {
currentStep: 1,
totalSteps: 5,
init() {
this.checkEnv();
this.addProvider(); // 默认添加一个供应商表单
},
checkEnv() {
const checks = document.getElementById('envCheckData');
if (!checks) return;
const data = JSON.parse(checks.textContent);
const container = document.getElementById('envChecks');
container.innerHTML = data.map(c => `
<div class="check-item ${c.pass ? 'pass' : 'fail'}">
<span class="check-name">${c.name}</span>
<span class="check-status">
${c.pass
? '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 通过'
: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg> 未通过'
}
</span>
</div>
`).join('');
},
nextStep() {
if (this.currentStep === 4) {
// 验证管理员密码
const pwd = document.getElementById('adminPassword').value;
const confirm = document.getElementById('adminPasswordConfirm').value;
const username = document.getElementById('adminUsername').value;
if (!username) { alert('请输入管理员用户名'); return; }
if (pwd.length < 6) { alert('密码至少6位'); return; }
if (pwd !== confirm) { alert('两次密码不一致'); return; }
}
if (this.currentStep < this.totalSteps) {
this.currentStep++;
this.updateSteps();
}
},
prevStep() {
if (this.currentStep > 1) {
this.currentStep--;
this.updateSteps();
}
},
updateSteps() {
document.querySelectorAll('.step-content').forEach((el, i) => {
el.classList.toggle('active', i + 1 === this.currentStep);
});
document.querySelectorAll('.step-indicator .step').forEach((el, i) => {
el.classList.remove('active', 'completed');
if (i + 1 === this.currentStep) el.classList.add('active');
if (i + 1 < this.currentStep) el.classList.add('completed');
});
},
async testDb() {
const result = document.getElementById('dbTestResult');
try {
const response = await fetch('/api/install/test-db', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
host: document.getElementById('dbHost').value,
port: parseInt(document.getElementById('dbPort').value),
user: document.getElementById('dbUser').value,
password: document.getElementById('dbPassword').value,
database: document.getElementById('dbName').value
})
});
const data = await response.json();
result.innerHTML = `<div class="alert ${data.success ? 'alert-success' : 'alert-error'}">${data.message}</div>`;
} catch (err) {
result.innerHTML = '<div class="alert alert-error">连接失败: ' + err.message + '</div>';
}
},
addProvider() {
const list = document.getElementById('providerList');
const index = list.children.length;
const html = `
<div class="provider-item" data-index="${index}">
<div class="provider-item-header">
<span class="provider-item-title">供应商 #${index + 1}</span>
${index > 0 ? '<button class="btn btn-danger btn-sm" onclick="this.closest(\'.provider-item\').remove()">删除</button>' : ''}
</div>
<div class="form-row">
<div class="form-group col-flex-1">
<label>供应商名称</label>
<input type="text" class="provider-name" placeholder="如OpenAI、DeepSeek">
</div>
<div class="form-group col-flex-1">
<label>供应商类型</label>
<select class="provider-type">
<option value="newapi">OpenAI 兼容</option>
<option value="openai">OpenAI 官方</option>
<option value="claude">Claude (Anthropic)</option>
</select>
</div>
</div>
<div class="form-group">
<label>API URL</label>
<input type="text" class="provider-url" placeholder="如https://api.openai.com">
</div>
<div class="form-group">
<label>API Key</label>
<input type="password" class="provider-key" placeholder="API 密钥">
</div>
<div class="form-group">
<label>可用模型(逗号分隔)</label>
<input type="text" class="provider-models" placeholder="如gpt-3.5-turbo, gpt-4">
</div>
</div>
`;
list.insertAdjacentHTML('beforeend', html);
},
generateSecret() {
const chars = '0123456789abcdef';
let result = '';
for (let i = 0; i < 64; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
},
async runInstall() {
const btn = document.getElementById('installBtn');
const result = document.getElementById('installResult');
// 收集供应商数据
const providers = [];
document.querySelectorAll('.provider-item').forEach(item => {
const models = item.querySelector('.provider-models').value.split(',').map(m => m.trim()).filter(m => m);
providers.push({
name: item.querySelector('.provider-name').value,
apiUrl: item.querySelector('.provider-url').value,
apiKey: item.querySelector('.provider-key').value,
models: models,
type: item.querySelector('.provider-type').value,
enabled: true
});
});
if (providers.length === 0 || !providers[0].name || !providers[0].apiKey) {
alert('请至少配置一个完整的供应商');
return;
}
btn.disabled = true;
btn.textContent = '安装中...';
const setupData = {
username: document.getElementById('adminUsername').value,
password: document.getElementById('adminPassword').value,
dbConfig: {
host: document.getElementById('dbHost').value,
port: parseInt(document.getElementById('dbPort').value),
user: document.getElementById('dbUser').value,
password: document.getElementById('dbPassword').value,
database: document.getElementById('dbName').value
},
appConfig: {
jwtSecret: document.getElementById('jwtSecret').value || undefined,
jwtExpiry: parseInt(document.getElementById('jwtExpiry').value) || 86400
},
providers: providers
};
try {
const response = await fetch('/api/install/setup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(setupData)
});
const data = await response.json();
if (data.success) {
result.innerHTML = '<div class="alert alert-success">安装成功!正在跳转到登录页...</div>';
setTimeout(() => { window.location.href = '/login.php'; }, 2000);
} else {
result.innerHTML = '<div class="alert alert-error">安装失败: ' + data.message + '</div>';
btn.disabled = false;
btn.textContent = '完成安装';
}
} catch (err) {
result.innerHTML = '<div class="alert alert-error">安装失败: ' + err.message + '</div>';
btn.disabled = false;
btn.textContent = '完成安装';
}
}
};
InstallWizard.init();