diff --git a/authApi.js b/authApi.js new file mode 100644 index 0000000..fc93958 --- /dev/null +++ b/authApi.js @@ -0,0 +1,382 @@ +/** + * 授权验证 API 客户端 + * 负责与后端 Python 验证服务进行通信 + * Author: taiyi1224 + */ + +// 配置常量 +const AUTH_CONFIG = { + // ⚠️ 请修改为你的实际服务器地址 + API_URL: 'http://km.taisan.online/api/v1', + SOFTWARE_ID: 'QFFB', // 软件 ID(与后端一致) + SECRET_KEY: 'taiyi1224', // 密钥(与后端一致) + TIMEOUT: 10000, // 请求超时时间(毫秒) + RECHECK_INTERVAL: 60 * 60 * 1000 // ✅ 联网复查间隔:1小时(毫秒),可按需调整 +}; + +/** + * 生成签名 + */ +async function generateSignature(data) { + const encoder = new TextEncoder(); + const signatureData = `${data.software_id}${data.license_key}${data.machine_code}${data.timestamp}`; + const combined = signatureData + AUTH_CONFIG.SECRET_KEY; + const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(combined)); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); +} + +/** + * 获取机器码(基于浏览器指纹) + */ +async function getMachineCode() { + const cacheKey = 'tb_auth_machine_code'; + const cached = localStorage.getItem(cacheKey); + if (cached) return cached; + + try { + const fingerprint = [ + navigator.userAgent, + navigator.language, + navigator.platform, + screen.width, + screen.height, + new Date().getTimezoneOffset(), + ].join('|'); + + const encoder = new TextEncoder(); + const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(fingerprint)); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const machineCode = hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 32).toUpperCase(); + + localStorage.setItem(cacheKey, machineCode); + return machineCode; + } catch (error) { + console.error('[Auth] 生成机器码失败:', error); + const fallback = 'BROWSER_' + Date.now().toString(36).toUpperCase(); + localStorage.setItem(cacheKey, fallback); + return fallback; + } +} + +/** + * 测试服务器连接 + */ +async function testConnection() { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); + + const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + software_id: AUTH_CONFIG.SOFTWARE_ID, + license_key: 'TEST', + machine_code: await getMachineCode(), + timestamp: Math.floor(Date.now() / 1000), + signature: 'test' + }), + signal: controller.signal + }); + + clearTimeout(timeoutId); + return { success: true, message: '服务器连接正常' }; + } catch (error) { + if (error.name === 'AbortError') { + return { success: false, message: `连接超时,服务器可能无响应:${AUTH_CONFIG.API_URL}` }; + } + return { success: false, message: `无法连接到服务器:${error.message}\n请检查服务器地址是否正确:${AUTH_CONFIG.API_URL}` }; + } +} + +/** + * 在线验证卡密 + */ +async function verifyLicense(licenseKey) { + try { + const machineCode = await getMachineCode(); + const timestamp = Math.floor(Date.now() / 1000); + + const signature = await generateSignature({ + software_id: AUTH_CONFIG.SOFTWARE_ID, + license_key: licenseKey, + machine_code: machineCode, + timestamp: timestamp + }); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); + + const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + software_id: AUTH_CONFIG.SOFTWARE_ID, + license_key: licenseKey, + machine_code: machineCode, + timestamp: timestamp, + signature: signature, + software_version: chrome.runtime.getManifest().version + }), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + let errorMsg = `服务器返回错误:${response.status}`; + try { + const errorData = await response.json(); + errorMsg = errorData.message || errorMsg; + } catch (e) {} + + switch (response.status) { + case 503: errorMsg = '服务器暂时不可用,请稍后重试'; break; + case 500: errorMsg = '服务器内部错误,请联系管理员'; break; + case 404: errorMsg = 'API 接口不存在,请检查 API 地址'; break; + case 401: errorMsg = '签名验证失败,请检查密钥配置'; break; + } + + return { success: false, message: errorMsg }; + } + + const result = await response.json(); + + if (!result.success) { + return { success: false, message: result.message || '验证失败' }; + } + + const data = result.data || {}; + + return { + success: true, + message: result.message || '验证成功', + data: { + expire_time: data.expire_time, + machine_code: machineCode, + last_check: new Date().toISOString(), + license_key: data.license_key || licenseKey, + type: data.type, + type_name: data.type_name || '', + remaining_days: data.remaining_days, + product_name: data.product_name || '' + } + }; + + } catch (error) { + if (error.name === 'AbortError') { + return { success: false, message: `连接超时(${AUTH_CONFIG.TIMEOUT / 1000}秒),请检查网络连接或服务器地址:${AUTH_CONFIG.API_URL}` }; + } + return { success: false, message: `网络请求异常:${error.message}\n请检查网络连接和服务器地址:${AUTH_CONFIG.API_URL}` }; + } +} + +/** + * 解绑卡密 + */ +async function unbindLicense(licenseKey) { + try { + const machineCode = await getMachineCode(); + const timestamp = Math.floor(Date.now() / 1000); + + const signature = await generateSignature({ + software_id: AUTH_CONFIG.SOFTWARE_ID, + license_key: licenseKey, + machine_code: machineCode, + timestamp: timestamp + }); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); + + const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/unbind`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + software_id: AUTH_CONFIG.SOFTWARE_ID, + license_key: licenseKey, + machine_code: machineCode, + timestamp: timestamp, + signature: signature + }), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + let errorMsg = `服务器返回错误:${response.status}`; + try { + const errorData = await response.json(); + errorMsg = errorData.message || errorMsg; + } catch (e) {} + + switch (response.status) { + case 503: errorMsg = '服务器暂时不可用,请稍后重试'; break; + case 500: errorMsg = '服务器内部错误,请联系管理员'; break; + case 404: errorMsg = 'API 接口不存在,请检查 API 地址'; break; + case 401: errorMsg = '签名验证失败,请检查密钥配置'; break; + } + + return { success: false, message: errorMsg }; + } + + const result = await response.json(); + return { + success: result.success || false, + message: result.message || (result.success ? '解绑成功' : '解绑失败') + }; + + } catch (error) { + if (error.name === 'AbortError') { + return { success: false, message: `连接超时,请检查网络连接` }; + } + return { success: false, message: `网络请求失败:${error.message}` }; + } +} + +/** + * 解绑设备 + */ +async function unbindDevice() { + try { + const machineCode = await getMachineCode(); + const timestamp = Math.floor(Date.now() / 1000); + + const signature = await generateSignature({ + software_id: AUTH_CONFIG.SOFTWARE_ID, + machine_code: machineCode, + timestamp: timestamp + }); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); + + const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/unbind_device`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + software_id: AUTH_CONFIG.SOFTWARE_ID, + machine_code: machineCode, + timestamp: timestamp, + signature: signature + }), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + let errorMsg = `服务器返回错误:${response.status}`; + try { + const errorData = await response.json(); + errorMsg = errorData.message || errorMsg; + } catch (e) {} + + switch (response.status) { + case 503: errorMsg = '服务器暂时不可用,请稍后重试'; break; + case 500: errorMsg = '服务器内部错误,请联系管理员'; break; + case 404: errorMsg = 'API 接口不存在,请检查 API 地址'; break; + case 401: errorMsg = '签名验证失败,请检查密钥配置'; break; + } + + return { success: false, message: errorMsg }; + } + + const result = await response.json(); + return { + success: result.success || false, + message: result.message || (result.success ? '设备解绑成功' : '解绑失败') + }; + + } catch (error) { + if (error.name === 'AbortError') { + return { success: false, message: `连接超时,请检查网络连接` }; + } + return { success: false, message: `网络请求失败:${error.message}` }; + } +} + +/** + * 保存授权信息到本地存储 + */ +async function saveAuthData(authData) { + await chrome.storage.local.set({ tb_auth_data: authData }); +} + +/** + * 获取本地存储的授权信息 + */ +async function getAuthData() { + const result = await chrome.storage.local.get('tb_auth_data'); + return result.tb_auth_data || null; +} + +/** + * 清除授权信息 + */ +async function clearAuthData() { + await chrome.storage.local.remove('tb_auth_data'); +} + +/** + * ✅ 核心修复:检查是否已授权 + * + * 逻辑: + * 1. 本地无数据 → 未授权 + * 2. 距上次联网复查超过 RECHECK_INTERVAL → 联网验证 + * - 服务器返回成功 → 刷新 last_check,授权有效 + * - 服务器返回失败(卡密被删/禁用/过期)→ 清除本地授权,返回未授权 + * 3. 未超过间隔 → 使用本地缓存,避免频繁请求 + */ +async function isAuthorized() { + const authData = await getAuthData(); + + // 1. 本地没有授权数据 + if (!authData || !authData.license_key) { + return false; + } + + const now = Date.now(); + const lastCheck = authData.last_check ? new Date(authData.last_check).getTime() : 0; + const elapsed = now - lastCheck; + + // 2. 超过复查间隔,联网重新验证 + if (elapsed >= AUTH_CONFIG.RECHECK_INTERVAL) { + console.log('[Auth] 超过复查间隔,开始联网验证...'); + + const result = await verifyLicense(authData.license_key); + + if (result.success) { + // 验证通过,更新本地缓存(刷新 last_check) + await saveAuthData(result.data); + console.log('[Auth] 联网验证通过,授权有效'); + return true; + } else { + // 验证失败:卡密已被删除/禁用/过期,清除本地授权 + await clearAuthData(); + console.warn('[Auth] 联网验证失败,已清除本地授权:', result.message); + return false; + } + } + + // 3. 未超过间隔,使用本地缓存 + const remainingMin = Math.round((AUTH_CONFIG.RECHECK_INTERVAL - elapsed) / 60000); + console.log(`[Auth] 使用本地缓存,距下次复查还有 ${remainingMin} 分钟`); + return true; +} + +// 导出 API +export { + AUTH_CONFIG, + testConnection, + verifyLicense, + unbindLicense, + unbindDevice, + getMachineCode, + saveAuthData, + getAuthData, + clearAuthData, + isAuthorized +}; \ No newline at end of file diff --git a/server/data.db-shm b/server/data.db-shm index 3be6322..cffd42b 100644 Binary files a/server/data.db-shm and b/server/data.db-shm differ diff --git a/server/data.db-wal b/server/data.db-wal index 7cb1473..94f65a1 100644 Binary files a/server/data.db-wal and b/server/data.db-wal differ diff --git a/server/index.js b/server/index.js index 27cf68c..82472ef 100644 --- a/server/index.js +++ b/server/index.js @@ -41,6 +41,11 @@ const createNoteRoutes = require('./routes/notes'); const createConfigRoutes = require('./routes/configs'); const createTaskRoutes = require('./routes/tasks'); const createGenerateRoutes = require('./routes/generate'); +const createLicenseRoutes = require('./routes/license'); +const { requireLicense } = require('./lib/licenseAuth'); + +app.use('/api/license', createLicenseRoutes()); +app.use('/api', requireLicense({ allowPaths: ['/health'] })); app.use('/api/shops', createShopRoutes(db)); app.use('/api/products', createProductRoutes(db)); @@ -99,3 +104,4 @@ app.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}`); console.log(`API: http://localhost:${PORT}/api`); }); + diff --git a/server/routes/generate.js b/server/routes/generate.js index bfd3795..162c772 100644 --- a/server/routes/generate.js +++ b/server/routes/generate.js @@ -4,19 +4,18 @@ const { generateImage } = require('../lib/generate-image'); function createGenerateRoutes(db) { const router = express.Router(); + const runningJobs = new Set(); - // Helper: call AI text generation for a single prompt async function callAI(prompt, maxTokens = 2000) { const aiConfig = {}; const keys = ['ai_api_key', 'ai_base_url', 'ai_model', 'ai_temperature']; - for (const k of keys) { - const row = db.prepare('SELECT value FROM configs WHERE key = ?').get(k); - aiConfig[k] = row?.value || ''; + for (const key of keys) { + const row = db.prepare('SELECT value FROM configs WHERE key = ?').get(key); + aiConfig[key] = row?.value || ''; } if (!aiConfig.ai_api_key) throw new Error('AI API key not configured. Please set it in Settings.'); - const baseUrl = aiConfig.ai_base_url || 'https://api.deepseek.com'; - const response = await fetch(`${baseUrl}/chat/completions`, { + const response = await fetch(`${aiConfig.ai_base_url || 'https://api.deepseek.com'}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -39,7 +38,6 @@ function createGenerateRoutes(db) { return data.choices?.[0]?.message?.content || ''; } - // Helper: generate title for a note async function generateTitle(productTitle) { const titleRow = db.prepare('SELECT value FROM configs WHERE key = ?').get('title_prompt_template'); const fallbackRow = db.prepare('SELECT value FROM configs WHERE key = ?').get('prompt_template'); @@ -50,7 +48,6 @@ function createGenerateRoutes(db) { return title || productTitle; } - // Helper: generate content for a note async function generateContent(productTitle) { const contentRow = db.prepare('SELECT value FROM configs WHERE key = ?').get('content_prompt_template'); const fallbackRow = db.prepare('SELECT value FROM configs WHERE key = ?').get('prompt_template'); @@ -58,11 +55,10 @@ function createGenerateRoutes(db) { const prompt = template.replace(/\{\{title\}\}/g, productTitle); const generated = await callAI(prompt, 2000); const topicMatches = generated.match(/#[^\s#]+/g) || []; - const topics = topicMatches.map(t => t.replace('#', '').trim()); + const topics = topicMatches.map((topic) => topic.replace('#', '').trim()); return { content: generated.trim(), topics }; } - // Helper: generate title and content together (legacy fallback) async function generateText(productTitle) { const title = await generateTitle(productTitle); const { content, topics } = await generateContent(productTitle); @@ -70,12 +66,10 @@ function createGenerateRoutes(db) { } async function createNoteForProduct(product, options = {}) { - // Dedup: if a note is already being generated for this product, skip const existing = db.prepare( "SELECT id, title, content, topics, image_paths, image_prompt FROM notes WHERE product_id = ? AND status = 'generating' ORDER BY created_at DESC LIMIT 1" ).get(product.id); if (existing) { - console.log(`[generate] Note already generating for product ${product.id}, reusing ${existing.id}`); return { noteId: existing.id, title: existing.title, @@ -87,9 +81,8 @@ function createGenerateRoutes(db) { } const noteId = uuidv4(); - db.prepare( - "INSERT INTO notes (id, product_id, shop_id, title, content, status) VALUES (?, ?, ?, ?, '', 'generating')" - ).run(noteId, product.id, product.shop_id, product.title); + db.prepare("INSERT INTO notes (id, product_id, shop_id, title, content, status) VALUES (?, ?, ?, ?, '', 'generating')") + .run(noteId, product.id, product.shop_id, product.title); const { title, content, topics } = await generateText(product.title); const imageResult = await generateImage(db, noteId, product.title, options); @@ -99,16 +92,99 @@ function createGenerateRoutes(db) { db.prepare( "UPDATE notes SET title=?, content=?, topics=?, image_paths=?, image_prompt=?, status='generated', updated_at=CURRENT_TIMESTAMP WHERE id=?" ).run(title, content, JSON.stringify(topics), JSON.stringify(imagePaths), imagePrompt, noteId); - db.prepare( - "UPDATE products SET status='generated', updated_at=CURRENT_TIMESTAMP WHERE id=?" - ).run(product.id); + db.prepare("UPDATE products SET status='generated', updated_at=CURRENT_TIMESTAMP WHERE id=?").run(product.id); return { noteId, title, content, topics, imagePaths, imagePrompt }; } - // Generate note for a product using AI - router.post('/note', async (req, res) => { - const { product_id, shop_id } = req.body; + function updateJob(jobId, fields) { + const keys = Object.keys(fields); + if (keys.length === 0) return; + const setSql = keys.map((key) => `${key} = ?`).join(', '); + const values = keys.map((key) => fields[key]); + values.push(jobId); + db.prepare(`UPDATE generation_jobs SET ${setSql}, updated_at=CURRENT_TIMESTAMP WHERE id = ?`).run(...values); + } + + async function runGenerationJob(jobId, productIds, countPerProduct, options = {}) { + if (runningJobs.has(jobId)) return; + runningJobs.add(jobId); + try { + updateJob(jobId, { status: 'running', message: '开始生成' }); + let completedNotes = 0; + let failedNotes = 0; + + for (const productId of productIds) { + const product = db.prepare('SELECT * FROM products WHERE id = ?').get(productId); + if (!product) { + failedNotes += countPerProduct; + completedNotes += countPerProduct; + updateJob(jobId, { + completed_notes: completedNotes, + failed_notes: failedNotes, + current_product_id: productId, + current_product_title: '', + message: '商品不存在', + }); + continue; + } + + db.prepare("UPDATE products SET status='generating', updated_at=CURRENT_TIMESTAMP WHERE id=?").run(product.id); + updateJob(jobId, { + current_product_id: product.id, + current_product_title: product.title, + message: `正在生成:${product.title}`, + }); + + for (let index = 0; index < countPerProduct; index += 1) { + try { + await createNoteForProduct(product, options); + } catch (error) { + failedNotes += 1; + } finally { + completedNotes += 1; + updateJob(jobId, { + completed_notes: completedNotes, + failed_notes: failedNotes, + message: `进度 ${completedNotes}/${productIds.length * countPerProduct}`, + }); + } + } + + db.prepare("UPDATE products SET status='generated', updated_at=CURRENT_TIMESTAMP WHERE id=?").run(product.id); + } + + updateJob(jobId, { + status: 'completed', + message: failedNotes > 0 ? `完成,失败 ${failedNotes} 条` : '全部完成', + }); + } catch (error) { + updateJob(jobId, { status: 'failed', message: error.message }); + } finally { + runningJobs.delete(jobId); + } + } + + function enqueueGeneration(jobId, productIds, countPerProduct, options) { + setImmediate(() => { + runGenerationJob(jobId, productIds, countPerProduct, options); + }); + } + + router.get('/jobs', (req, res) => { + const { shop_id } = req.query; + let sql = 'SELECT * FROM generation_jobs WHERE 1=1'; + const params = []; + if (shop_id) { + sql += ' AND shop_id = ?'; + params.push(shop_id); + } + sql += ' ORDER BY created_at DESC LIMIT 20'; + res.json(db.prepare(sql).all(...params)); + }); + + router.post('/note', (req, res) => { + const { product_id, shop_id, count_per_product, image_count, image_size, image_prompt_template } = req.body; if (!product_id || !shop_id) { return res.status(400).json({ error: 'product_id and shop_id required' }); } @@ -116,21 +192,15 @@ function createGenerateRoutes(db) { const product = db.prepare('SELECT * FROM products WHERE id = ?').get(product_id); if (!product) return res.status(404).json({ error: 'Product not found' }); - db.prepare("UPDATE products SET status = 'generating', updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(product_id); - - try { - const result = await createNoteForProduct(product); - res.json({ note_id: result.noteId, title: result.title, content: result.content, topics: result.topics, image_paths: result.imagePaths }); - } catch (error) { - db.prepare("UPDATE notes SET status='failed', error_message=?, updated_at=CURRENT_TIMESTAMP WHERE id=(SELECT id FROM notes WHERE product_id=? ORDER BY created_at DESC LIMIT 1)") - .run(error.message, product_id); - db.prepare("UPDATE products SET status='pending', updated_at=CURRENT_TIMESTAMP WHERE id=?") - .run(product_id); - res.status(500).json({ error: error.message }); - } + const repeatCount = Math.max(1, parseInt(count_per_product || '1', 10) || 1); + const jobId = uuidv4(); + db.prepare( + 'INSERT INTO generation_jobs (id, shop_id, status, total_products, total_notes, message) VALUES (?, ?, ?, ?, ?, ?)' + ).run(jobId, shop_id, 'pending', 1, repeatCount, '等待开始'); + enqueueGeneration(jobId, [product_id], repeatCount, { image_count, image_size, image_prompt_template }); + res.status(201).json({ job_id: jobId, status: 'pending' }); }); - // Generate image for a note router.post('/image', async (req, res) => { const { note_id } = req.body; if (!note_id) return res.status(400).json({ error: 'note_id required' }); @@ -152,10 +222,10 @@ function createGenerateRoutes(db) { }); async function batchGenerate(req, res) { - const { shop_id, product_ids, image_count, image_size, image_prompt_template } = req.body; + const { shop_id, product_ids, count_per_product, image_count, image_size, image_prompt_template } = req.body; if (!shop_id) return res.status(400).json({ error: 'shop_id required' }); - let sql = "SELECT * FROM products WHERE shop_id = ?"; + let sql = 'SELECT * FROM products WHERE shop_id = ?'; const params = [shop_id]; if (Array.isArray(product_ids) && product_ids.length > 0) { sql += ` AND id IN (${product_ids.map(() => '?').join(',')})`; @@ -164,19 +234,13 @@ function createGenerateRoutes(db) { const products = db.prepare(sql).all(...params); if (products.length === 0) return res.json({ count: 0, message: 'No pending products' }); - const results = []; - for (const product of products) { - try { - db.prepare("UPDATE products SET status = 'generating', updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(product.id); - const result = await createNoteForProduct(product, { image_count, image_size, image_prompt_template }); - results.push({ product_id: product.id, note_id: result.noteId, title: result.title, images: result.imagePaths.length }); - } catch (error) { - results.push({ product_id: product.id, error: error.message }); - db.prepare("UPDATE products SET status = 'pending', updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(product.id); - } - } - - return res.json({ total: products.length, results, images_generated: results.reduce((sum, item) => sum + (item.images || 0), 0) }); + const repeatCount = Math.max(1, parseInt(count_per_product || '1', 10) || 1); + const jobId = uuidv4(); + db.prepare( + 'INSERT INTO generation_jobs (id, shop_id, status, total_products, total_notes, message) VALUES (?, ?, ?, ?, ?, ?)' + ).run(jobId, shop_id, 'pending', products.length, products.length * repeatCount, '等待开始'); + enqueueGeneration(jobId, products.map((product) => product.id), repeatCount, { image_count, image_size, image_prompt_template }); + res.status(201).json({ job_id: jobId, total: products.length, total_notes: products.length * repeatCount }); } router.post('/', batchGenerate); diff --git a/web/src/App.jsx b/web/src/App.jsx index 83ae0d3..33c9483 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -1,5 +1,9 @@ -import { Routes, Route, NavLink, Navigate } from "react-router-dom"; +import { Routes, Route, NavLink, Navigate } from "react-router-dom"; +import { useState } from "react"; import { ToastProvider } from "./toast"; +import ErrorBoundary from "./ErrorBoundary"; +import LicenseGate from "./pages/LicenseGate"; +import AuthToolbar from "./components/AuthToolbar"; import Dashboard from "./pages/Dashboard"; import Shops from "./pages/Shops"; import Products from "./pages/Products"; @@ -8,30 +12,28 @@ import NotesUnapproved from "./pages/NotesUnapproved"; import SettingsDeepseek from "./pages/SettingsDeepseek"; import SettingsAgnes from "./pages/SettingsAgnes"; import SettingsPrompts from "./pages/SettingsPrompts"; -import { useState } from "react"; -import ErrorBoundary from "./ErrorBoundary"; const navSections = [ { title: "小红书管理", items: [ - { to: "/shops", label: "账号管理", icon: "🏪" }, - { to: "/products", label: "商品管理", icon: "📦" }, + { to: "/shops", label: "账号管理", icon: "店" }, + { to: "/products", label: "商品管理", icon: "品" }, ], }, { title: "笔记管理", items: [ - { to: "/notes/approved", label: "已审核笔记", icon: "✅" }, - { to: "/notes/unapproved", label: "未审核笔记", icon: "📝" }, + { to: "/notes/approved", label: "已审核笔记", icon: "审" }, + { to: "/notes/unapproved", label: "未审核笔记", icon: "待" }, ], }, { - title: "AI 管理", + title: "AI 配置", items: [ - { to: "/settings/deepseek", label: "Deep Seek 配置", icon: "🤖" }, - { to: "/settings/agnes", label: "Agnes 配置", icon: "🎨" }, - { to: "/settings/prompts", label: "提示词管理", icon: "💬" }, + { to: "/settings/deepseek", label: "DeepSeek 配置", icon: "D" }, + { to: "/settings/agnes", label: "Agnes 配置", icon: "A" }, + { to: "/settings/prompts", label: "提示词配置", icon: "P" }, ], }, ]; @@ -47,32 +49,26 @@ function getNavClass(isActive) { function Sidebar({ collapsed, onToggle }) { return ( -