update at 2025-10-14 21:52:11

This commit is contained in:
douboer
2025-10-14 21:52:11 +08:00
parent ac3ed480ab
commit 4f5eea604e
40 changed files with 15231 additions and 126 deletions

View File

@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ModelStore Debug</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
h1 { color: #333; }
.section {
background: #f5f5f5;
padding: 15px;
margin: 15px 0;
border-radius: 5px;
}
pre {
background: white;
padding: 10px;
border: 1px solid #ddd;
border-radius: 3px;
overflow-x: auto;
}
button {
background: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background: #45a049;
}
.error { color: red; }
.success { color: green; }
</style>
</head>
<body>
<h1>ModelStore 调试工具</h1>
<div class="section">
<h2>LocalStorage 数据</h2>
<button onclick="checkLocalStorage()">检查 LocalStorage</button>
<button onclick="clearLocalStorage()">清空 model-providers</button>
<pre id="localStorage-data">点击"检查 LocalStorage"查看数据</pre>
</div>
<div class="section">
<h2>测试添加模型服务</h2>
<button onclick="addTestProvider()">添加测试 OpenAI 服务</button>
<button onclick="addTestOllama()">添加测试 Ollama 服务</button>
<pre id="add-result"></pre>
</div>
<div class="section">
<h2>当前配置</h2>
<button onclick="showCurrentConfig()">显示当前配置</button>
<pre id="current-config"></pre>
</div>
<script>
function checkLocalStorage() {
const data = {
'model-providers': localStorage.getItem('model-providers'),
'global-model-settings': localStorage.getItem('global-model-settings'),
'all-keys': Object.keys(localStorage)
}
document.getElementById('localStorage-data').textContent = JSON.stringify(data, null, 2)
if (data['model-providers']) {
try {
const providers = JSON.parse(data['model-providers'])
console.log('Parsed providers:', providers)
} catch (e) {
console.error('Parse error:', e)
}
}
}
function clearLocalStorage() {
localStorage.removeItem('model-providers')
document.getElementById('localStorage-data').textContent = 'model-providers 已清空'
setTimeout(checkLocalStorage, 500)
}
function addTestProvider() {
const providers = []
// 添加 OpenAI 测试配置
const openaiProvider = {
id: 'openai-test-' + Date.now(),
name: 'OpenAI 测试',
type: 'openai',
enabled: true,
apiKey: 'sk-test-key',
baseUrl: 'https://api.openai.com/v1',
models: [
{
id: 'gpt-4',
name: 'GPT-4',
maxTokens: 8192,
temperature: 0.7
},
{
id: 'gpt-3.5-turbo',
name: 'GPT-3.5 Turbo',
maxTokens: 4096,
temperature: 0.7
}
],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
// 读取现有配置
const existing = localStorage.getItem('model-providers')
if (existing) {
try {
const existingProviders = JSON.parse(existing)
providers.push(...existingProviders)
} catch (e) {
console.error('Parse existing error:', e)
}
}
providers.push(openaiProvider)
localStorage.setItem('model-providers', JSON.stringify(providers))
document.getElementById('add-result').innerHTML = '<span class="success">✓ 已添加 OpenAI 测试服务</span>'
setTimeout(checkLocalStorage, 500)
}
function addTestOllama() {
const providers = []
// 添加 Ollama 测试配置
const ollamaProvider = {
id: 'ollama-test-' + Date.now(),
name: 'Ollama 本地',
type: 'ollama',
enabled: true,
baseUrl: 'http://localhost:11434',
models: [
{
id: 'llama2',
name: 'Llama 2',
maxTokens: 4096,
temperature: 0.7
},
{
id: 'mistral',
name: 'Mistral',
maxTokens: 8192,
temperature: 0.7
}
],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
// 读取现有配置
const existing = localStorage.getItem('model-providers')
if (existing) {
try {
const existingProviders = JSON.parse(existing)
providers.push(...existingProviders)
} catch (e) {
console.error('Parse existing error:', e)
}
}
providers.push(ollamaProvider)
localStorage.setItem('model-providers', JSON.stringify(providers))
document.getElementById('add-result').innerHTML = '<span class="success">✓ 已添加 Ollama 测试服务</span>'
setTimeout(checkLocalStorage, 500)
}
function showCurrentConfig() {
const providers = localStorage.getItem('model-providers')
if (providers) {
try {
const parsed = JSON.parse(providers)
const summary = {
总数: parsed.length,
已启用: parsed.filter(p => p.enabled).length,
服务列表: parsed.map(p => ({
名称: p.name,
类型: p.type,
启用: p.enabled,
模型数: p.models?.length || 0,
模型列表: p.models?.map(m => m.name) || []
}))
}
document.getElementById('current-config').textContent = JSON.stringify(summary, null, 2)
} catch (e) {
document.getElementById('current-config').innerHTML = '<span class="error">解析错误: ' + e.message + '</span>'
}
} else {
document.getElementById('current-config').textContent = '暂无配置'
}
}
// 页面加载时自动检查
window.onload = function() {
checkLocalStorage()
showCurrentConfig()
}
</script>
</body>
</html>

View File

@@ -0,0 +1,292 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LocalStorage 诊断工具</title>
<style>
body {
font-family: 'Monaco', 'Menlo', monospace;
padding: 20px;
background: #1e1e1e;
color: #d4d4d4;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: #4ec9b0;
border-bottom: 2px solid #4ec9b0;
padding-bottom: 10px;
}
h2 {
color: #dcdcaa;
margin-top: 30px;
}
.section {
background: #252526;
padding: 20px;
border-radius: 8px;
margin: 15px 0;
border: 1px solid #3c3c3c;
}
.key-name {
color: #9cdcfe;
font-weight: bold;
}
.value {
color: #ce9178;
}
pre {
background: #1e1e1e;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
border: 1px solid #3c3c3c;
}
button {
background: #0e639c;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
font-size: 14px;
}
button:hover {
background: #1177bb;
}
.success { color: #4ec9b0; }
.error { color: #f48771; }
.warning { color: #dcdcaa; }
.info { color: #9cdcfe; }
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
th, td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #3c3c3c;
}
th {
background: #1e1e1e;
color: #4ec9b0;
}
.action-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>🔍 LocalStorage 完整诊断</h1>
<div class="action-buttons">
<button onclick="diagnose()">🔄 重新诊断</button>
<button onclick="fixData()">🔧 修复数据</button>
<button onclick="clearAll()">🗑️ 清空所有</button>
<button onclick="exportData()">💾 导出数据</button>
</div>
<div id="output"></div>
</div>
<script>
function diagnose() {
const output = document.getElementById('output')
let html = ''
// 1. 显示所有 LocalStorage 键
html += '<div class="section">'
html += '<h2>📋 所有 LocalStorage 键值对</h2>'
html += '<table>'
html += '<tr><th>键名</th><th>大小</th><th>数据类型</th><th>操作</th></tr>'
const allKeys = Object.keys(localStorage).sort()
if (allKeys.length === 0) {
html += '<tr><td colspan="4" class="warning">⚠️ LocalStorage 为空</td></tr>'
} else {
allKeys.forEach(key => {
const value = localStorage.getItem(key)
const size = new Blob([value]).size
let type = 'string'
try {
const parsed = JSON.parse(value)
type = Array.isArray(parsed) ? 'array' : typeof parsed
} catch (e) {
type = 'string'
}
html += `<tr>
<td class="key-name">${key}</td>
<td>${(size / 1024).toFixed(2)} KB</td>
<td>${type}</td>
<td><button onclick="viewKey('${key}')">查看</button></td>
</tr>`
})
}
html += '</table></div>'
// 2. 重点检查模型相关的键
html += '<div class="section">'
html += '<h2>🎯 模型服务相关键检查</h2>'
const modelKeys = [
'model-services',
'model-providers',
'model-service-config',
'services',
'providers'
]
modelKeys.forEach(key => {
const value = localStorage.getItem(key)
html += `<h3 class="key-name">${key}:</h3>`
if (!value) {
html += `<p class="error">✗ 不存在</p>`
} else {
try {
const parsed = JSON.parse(value)
if (Array.isArray(parsed)) {
html += `<p class="success">✓ 找到 ${parsed.length} 项</p>`
html += '<pre>' + JSON.stringify(parsed, null, 2) + '</pre>'
} else {
html += '<pre>' + JSON.stringify(parsed, null, 2) + '</pre>'
}
} catch (e) {
html += `<p class="warning">⚠️ 非JSON格式</p>`
html += '<pre>' + value.substring(0, 500) + '...</pre>'
}
}
})
html += '</div>'
// 3. 诊断建议
html += '<div class="section">'
html += '<h2>💡 诊断结果与建议</h2>'
const modelServices = localStorage.getItem('model-services')
const modelProviders = localStorage.getItem('model-providers')
if (!modelServices && !modelProviders) {
html += '<p class="error">❌ 未找到任何模型服务数据</p>'
html += '<p class="info">原因可能:</p>'
html += '<ul>'
html += '<li>数据保存失败(检查浏览器控制台是否有错误)</li>'
html += '<li>使用了不同的存储方式sessionStorage、IndexedDB等</li>'
html += '<li>页面域名不同导致 LocalStorage 隔离</li>'
html += '</ul>'
html += '<p class="warning">🔧 建议:点击"修复数据"按钮创建测试数据</p>'
} else if (modelServices && !modelProviders) {
html += '<p class="warning">⚠️ 找到 model-services 但缺少 model-providers</p>'
html += '<p class="info">需要同步数据到 model-providers</p>'
} else if (!modelServices && modelProviders) {
html += '<p class="success">✓ model-providers 有数据(正确的键)</p>'
html += '<p class="info">可以清理旧的 model-services 键</p>'
} else {
html += '<p class="info"> 两个键都存在,需要判断哪个是最新的</p>'
}
html += '</div>'
output.innerHTML = html
}
function viewKey(key) {
const value = localStorage.getItem(key)
const newWindow = window.open('', '_blank')
newWindow.document.write('<pre>' + JSON.stringify(JSON.parse(value), null, 2) + '</pre>')
}
function fixData() {
// 检查是否有 model-services 数据
const modelServices = localStorage.getItem('model-services')
if (modelServices) {
// 复制到 model-providers
localStorage.setItem('model-providers', modelServices)
alert('✓ 已将 model-services 同步到 model-providers')
} else {
// 创建测试数据
const testData = [
{
id: 'test-dashscope',
name: '阿里大模型',
type: 'dashscope',
url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
apiKey: 'sk-test',
enabled: true,
connected: true,
status: 'connected',
models: [
{ id: 'qwen-max', name: 'Qwen Max' },
{ id: 'qwen-plus', name: 'Qwen Plus' }
]
},
{
id: 'test-volcengine',
name: '火山大模型',
type: 'volcengine',
url: 'https://ark.cn-beijing.volces.com/api/v3',
apiKey: 'test-key',
enabled: true,
connected: true,
status: 'connected',
models: [
{ id: 'doubao-pro-4k', name: 'Doubao Pro 4K' },
{ id: 'doubao-pro-32k', name: 'Doubao Pro 32K' }
]
}
]
localStorage.setItem('model-providers', JSON.stringify(testData))
localStorage.setItem('model-services', JSON.stringify(testData))
alert('✓ 已创建测试数据')
}
diagnose()
}
function clearAll() {
if (confirm('确定要清空所有 LocalStorage 数据吗?')) {
localStorage.clear()
alert('✓ 已清空')
diagnose()
}
}
function exportData() {
const data = {}
Object.keys(localStorage).forEach(key => {
try {
data[key] = JSON.parse(localStorage.getItem(key))
} catch (e) {
data[key] = localStorage.getItem(key)
}
})
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'localStorage-backup.json'
a.click()
}
// 页面加载时自动诊断
window.onload = diagnose
</script>
</body>
</html>

318
web/public/sync-data.html Normal file
View File

@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据同步工具</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 40px;
max-width: 800px;
margin: 0 auto;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 10px;
}
.description {
color: #666;
margin-bottom: 30px;
line-height: 1.6;
}
.button-group {
display: flex;
gap: 10px;
margin: 20px 0;
}
button {
padding: 12px 24px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #18a058;
color: white;
}
.btn-primary:hover {
background: #16915e;
}
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.btn-danger {
background: #d03050;
color: white;
}
.btn-danger:hover {
background: #b02848;
}
.result {
margin-top: 20px;
padding: 15px;
border-radius: 4px;
display: none;
}
.result.show {
display: block;
}
.result.success {
background: #f0f9ff;
border: 1px solid #18a058;
color: #18a058;
}
.result.error {
background: #fff0f0;
border: 1px solid #d03050;
color: #d03050;
}
.result.info {
background: #f8f9fa;
border: 1px solid #909399;
color: #606266;
}
pre {
background: #f5f5f5;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
font-size: 12px;
line-height: 1.5;
}
.step {
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
margin: 15px 0;
border-left: 4px solid #18a058;
}
.step-title {
font-weight: bold;
color: #18a058;
margin-bottom: 8px;
}
</style>
</head>
<body>
<div class="container">
<h1>🔄 数据同步工具</h1>
<p class="description">
由于系统使用了不同的 LocalStorage 键名,需要将数据从 <code>model-services</code> 同步到 <code>model-providers</code>
<br>点击下方按钮完成一键同步。
</p>
<div class="button-group">
<button class="btn-primary" onclick="syncData()">
✓ 一键同步数据
</button>
<button class="btn-secondary" onclick="checkData()">
🔍 检查当前数据
</button>
<button class="btn-danger" onclick="clearOldData()">
🗑️ 清理旧数据
</button>
</div>
<div id="result" class="result"></div>
<div class="step">
<div class="step-title">📋 同步步骤说明:</div>
<ol>
<li>点击"检查当前数据"查看两个键的数据状态</li>
<li>点击"一键同步数据"将 model-services 的数据复制到 model-providers</li>
<li>刷新聊天页面,模型列表将显示正确的数据</li>
<li>(可选) 同步完成后可以点击"清理旧数据"删除 model-services</li>
</ol>
</div>
</div>
<script>
function showResult(message, type = 'info') {
const resultDiv = document.getElementById('result')
resultDiv.innerHTML = message
resultDiv.className = `result ${type} show`
}
function syncData() {
try {
// 读取旧数据
const oldData = localStorage.getItem('model-services')
const newData = localStorage.getItem('model-providers')
if (!oldData) {
showResult('⚠️ 未找到 model-services 数据,无需同步', 'info')
return
}
// 解析并验证数据
let services
try {
services = JSON.parse(oldData)
if (!Array.isArray(services)) {
throw new Error('数据格式不正确')
}
} catch (e) {
showResult(`❌ 数据解析失败: ${e.message}`, 'error')
return
}
// 保存到新的键
localStorage.setItem('model-providers', oldData)
// 验证同步结果
const synced = localStorage.getItem('model-providers')
const syncedData = JSON.parse(synced)
let summary = `
<div style="font-size: 16px; font-weight: bold; margin-bottom: 10px;">
✓ 同步成功!
</div>
<div style="margin-bottom: 10px;">
已将 ${services.length} 个服务从 model-services 同步到 model-providers
</div>
<div style="margin-top: 15px;">
<strong>同步的服务:</strong>
<ul style="margin: 10px 0; padding-left: 20px;">
`
services.forEach(service => {
const modelCount = service.models?.length || 0
summary += `<li>${service.name} (${service.type}) - ${modelCount} 个模型</li>`
})
summary += `
</ul>
</div>
<div style="margin-top: 15px; padding: 10px; background: #fff3cd; border-radius: 4px;">
⚡ <strong>下一步:</strong> 请刷新聊天页面查看效果
</div>
`
showResult(summary, 'success')
// 自动刷新页面提示
setTimeout(() => {
if (confirm('数据同步成功!是否立即打开聊天页面查看效果?')) {
window.open('/', '_blank')
}
}, 1000)
} catch (error) {
showResult(`❌ 同步失败: ${error.message}`, 'error')
console.error('Sync error:', error)
}
}
function checkData() {
try {
const oldData = localStorage.getItem('model-services')
const newData = localStorage.getItem('model-providers')
let report = '<div style="font-size: 16px; font-weight: bold; margin-bottom: 15px;">📊 数据检查报告</div>'
// 检查 model-services
report += '<div style="margin-bottom: 20px;">'
report += '<strong>model-services (旧键):</strong><br>'
if (oldData) {
try {
const services = JSON.parse(oldData)
report += `✓ 找到数据 (${services.length} 个服务)<br>`
report += '<ul style="margin: 5px 0; padding-left: 20px;">'
services.forEach(s => {
report += `<li>${s.name} - ${s.models?.length || 0} 个模型</li>`
})
report += '</ul>'
} catch (e) {
report += `⚠️ 数据格式错误: ${e.message}`
}
} else {
report += '✗ 无数据'
}
report += '</div>'
// 检查 model-providers
report += '<div style="margin-bottom: 20px;">'
report += '<strong>model-providers (新键):</strong><br>'
if (newData) {
try {
const providers = JSON.parse(newData)
report += `✓ 找到数据 (${providers.length} 个服务)<br>`
report += '<ul style="margin: 5px 0; padding-left: 20px;">'
providers.forEach(p => {
report += `<li>${p.name} - ${p.models?.length || 0} 个模型</li>`
})
report += '</ul>'
} catch (e) {
report += `⚠️ 数据格式错误: ${e.message}`
}
} else {
report += '✗ 无数据'
}
report += '</div>'
// 建议
report += '<div style="padding: 10px; background: #f0f9ff; border-radius: 4px; border-left: 4px solid #18a058;">'
if (oldData && !newData) {
report += '💡 建议: 点击"一键同步数据"将旧数据迁移到新键'
} else if (oldData && newData) {
const oldServices = JSON.parse(oldData)
const newProviders = JSON.parse(newData)
if (oldServices.length > newProviders.length) {
report += '💡 建议: model-services 的数据更新,建议重新同步'
} else {
report += '✓ 数据已同步,可以点击"清理旧数据"删除 model-services'
}
} else if (!oldData && newData) {
report += '✓ 数据正常,新键已有数据'
} else {
report += '⚠️ 未找到任何模型服务数据,请先在"模型服务"页面添加服务'
}
report += '</div>'
showResult(report, 'info')
} catch (error) {
showResult(`❌ 检查失败: ${error.message}`, 'error')
console.error('Check error:', error)
}
}
function clearOldData() {
if (!confirm('确定要删除 model-services 的旧数据吗?\n\n请确保已经完成数据同步')) {
return
}
try {
const oldData = localStorage.getItem('model-services')
if (!oldData) {
showResult(' model-services 中没有数据', 'info')
return
}
localStorage.removeItem('model-services')
showResult('✓ 旧数据已清理完成', 'success')
} catch (error) {
showResult(`❌ 清理失败: ${error.message}`, 'error')
}
}
// 页面加载时自动检查
window.onload = function() {
checkData()
}
</script>
</body>
</html>