mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-27 21:23:12 +08:00
feat: refactor AI interpretation system and fix recordId issues
- Refactored AI interpretation table to use proper 1-to-1 relationship with reading records - Fixed recordId parameter passing in AnalysisResultDisplay component - Updated database schema to use reading_id instead of analysis_id - Removed complex string ID generation logic - Fixed TypeScript type definitions for all ID fields - Added database migration scripts for AI interpretation refactoring - Improved error handling and debugging capabilities
This commit is contained in:
202
cleanup-duplicate-records.cjs
Normal file
202
cleanup-duplicate-records.cjs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
const { getDB } = require('./server/database/index.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理重复记录的脚本
|
||||||
|
* 保留每组重复记录中最新的一条,删除其他重复记录
|
||||||
|
* 重新关联AI解读记录到保留的记录
|
||||||
|
*/
|
||||||
|
function cleanupDuplicateRecords() {
|
||||||
|
try {
|
||||||
|
// 初始化数据库连接
|
||||||
|
const { dbManager } = require('./server/database/index.cjs');
|
||||||
|
dbManager.init();
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
console.log('=== 开始清理重复记录 ===\n');
|
||||||
|
|
||||||
|
// 开始事务
|
||||||
|
db.exec('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 找出所有重复记录组
|
||||||
|
console.log('1. 识别重复记录组...');
|
||||||
|
const duplicateGroups = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
reading_type,
|
||||||
|
user_id,
|
||||||
|
COUNT(*) as count,
|
||||||
|
GROUP_CONCAT(id ORDER BY created_at DESC) as ids,
|
||||||
|
MAX(id) as keep_id
|
||||||
|
FROM numerology_readings
|
||||||
|
GROUP BY name, reading_type, user_id
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY count DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log(`找到 ${duplicateGroups.length} 个重复记录组`);
|
||||||
|
|
||||||
|
let totalDuplicates = 0;
|
||||||
|
let totalDeleted = 0;
|
||||||
|
let aiRecordsUpdated = 0;
|
||||||
|
|
||||||
|
// 2. 处理每个重复组
|
||||||
|
for (const group of duplicateGroups) {
|
||||||
|
const ids = group.ids.split(',').map(id => parseInt(id));
|
||||||
|
const keepId = group.keep_id; // 保留最新的记录
|
||||||
|
const deleteIds = ids.filter(id => id !== keepId);
|
||||||
|
|
||||||
|
totalDuplicates += (group.count - 1);
|
||||||
|
|
||||||
|
console.log(`\n处理组: ${group.name} (${group.reading_type})`);
|
||||||
|
console.log(` 总记录: ${group.count}条`);
|
||||||
|
console.log(` 保留记录: ID ${keepId}`);
|
||||||
|
console.log(` 删除记录: ${deleteIds.join(', ')}`);
|
||||||
|
|
||||||
|
// 3. 更新AI解读记录的关联
|
||||||
|
for (const deleteId of deleteIds) {
|
||||||
|
const aiRecords = db.prepare(`
|
||||||
|
SELECT id FROM ai_interpretations
|
||||||
|
WHERE analysis_id = ? AND user_id = ?
|
||||||
|
`).all(deleteId.toString(), group.user_id);
|
||||||
|
|
||||||
|
if (aiRecords.length > 0) {
|
||||||
|
console.log(` 发现 ${aiRecords.length} 条AI解读记录需要重新关联`);
|
||||||
|
|
||||||
|
// 检查目标记录是否已有AI解读
|
||||||
|
const existingAI = db.prepare(`
|
||||||
|
SELECT id FROM ai_interpretations
|
||||||
|
WHERE analysis_id = ? AND user_id = ?
|
||||||
|
`).get(keepId.toString(), group.user_id);
|
||||||
|
|
||||||
|
if (existingAI) {
|
||||||
|
console.log(` 目标记录已有AI解读,删除重复的AI解读记录`);
|
||||||
|
// 删除重复的AI解读记录
|
||||||
|
const deleteAI = db.prepare(`
|
||||||
|
DELETE FROM ai_interpretations
|
||||||
|
WHERE analysis_id = ? AND user_id = ?
|
||||||
|
`);
|
||||||
|
deleteAI.run(deleteId.toString(), group.user_id);
|
||||||
|
} else {
|
||||||
|
console.log(` 重新关联AI解读记录到保留的记录`);
|
||||||
|
// 更新AI解读记录的analysis_id
|
||||||
|
const updateAI = db.prepare(`
|
||||||
|
UPDATE ai_interpretations
|
||||||
|
SET analysis_id = ?
|
||||||
|
WHERE analysis_id = ? AND user_id = ?
|
||||||
|
`);
|
||||||
|
const result = updateAI.run(keepId.toString(), deleteId.toString(), group.user_id);
|
||||||
|
aiRecordsUpdated += result.changes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 删除重复的历史记录
|
||||||
|
if (deleteIds.length > 0) {
|
||||||
|
const deleteStmt = db.prepare(`
|
||||||
|
DELETE FROM numerology_readings
|
||||||
|
WHERE id IN (${deleteIds.map(() => '?').join(',')})
|
||||||
|
`);
|
||||||
|
const result = deleteStmt.run(...deleteIds);
|
||||||
|
totalDeleted += result.changes;
|
||||||
|
console.log(` 删除了 ${result.changes} 条重复记录`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 清理无法关联的AI解读记录
|
||||||
|
console.log('\n5. 清理无法关联的AI解读记录...');
|
||||||
|
const orphanedAI = db.prepare(`
|
||||||
|
SELECT ai.id, ai.analysis_id, ai.analysis_type
|
||||||
|
FROM ai_interpretations ai
|
||||||
|
LEFT JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
||||||
|
WHERE nr.id IS NULL
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
if (orphanedAI.length > 0) {
|
||||||
|
console.log(`发现 ${orphanedAI.length} 条无法关联的AI解读记录`);
|
||||||
|
orphanedAI.forEach(record => {
|
||||||
|
console.log(` AI记录 ${record.id}: analysis_id=${record.analysis_id}, type=${record.analysis_type}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除无法关联的AI解读记录
|
||||||
|
const deleteOrphanedAI = db.prepare(`
|
||||||
|
DELETE FROM ai_interpretations
|
||||||
|
WHERE id IN (${orphanedAI.map(() => '?').join(',')})
|
||||||
|
`);
|
||||||
|
const result = deleteOrphanedAI.run(...orphanedAI.map(r => r.id));
|
||||||
|
console.log(`删除了 ${result.changes} 条无法关联的AI解读记录`);
|
||||||
|
} else {
|
||||||
|
console.log('没有发现无法关联的AI解读记录');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
db.exec('COMMIT');
|
||||||
|
|
||||||
|
// 6. 输出清理结果
|
||||||
|
console.log('\n=== 清理完成 ===');
|
||||||
|
console.log(`处理的重复组: ${duplicateGroups.length}`);
|
||||||
|
console.log(`总重复记录: ${totalDuplicates}`);
|
||||||
|
console.log(`删除的记录: ${totalDeleted}`);
|
||||||
|
console.log(`更新的AI解读: ${aiRecordsUpdated}`);
|
||||||
|
console.log(`删除的孤立AI解读: ${orphanedAI.length}`);
|
||||||
|
|
||||||
|
// 7. 验证清理结果
|
||||||
|
console.log('\n=== 验证清理结果 ===');
|
||||||
|
const remainingDuplicates = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM (
|
||||||
|
SELECT name, reading_type, user_id, COUNT(*) as cnt
|
||||||
|
FROM numerology_readings
|
||||||
|
GROUP BY name, reading_type, user_id
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
)
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
console.log(`剩余重复记录组: ${remainingDuplicates.count}`);
|
||||||
|
|
||||||
|
const totalRecords = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM numerology_readings
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
console.log(`当前历史记录总数: ${totalRecords.count}`);
|
||||||
|
|
||||||
|
const totalAI = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM ai_interpretations
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
console.log(`当前AI解读记录总数: ${totalAI.count}`);
|
||||||
|
|
||||||
|
const matchedAI = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM ai_interpretations ai
|
||||||
|
JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
console.log(`成功关联的AI解读: ${matchedAI.count}`);
|
||||||
|
console.log(`AI解读关联率: ${((matchedAI.count / totalAI.count) * 100).toFixed(1)}%`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// 回滚事务
|
||||||
|
db.exec('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清理过程中发生错误:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
try {
|
||||||
|
cleanupDuplicateRecords();
|
||||||
|
console.log('\n✅ 数据清理成功完成');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ 数据清理失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { cleanupDuplicateRecords };
|
||||||
180
debug-ai-button-status.cjs
Normal file
180
debug-ai-button-status.cjs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/**
|
||||||
|
* 调试AI解读按钮状态的脚本
|
||||||
|
* 检查AI配置和recordId传递情况
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 模拟前端AI配置检查
|
||||||
|
function checkAIConfig() {
|
||||||
|
console.log('=== AI配置检查 ===');
|
||||||
|
|
||||||
|
// 模拟默认配置
|
||||||
|
const defaultConfig = {
|
||||||
|
apiKey: 'dee444451bdf4232920a88ef430ce753.Z4SAbECrSnf5JMq7',
|
||||||
|
apiUrl: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
|
||||||
|
modelName: 'GLM-4.5',
|
||||||
|
maxTokens: 50000,
|
||||||
|
temperature: 0.6,
|
||||||
|
timeout: 120000,
|
||||||
|
stream: true
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('默认AI配置:');
|
||||||
|
console.log(JSON.stringify(defaultConfig, null, 2));
|
||||||
|
|
||||||
|
// 验证配置
|
||||||
|
const isValid = !!(defaultConfig.apiKey && defaultConfig.apiUrl && defaultConfig.modelName);
|
||||||
|
console.log(`AI配置是否有效: ${isValid}`);
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查数据库中的记录和AI解读状态
|
||||||
|
function checkRecordStatus() {
|
||||||
|
try {
|
||||||
|
const { getDB } = require('./server/database/index.cjs');
|
||||||
|
const { dbManager } = require('./server/database/index.cjs');
|
||||||
|
|
||||||
|
dbManager.init();
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
console.log('\n=== 数据库记录状态检查 ===');
|
||||||
|
|
||||||
|
// 检查最近的历史记录
|
||||||
|
const recentRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
reading_type,
|
||||||
|
created_at,
|
||||||
|
datetime(created_at, 'localtime') as local_time
|
||||||
|
FROM numerology_readings
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 10
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log('\n最近的历史记录:');
|
||||||
|
recentRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.local_time}`);
|
||||||
|
|
||||||
|
// 检查是否有对应的AI解读
|
||||||
|
const aiRecord = db.prepare(`
|
||||||
|
SELECT id, success FROM ai_interpretations
|
||||||
|
WHERE analysis_id = ? AND user_id = (
|
||||||
|
SELECT user_id FROM numerology_readings WHERE id = ?
|
||||||
|
)
|
||||||
|
`).get(record.id.toString(), record.id);
|
||||||
|
|
||||||
|
if (aiRecord) {
|
||||||
|
console.log(` → 有AI解读记录 (ID: ${aiRecord.id}, success: ${aiRecord.success})`);
|
||||||
|
} else {
|
||||||
|
console.log(` → 无AI解读记录`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查AI解读记录
|
||||||
|
const aiRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
analysis_id,
|
||||||
|
analysis_type,
|
||||||
|
success,
|
||||||
|
created_at,
|
||||||
|
datetime(created_at, 'localtime') as local_time
|
||||||
|
FROM ai_interpretations
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 5
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log('\n最近的AI解读记录:');
|
||||||
|
aiRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}, success: ${record.success}, created: ${record.local_time}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { recentRecords, aiRecords };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('检查数据库记录时发生错误:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟AI解读按钮状态检查
|
||||||
|
function simulateButtonStatus(recordId, hasAIConfig) {
|
||||||
|
console.log('\n=== AI解读按钮状态模拟 ===');
|
||||||
|
|
||||||
|
const hasValidId = !!recordId;
|
||||||
|
const isConfigValid = hasAIConfig;
|
||||||
|
|
||||||
|
console.log(`recordId: ${recordId}`);
|
||||||
|
console.log(`hasValidId: ${hasValidId}`);
|
||||||
|
console.log(`isConfigValid: ${isConfigValid}`);
|
||||||
|
|
||||||
|
// 模拟按钮禁用逻辑
|
||||||
|
const isDisabled = !isConfigValid || !hasValidId;
|
||||||
|
|
||||||
|
console.log(`按钮是否禁用: ${isDisabled}`);
|
||||||
|
|
||||||
|
if (isDisabled) {
|
||||||
|
console.log('禁用原因:');
|
||||||
|
if (!isConfigValid) {
|
||||||
|
console.log(' - AI配置无效');
|
||||||
|
}
|
||||||
|
if (!hasValidId) {
|
||||||
|
console.log(' - 缺少有效的recordId');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('按钮应该可以点击');
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
function debugAIButtonStatus() {
|
||||||
|
console.log('=== AI解读按钮状态调试 ===\n');
|
||||||
|
|
||||||
|
// 1. 检查AI配置
|
||||||
|
const isAIConfigValid = checkAIConfig();
|
||||||
|
|
||||||
|
// 2. 检查数据库记录
|
||||||
|
const dbStatus = checkRecordStatus();
|
||||||
|
|
||||||
|
if (dbStatus && dbStatus.recentRecords.length > 0) {
|
||||||
|
// 3. 模拟最新记录的按钮状态
|
||||||
|
const latestRecord = dbStatus.recentRecords[0];
|
||||||
|
console.log(`\n=== 模拟最新记录 (ID: ${latestRecord.id}) 的按钮状态 ===`);
|
||||||
|
|
||||||
|
const canClick = simulateButtonStatus(latestRecord.id, isAIConfigValid);
|
||||||
|
|
||||||
|
console.log('\n=== 总结 ===');
|
||||||
|
console.log(`最新记录: ${latestRecord.name} (${latestRecord.reading_type})`);
|
||||||
|
console.log(`记录ID: ${latestRecord.id}`);
|
||||||
|
console.log(`AI配置有效: ${isAIConfigValid}`);
|
||||||
|
console.log(`AI解读按钮可点击: ${canClick}`);
|
||||||
|
|
||||||
|
if (!canClick) {
|
||||||
|
console.log('\n🔧 解决建议:');
|
||||||
|
if (!isAIConfigValid) {
|
||||||
|
console.log('1. 检查AI配置是否正确设置');
|
||||||
|
console.log('2. 确认API密钥、URL和模型名称都已配置');
|
||||||
|
}
|
||||||
|
if (!latestRecord.id) {
|
||||||
|
console.log('1. 检查分析结果保存逻辑');
|
||||||
|
console.log('2. 确认recordId正确传递给AI解读按钮');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('\n❌ 没有找到历史记录,请先进行分析');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
try {
|
||||||
|
debugAIButtonStatus();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调试过程中发生错误:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { debugAIButtonStatus };
|
||||||
191
debug-ai-interpretation-matching.cjs
Normal file
191
debug-ai-interpretation-matching.cjs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
const { getDB } = require('./server/database/index.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试AI解读记录匹配问题
|
||||||
|
* 检查历史记录与AI解读记录的ID匹配情况
|
||||||
|
*/
|
||||||
|
function debugAIInterpretationMatching() {
|
||||||
|
try {
|
||||||
|
// 初始化数据库连接
|
||||||
|
const { dbManager } = require('./server/database/index.cjs');
|
||||||
|
dbManager.init();
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
console.log('=== AI解读记录匹配调试 ===\n');
|
||||||
|
|
||||||
|
// 1. 获取最近的历史记录
|
||||||
|
console.log('1. 最近的历史记录:');
|
||||||
|
const recentRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
reading_type,
|
||||||
|
created_at,
|
||||||
|
datetime(created_at, 'localtime') as local_time
|
||||||
|
FROM numerology_readings
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 10
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
recentRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. ID: ${record.id} (${typeof record.id}), name: ${record.name}, type: ${record.reading_type}, created: ${record.local_time}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 获取所有AI解读记录
|
||||||
|
console.log('\n2. 所有AI解读记录:');
|
||||||
|
const aiRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
analysis_id,
|
||||||
|
analysis_type,
|
||||||
|
success,
|
||||||
|
created_at,
|
||||||
|
datetime(created_at, 'localtime') as local_time
|
||||||
|
FROM ai_interpretations
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
aiRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. AI_ID: ${record.id}, analysis_id: ${record.analysis_id} (${typeof record.analysis_id}), type: ${record.analysis_type}, success: ${record.success}, created: ${record.local_time}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 检查每个历史记录是否有对应的AI解读
|
||||||
|
console.log('\n3. 历史记录与AI解读匹配检查:');
|
||||||
|
recentRecords.forEach((record, index) => {
|
||||||
|
const recordIdStr = record.id.toString();
|
||||||
|
const recordIdNum = parseInt(record.id);
|
||||||
|
|
||||||
|
// 尝试字符串匹配
|
||||||
|
const aiByString = db.prepare(`
|
||||||
|
SELECT id, analysis_id, success FROM ai_interpretations
|
||||||
|
WHERE analysis_id = ?
|
||||||
|
`).get(recordIdStr);
|
||||||
|
|
||||||
|
// 尝试数字匹配
|
||||||
|
const aiByNumber = db.prepare(`
|
||||||
|
SELECT id, analysis_id, success FROM ai_interpretations
|
||||||
|
WHERE analysis_id = ?
|
||||||
|
`).get(recordIdNum);
|
||||||
|
|
||||||
|
console.log(` ${index + 1}. 历史记录 ${record.id}:`);
|
||||||
|
console.log(` - 字符串匹配 '${recordIdStr}': ${aiByString ? `找到 AI_ID=${aiByString.id}` : '未找到'}`);
|
||||||
|
console.log(` - 数字匹配 ${recordIdNum}: ${aiByNumber ? `找到 AI_ID=${aiByNumber.id}` : '未找到'}`);
|
||||||
|
|
||||||
|
if (aiByString || aiByNumber) {
|
||||||
|
const ai = aiByString || aiByNumber;
|
||||||
|
console.log(` → 有AI解读: analysis_id=${ai.analysis_id}, success=${ai.success}`);
|
||||||
|
} else {
|
||||||
|
console.log(` → 无AI解读记录`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 检查AI解读记录是否有对应的历史记录
|
||||||
|
console.log('\n4. AI解读记录与历史记录匹配检查:');
|
||||||
|
aiRecords.forEach((aiRecord, index) => {
|
||||||
|
const analysisIdStr = aiRecord.analysis_id.toString();
|
||||||
|
const analysisIdNum = parseInt(aiRecord.analysis_id);
|
||||||
|
|
||||||
|
// 尝试字符串匹配
|
||||||
|
const historyByString = db.prepare(`
|
||||||
|
SELECT id, name, reading_type FROM numerology_readings
|
||||||
|
WHERE id = ?
|
||||||
|
`).get(analysisIdStr);
|
||||||
|
|
||||||
|
// 尝试数字匹配
|
||||||
|
const historyByNumber = db.prepare(`
|
||||||
|
SELECT id, name, reading_type FROM numerology_readings
|
||||||
|
WHERE id = ?
|
||||||
|
`).get(analysisIdNum);
|
||||||
|
|
||||||
|
console.log(` ${index + 1}. AI解读 analysis_id=${aiRecord.analysis_id}:`);
|
||||||
|
console.log(` - 字符串匹配 '${analysisIdStr}': ${historyByString ? `找到 ${historyByString.name} (${historyByString.reading_type})` : '未找到'}`);
|
||||||
|
console.log(` - 数字匹配 ${analysisIdNum}: ${historyByNumber ? `找到 ${historyByNumber.name} (${historyByNumber.reading_type})` : '未找到'}`);
|
||||||
|
|
||||||
|
if (!historyByString && !historyByNumber) {
|
||||||
|
console.log(` → 孤立的AI解读记录,无对应历史记录`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. 模拟前端API调用
|
||||||
|
console.log('\n5. 模拟前端API调用:');
|
||||||
|
const testIds = recentRecords.slice(0, 3).map(r => r.id);
|
||||||
|
|
||||||
|
testIds.forEach(id => {
|
||||||
|
const idStr = id.toString();
|
||||||
|
console.log(`\n 测试ID: ${id} (string: '${idStr}')`);
|
||||||
|
|
||||||
|
// 模拟API调用逻辑
|
||||||
|
const aiRecord = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
ai.id,
|
||||||
|
ai.analysis_id,
|
||||||
|
ai.success,
|
||||||
|
ai.content,
|
||||||
|
ai.created_at,
|
||||||
|
ai.model,
|
||||||
|
ai.tokens_used,
|
||||||
|
ai.error_message,
|
||||||
|
nr.name,
|
||||||
|
nr.reading_type
|
||||||
|
FROM ai_interpretations ai
|
||||||
|
LEFT JOIN numerology_readings nr ON ai.analysis_id = nr.id
|
||||||
|
WHERE ai.analysis_id = ? AND ai.user_id = (
|
||||||
|
SELECT user_id FROM numerology_readings WHERE id = ?
|
||||||
|
)
|
||||||
|
`).get(idStr, id);
|
||||||
|
|
||||||
|
if (aiRecord) {
|
||||||
|
console.log(` ✅ API会返回: success=${aiRecord.success}, content长度=${aiRecord.content?.length || 0}`);
|
||||||
|
console.log(` 关联记录: ${aiRecord.name} (${aiRecord.reading_type})`);
|
||||||
|
} else {
|
||||||
|
console.log(` ❌ API会返回: 404 Not Found`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. 总结
|
||||||
|
console.log('\n=== 总结 ===');
|
||||||
|
const totalHistory = recentRecords.length;
|
||||||
|
const totalAI = aiRecords.length;
|
||||||
|
const matchedCount = recentRecords.filter(record => {
|
||||||
|
const recordIdStr = record.id.toString();
|
||||||
|
const recordIdNum = parseInt(record.id);
|
||||||
|
const aiByString = db.prepare(`SELECT id FROM ai_interpretations WHERE analysis_id = ?`).get(recordIdStr);
|
||||||
|
const aiByNumber = db.prepare(`SELECT id FROM ai_interpretations WHERE analysis_id = ?`).get(recordIdNum);
|
||||||
|
return aiByString || aiByNumber;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
console.log(`历史记录总数: ${totalHistory}`);
|
||||||
|
console.log(`AI解读记录总数: ${totalAI}`);
|
||||||
|
console.log(`匹配成功数: ${matchedCount}`);
|
||||||
|
console.log(`匹配率: ${((matchedCount / totalHistory) * 100).toFixed(1)}%`);
|
||||||
|
|
||||||
|
if (matchedCount === 0) {
|
||||||
|
console.log('\n⚠️ 没有任何匹配的记录,可能的原因:');
|
||||||
|
console.log('1. AI解读记录的analysis_id与历史记录的id不匹配');
|
||||||
|
console.log('2. 数据类型不一致(字符串 vs 数字)');
|
||||||
|
console.log('3. AI解读记录被意外删除或损坏');
|
||||||
|
} else if (matchedCount < totalHistory) {
|
||||||
|
console.log(`\n⚠️ 部分记录不匹配,${totalHistory - matchedCount}条历史记录没有AI解读`);
|
||||||
|
} else {
|
||||||
|
console.log('\n✅ 所有记录都有对应的AI解读');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调试过程中发生错误:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
try {
|
||||||
|
debugAIInterpretationMatching();
|
||||||
|
console.log('\n✅ 调试完成');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ 调试失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { debugAIInterpretationMatching };
|
||||||
158
debug-ai-interpretations.cjs
Normal file
158
debug-ai-interpretations.cjs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
const { getDB } = require('./server/database/index.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试AI解读记录与历史记录匹配问题的脚本
|
||||||
|
*/
|
||||||
|
function debugAIInterpretations() {
|
||||||
|
try {
|
||||||
|
// 初始化数据库连接
|
||||||
|
const { dbManager } = require('./server/database/index.cjs');
|
||||||
|
dbManager.init();
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
console.log('=== AI解读记录与历史记录匹配分析 ===\n');
|
||||||
|
|
||||||
|
// 1. 查看所有AI解读记录
|
||||||
|
console.log('1. 所有AI解读记录:');
|
||||||
|
const aiInterpretations = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
analysis_id,
|
||||||
|
analysis_type,
|
||||||
|
created_at,
|
||||||
|
success
|
||||||
|
FROM ai_interpretations
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log(`总共有 ${aiInterpretations.length} 条AI解读记录`);
|
||||||
|
aiInterpretations.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}, created: ${record.created_at}, success: ${record.success}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n2. 所有历史记录:');
|
||||||
|
const historyRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
reading_type,
|
||||||
|
created_at
|
||||||
|
FROM numerology_readings
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log(`总共有 ${historyRecords.length} 条历史记录`);
|
||||||
|
historyRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.created_at}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n3. 匹配分析:');
|
||||||
|
|
||||||
|
// 3.1 数字ID匹配分析
|
||||||
|
console.log('\n3.1 数字ID匹配分析:');
|
||||||
|
const numericMatches = [];
|
||||||
|
const numericMismatches = [];
|
||||||
|
|
||||||
|
aiInterpretations.forEach(ai => {
|
||||||
|
const isNumeric = !isNaN(parseInt(ai.analysis_id)) && isFinite(ai.analysis_id);
|
||||||
|
if (isNumeric) {
|
||||||
|
const historyRecord = historyRecords.find(h => h.id.toString() === ai.analysis_id.toString());
|
||||||
|
if (historyRecord) {
|
||||||
|
numericMatches.push({ ai, history: historyRecord });
|
||||||
|
} else {
|
||||||
|
numericMismatches.push(ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`数字ID匹配成功: ${numericMatches.length} 条`);
|
||||||
|
numericMatches.forEach((match, index) => {
|
||||||
|
console.log(` ${index + 1}. AI记录ID: ${match.ai.id}, analysis_id: ${match.ai.analysis_id} → 历史记录: ${match.history.name} (${match.history.reading_type})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n数字ID匹配失败: ${numericMismatches.length} 条`);
|
||||||
|
numericMismatches.forEach((mismatch, index) => {
|
||||||
|
console.log(` ${index + 1}. AI记录ID: ${mismatch.id}, analysis_id: ${mismatch.analysis_id} (找不到对应的历史记录)`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3.2 字符串ID分析
|
||||||
|
console.log('\n3.2 字符串ID分析:');
|
||||||
|
const stringIds = aiInterpretations.filter(ai => {
|
||||||
|
const isNumeric = !isNaN(parseInt(ai.analysis_id)) && isFinite(ai.analysis_id);
|
||||||
|
return !isNumeric;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`字符串ID记录: ${stringIds.length} 条`);
|
||||||
|
stringIds.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. AI记录ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3.3 没有AI解读的历史记录
|
||||||
|
console.log('\n3.3 没有AI解读的历史记录:');
|
||||||
|
const recordsWithoutAI = [];
|
||||||
|
|
||||||
|
historyRecords.forEach(history => {
|
||||||
|
const hasAI = aiInterpretations.some(ai => {
|
||||||
|
const isNumeric = !isNaN(parseInt(ai.analysis_id)) && isFinite(ai.analysis_id);
|
||||||
|
if (isNumeric) {
|
||||||
|
return ai.analysis_id.toString() === history.id.toString();
|
||||||
|
}
|
||||||
|
return false; // 字符串ID暂时不匹配
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasAI) {
|
||||||
|
recordsWithoutAI.push(history);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`没有AI解读的历史记录: ${recordsWithoutAI.length} 条`);
|
||||||
|
recordsWithoutAI.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. 历史记录ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.created_at}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 问题总结
|
||||||
|
console.log('\n=== 问题总结 ===');
|
||||||
|
console.log(`1. 总AI解读记录: ${aiInterpretations.length}`);
|
||||||
|
console.log(`2. 总历史记录: ${historyRecords.length}`);
|
||||||
|
console.log(`3. 数字ID匹配成功: ${numericMatches.length}`);
|
||||||
|
console.log(`4. 数字ID匹配失败: ${numericMismatches.length}`);
|
||||||
|
console.log(`5. 字符串ID记录: ${stringIds.length}`);
|
||||||
|
console.log(`6. 没有AI解读的历史记录: ${recordsWithoutAI.length}`);
|
||||||
|
|
||||||
|
// 5. 可能的问题原因
|
||||||
|
console.log('\n=== 可能的问题原因 ===');
|
||||||
|
if (numericMismatches.length > 0) {
|
||||||
|
console.log('1. 数字ID不匹配问题:');
|
||||||
|
console.log(' - AI解读记录的analysis_id指向不存在的历史记录');
|
||||||
|
console.log(' - 可能是历史记录被删除,但AI解读记录仍然存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringIds.length > 0) {
|
||||||
|
console.log('2. 字符串ID问题:');
|
||||||
|
console.log(' - 这些AI解读使用了生成的字符串ID(如"yijing-1234567890")');
|
||||||
|
console.log(' - 前端查询时无法找到对应的历史记录');
|
||||||
|
console.log(' - 需要改进匹配逻辑或数据关联方式');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recordsWithoutAI.length > 0) {
|
||||||
|
console.log('3. 历史记录缺少AI解读:');
|
||||||
|
console.log(' - 这些记录可能是在AI解读功能实现之前创建的');
|
||||||
|
console.log(' - 或者AI解读生成失败但历史记录保存成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n=== 建议的解决方案 ===');
|
||||||
|
console.log('1. 对于数字ID不匹配: 清理无效的AI解读记录');
|
||||||
|
console.log('2. 对于字符串ID: 改进前端匹配逻辑,支持字符串ID查询');
|
||||||
|
console.log('3. 对于缺少AI解读的历史记录: 可以提供重新生成AI解读的功能');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调试过程中发生错误:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
debugAIInterpretations();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { debugAIInterpretations };
|
||||||
194
debug-duplicate-records.cjs
Normal file
194
debug-duplicate-records.cjs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
const { getDB } = require('./server/database/index.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试重复记录问题的脚本
|
||||||
|
* 检查是否存在AI解读后创建新历史记录的情况
|
||||||
|
*/
|
||||||
|
function debugDuplicateRecords() {
|
||||||
|
try {
|
||||||
|
// 初始化数据库连接
|
||||||
|
const { dbManager } = require('./server/database/index.cjs');
|
||||||
|
dbManager.init();
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
console.log('=== 调试重复记录问题 ===\n');
|
||||||
|
|
||||||
|
// 1. 检查最近的历史记录
|
||||||
|
console.log('1. 最近的历史记录 (按创建时间排序):');
|
||||||
|
const recentRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
reading_type,
|
||||||
|
created_at,
|
||||||
|
datetime(created_at, 'localtime') as local_time
|
||||||
|
FROM numerology_readings
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 20
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
recentRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.local_time}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 检查AI解读记录
|
||||||
|
console.log('\n2. AI解读记录:');
|
||||||
|
const aiRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
analysis_id,
|
||||||
|
analysis_type,
|
||||||
|
created_at,
|
||||||
|
datetime(created_at, 'localtime') as local_time
|
||||||
|
FROM ai_interpretations
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
aiRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}, created: ${record.local_time}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 分析可能的重复模式
|
||||||
|
console.log('\n3. 分析重复模式:');
|
||||||
|
|
||||||
|
// 按名称和类型分组,查找可能的重复记录
|
||||||
|
const duplicateGroups = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
reading_type,
|
||||||
|
COUNT(*) as count,
|
||||||
|
GROUP_CONCAT(id) as ids,
|
||||||
|
GROUP_CONCAT(datetime(created_at, 'localtime')) as times
|
||||||
|
FROM numerology_readings
|
||||||
|
GROUP BY name, reading_type
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY count DESC, MAX(created_at) DESC
|
||||||
|
LIMIT 10
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
if (duplicateGroups.length > 0) {
|
||||||
|
console.log(' 发现可能的重复记录组:');
|
||||||
|
duplicateGroups.forEach((group, index) => {
|
||||||
|
console.log(` ${index + 1}. ${group.name} (${group.reading_type}): ${group.count}条记录`);
|
||||||
|
console.log(` IDs: ${group.ids}`);
|
||||||
|
console.log(` 创建时间: ${group.times}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(' 未发现明显的重复记录组');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 检查时间相近的记录
|
||||||
|
console.log('\n4. 检查时间相近的记录 (5分钟内):');
|
||||||
|
const closeTimeRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
r1.id as id1,
|
||||||
|
r1.name as name1,
|
||||||
|
r1.reading_type as type1,
|
||||||
|
r1.created_at as time1,
|
||||||
|
r2.id as id2,
|
||||||
|
r2.name as name2,
|
||||||
|
r2.reading_type as type2,
|
||||||
|
r2.created_at as time2,
|
||||||
|
ABS(strftime('%s', r1.created_at) - strftime('%s', r2.created_at)) as time_diff_seconds
|
||||||
|
FROM numerology_readings r1
|
||||||
|
JOIN numerology_readings r2 ON r1.id < r2.id
|
||||||
|
WHERE r1.name = r2.name
|
||||||
|
AND r1.reading_type = r2.reading_type
|
||||||
|
AND ABS(strftime('%s', r1.created_at) - strftime('%s', r2.created_at)) <= 300
|
||||||
|
ORDER BY time_diff_seconds ASC
|
||||||
|
LIMIT 10
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
if (closeTimeRecords.length > 0) {
|
||||||
|
console.log(' 发现时间相近的相似记录:');
|
||||||
|
closeTimeRecords.forEach((pair, index) => {
|
||||||
|
console.log(` ${index + 1}. ${pair.name1} (${pair.type1}):`);
|
||||||
|
console.log(` 记录1: ID ${pair.id1}, 时间: ${pair.time1}`);
|
||||||
|
console.log(` 记录2: ID ${pair.id2}, 时间: ${pair.time2}`);
|
||||||
|
console.log(` 时间差: ${pair.time_diff_seconds}秒`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(' 未发现时间相近的相似记录');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 检查AI解读与历史记录的关联情况
|
||||||
|
console.log('\n5. AI解读与历史记录关联分析:');
|
||||||
|
|
||||||
|
// 数字ID关联
|
||||||
|
const numericMatches = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
ai.id as ai_id,
|
||||||
|
ai.analysis_id,
|
||||||
|
ai.analysis_type,
|
||||||
|
nr.id as record_id,
|
||||||
|
nr.name,
|
||||||
|
nr.reading_type,
|
||||||
|
datetime(ai.created_at, 'localtime') as ai_time,
|
||||||
|
datetime(nr.created_at, 'localtime') as record_time
|
||||||
|
FROM ai_interpretations ai
|
||||||
|
JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
||||||
|
ORDER BY ai.created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log(` 数字ID成功关联: ${numericMatches.length}条`);
|
||||||
|
numericMatches.forEach((match, index) => {
|
||||||
|
console.log(` ${index + 1}. AI记录${match.ai_id} → 历史记录${match.record_id} (${match.name}, ${match.reading_type})`);
|
||||||
|
console.log(` AI创建: ${match.ai_time}, 记录创建: ${match.record_time}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 字符串ID(无法关联)
|
||||||
|
const stringIdRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
ai.id as ai_id,
|
||||||
|
ai.analysis_id,
|
||||||
|
ai.analysis_type,
|
||||||
|
datetime(ai.created_at, 'localtime') as ai_time
|
||||||
|
FROM ai_interpretations ai
|
||||||
|
LEFT JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
||||||
|
WHERE nr.id IS NULL
|
||||||
|
ORDER BY ai.created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log(`\n 字符串ID无法关联: ${stringIdRecords.length}条`);
|
||||||
|
stringIdRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. AI记录${record.ai_id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}`);
|
||||||
|
console.log(` 创建时间: ${record.ai_time}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. 总结分析
|
||||||
|
console.log('\n=== 问题分析总结 ===');
|
||||||
|
|
||||||
|
const totalRecords = recentRecords.length;
|
||||||
|
const totalAI = aiRecords.length;
|
||||||
|
const duplicateCount = duplicateGroups.reduce((sum, group) => sum + (group.count - 1), 0);
|
||||||
|
|
||||||
|
console.log(`历史记录总数: ${totalRecords}`);
|
||||||
|
console.log(`AI解读记录总数: ${totalAI}`);
|
||||||
|
console.log(`可能的重复记录: ${duplicateCount}条`);
|
||||||
|
console.log(`成功关联的AI解读: ${numericMatches.length}条`);
|
||||||
|
console.log(`无法关联的AI解读: ${stringIdRecords.length}条`);
|
||||||
|
|
||||||
|
if (stringIdRecords.length > 0) {
|
||||||
|
console.log('\n⚠️ 发现问题:');
|
||||||
|
console.log(' - 存在无法关联到历史记录的AI解读');
|
||||||
|
console.log(' - 这些AI解读使用了字符串ID,不对应任何历史记录');
|
||||||
|
console.log(' - 可能的原因:AI解读时没有正确的recordId');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateGroups.length > 0) {
|
||||||
|
console.log('\n⚠️ 发现重复记录:');
|
||||||
|
console.log(' - 存在相同名称和类型的多条历史记录');
|
||||||
|
console.log(' - 可能的原因:用户多次提交相同的分析请求');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调试过程中发生错误:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
debugDuplicateRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { debugDuplicateRecords };
|
||||||
33
debug-records.cjs
Normal file
33
debug-records.cjs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const { getDB } = require('./server/database/index.cjs');
|
||||||
|
const { dbManager } = require('./server/database/index.cjs');
|
||||||
|
|
||||||
|
try {
|
||||||
|
dbManager.init();
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
console.log('=== 最近的分析记录 ===');
|
||||||
|
const recent = db.prepare('SELECT id, name, reading_type, created_at FROM numerology_readings ORDER BY created_at DESC LIMIT 5').all();
|
||||||
|
console.log(`总共 ${recent.length} 条分析记录`);
|
||||||
|
recent.forEach((r, i) => {
|
||||||
|
console.log(`${i+1}. ID: ${r.id}, 名称: ${r.name}, 类型: ${r.reading_type}, 创建时间: ${r.created_at}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n=== AI解读记录 ===');
|
||||||
|
const ai = db.prepare('SELECT id, reading_id, created_at FROM ai_interpretations ORDER BY created_at DESC LIMIT 5').all();
|
||||||
|
console.log(`总共 ${ai.length} 条AI解读记录`);
|
||||||
|
ai.forEach((r, i) => {
|
||||||
|
console.log(`${i+1}. AI_ID: ${r.id}, reading_id: ${r.reading_id}, 创建时间: ${r.created_at}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n=== 检查最新记录的AI解读状态 ===');
|
||||||
|
if (recent.length > 0) {
|
||||||
|
const latestRecord = recent[0];
|
||||||
|
const hasAI = db.prepare('SELECT COUNT(*) as count FROM ai_interpretations WHERE reading_id = ?').get(latestRecord.id);
|
||||||
|
console.log(`最新记录 ID: ${latestRecord.id} (${latestRecord.reading_type}) 是否有AI解读: ${hasAI.count > 0 ? '是' : '否'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调试失败:', error);
|
||||||
|
} finally {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<script type="module" crossorigin src="/assets/index-DuhglRqS.js"></script>
|
<script type="module" crossorigin src="/assets/index-CipkF_Jj.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-e9aiQwGl.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-e9aiQwGl.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
136
fix-string-id-records.cjs
Normal file
136
fix-string-id-records.cjs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
const { getDB } = require('./server/database/index.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复字符串ID的AI解读记录,尝试关联到对应的历史记录
|
||||||
|
*/
|
||||||
|
function fixStringIdRecords() {
|
||||||
|
try {
|
||||||
|
// 初始化数据库连接
|
||||||
|
const { dbManager } = require('./server/database/index.cjs');
|
||||||
|
dbManager.init();
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
console.log('=== 开始修复字符串ID的AI解读记录 ===\n');
|
||||||
|
|
||||||
|
// 1. 获取所有字符串ID的AI解读记录
|
||||||
|
const stringIdRecords = db.prepare(`
|
||||||
|
SELECT * FROM ai_interpretations
|
||||||
|
WHERE analysis_id NOT GLOB '[0-9]*'
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log(`找到 ${stringIdRecords.length} 条字符串ID记录需要修复`);
|
||||||
|
|
||||||
|
let fixedCount = 0;
|
||||||
|
let skippedCount = 0;
|
||||||
|
|
||||||
|
stringIdRecords.forEach((record, index) => {
|
||||||
|
console.log(`\n处理记录 ${index + 1}/${stringIdRecords.length}:`);
|
||||||
|
console.log(` AI记录ID: ${record.id}`);
|
||||||
|
console.log(` analysis_id: ${record.analysis_id}`);
|
||||||
|
console.log(` analysis_type: ${record.analysis_type}`);
|
||||||
|
console.log(` created_at: ${record.created_at}`);
|
||||||
|
|
||||||
|
// 尝试根据时间戳和类型找到最匹配的历史记录
|
||||||
|
const possibleHistoryRecords = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
reading_type,
|
||||||
|
created_at,
|
||||||
|
ABS(strftime('%s', created_at) - strftime('%s', ?)) as time_diff
|
||||||
|
FROM numerology_readings
|
||||||
|
WHERE reading_type = ? AND user_id = ?
|
||||||
|
ORDER BY time_diff ASC
|
||||||
|
LIMIT 5
|
||||||
|
`).all(record.created_at, record.analysis_type, record.user_id);
|
||||||
|
|
||||||
|
if (possibleHistoryRecords.length > 0) {
|
||||||
|
const bestMatch = possibleHistoryRecords[0];
|
||||||
|
const timeDiffHours = bestMatch.time_diff / 3600; // 转换为小时
|
||||||
|
|
||||||
|
console.log(` 找到 ${possibleHistoryRecords.length} 个可能的匹配记录:`);
|
||||||
|
possibleHistoryRecords.forEach((match, i) => {
|
||||||
|
const diffHours = match.time_diff / 3600;
|
||||||
|
console.log(` ${i + 1}. ID: ${match.id}, name: ${match.name}, 时间差: ${diffHours.toFixed(2)}小时`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果时间差在24小时内,认为是合理的匹配
|
||||||
|
if (timeDiffHours <= 24) {
|
||||||
|
console.log(` ✅ 选择最佳匹配: ID ${bestMatch.id} (时间差: ${timeDiffHours.toFixed(2)}小时)`);
|
||||||
|
|
||||||
|
// 检查是否已经有AI解读记录关联到这个历史记录
|
||||||
|
const existingAI = db.prepare(`
|
||||||
|
SELECT id FROM ai_interpretations
|
||||||
|
WHERE analysis_id = ? AND user_id = ?
|
||||||
|
`).get(bestMatch.id.toString(), record.user_id);
|
||||||
|
|
||||||
|
if (existingAI) {
|
||||||
|
console.log(` ⚠️ 历史记录 ${bestMatch.id} 已经有AI解读记录,跳过修复`);
|
||||||
|
skippedCount++;
|
||||||
|
} else {
|
||||||
|
// 更新AI解读记录的analysis_id
|
||||||
|
const updateResult = db.prepare(`
|
||||||
|
UPDATE ai_interpretations
|
||||||
|
SET analysis_id = ?
|
||||||
|
WHERE id = ?
|
||||||
|
`).run(bestMatch.id.toString(), record.id);
|
||||||
|
|
||||||
|
if (updateResult.changes > 0) {
|
||||||
|
console.log(` ✅ 成功更新: analysis_id ${record.analysis_id} → ${bestMatch.id}`);
|
||||||
|
fixedCount++;
|
||||||
|
} else {
|
||||||
|
console.log(` ❌ 更新失败`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(` ⚠️ 最佳匹配的时间差过大 (${timeDiffHours.toFixed(2)}小时),跳过修复`);
|
||||||
|
skippedCount++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(` ❌ 没有找到匹配的历史记录`);
|
||||||
|
skippedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n=== 修复完成 ===');
|
||||||
|
console.log(`总处理记录: ${stringIdRecords.length}`);
|
||||||
|
console.log(`成功修复: ${fixedCount}`);
|
||||||
|
console.log(`跳过修复: ${skippedCount}`);
|
||||||
|
|
||||||
|
// 验证修复结果
|
||||||
|
console.log('\n=== 验证修复结果 ===');
|
||||||
|
const remainingStringIds = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM ai_interpretations
|
||||||
|
WHERE analysis_id NOT GLOB '[0-9]*'
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
console.log(`剩余字符串ID记录: ${remainingStringIds.count}`);
|
||||||
|
|
||||||
|
const totalAIRecords = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM ai_interpretations
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
console.log(`总AI解读记录: ${totalAIRecords.count}`);
|
||||||
|
|
||||||
|
// 重新统计匹配情况
|
||||||
|
const matchedRecords = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM ai_interpretations ai
|
||||||
|
JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
console.log(`成功匹配的记录: ${matchedRecords.count}`);
|
||||||
|
console.log(`匹配率: ${((matchedRecords.count / totalAIRecords.count) * 100).toFixed(1)}%`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('修复过程中发生错误:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
fixStringIdRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { fixStringIdRecords };
|
||||||
@@ -81,8 +81,7 @@ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON user_sessions(expires_at);
|
|||||||
CREATE TABLE IF NOT EXISTS ai_interpretations (
|
CREATE TABLE IF NOT EXISTS ai_interpretations (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
analysis_id INTEGER NOT NULL, -- 关联到numerology_readings表的id
|
reading_id INTEGER NOT NULL, -- 直接关联到numerology_readings表的id
|
||||||
analysis_type TEXT NOT NULL CHECK (analysis_type IN ('bazi', 'ziwei', 'yijing')),
|
|
||||||
content TEXT NOT NULL, -- AI解读的完整内容
|
content TEXT NOT NULL, -- AI解读的完整内容
|
||||||
model TEXT, -- 使用的AI模型
|
model TEXT, -- 使用的AI模型
|
||||||
tokens_used INTEGER, -- 消耗的token数量
|
tokens_used INTEGER, -- 消耗的token数量
|
||||||
@@ -91,12 +90,13 @@ CREATE TABLE IF NOT EXISTS ai_interpretations (
|
|||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (analysis_id) REFERENCES numerology_readings(id) ON DELETE CASCADE
|
FOREIGN KEY (reading_id) REFERENCES numerology_readings(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(reading_id) -- 确保1对1关系
|
||||||
);
|
);
|
||||||
|
|
||||||
-- 创建AI解读相关索引
|
-- 创建AI解读相关索引
|
||||||
CREATE INDEX IF NOT EXISTS idx_ai_interpretations_user_id ON ai_interpretations(user_id);
|
CREATE INDEX IF NOT EXISTS idx_ai_interpretations_user_id ON ai_interpretations(user_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_ai_interpretations_analysis_id ON ai_interpretations(analysis_id);
|
CREATE INDEX IF NOT EXISTS idx_ai_interpretations_reading_id ON ai_interpretations(reading_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_ai_interpretations_created_at ON ai_interpretations(created_at DESC);
|
CREATE INDEX IF NOT EXISTS idx_ai_interpretations_created_at ON ai_interpretations(created_at DESC);
|
||||||
|
|
||||||
-- 触发器:自动更新updated_at字段
|
-- 触发器:自动更新updated_at字段
|
||||||
|
|||||||
@@ -6,32 +6,32 @@ const router = express.Router();
|
|||||||
// 保存AI解读结果
|
// 保存AI解读结果
|
||||||
router.post('/save', authenticate, async (req, res) => {
|
router.post('/save', authenticate, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { analysis_id, analysis_type, content, model, tokens_used, success, error_message } = req.body;
|
const { reading_id, content, model, tokens_used, success, error_message } = req.body;
|
||||||
const user_id = req.user.id;
|
const user_id = req.user.id;
|
||||||
|
|
||||||
// 验证必需参数
|
// 验证必需参数
|
||||||
if (!analysis_id || !analysis_type || (!content && success !== false)) {
|
if (!reading_id || (!content && success !== false)) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: '缺少必需参数:analysis_id, analysis_type, content'
|
error: '缺少必需参数:reading_id, content'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证analysis_id是否属于当前用户
|
// 验证reading_id是否属于当前用户
|
||||||
const db = getDB();
|
const db = getDB();
|
||||||
const analysisExists = db.prepare(
|
const readingExists = db.prepare(
|
||||||
'SELECT id FROM numerology_readings WHERE id = ? AND user_id = ?'
|
'SELECT id FROM numerology_readings WHERE id = ? AND user_id = ?'
|
||||||
).get(analysis_id, user_id);
|
).get(reading_id, user_id);
|
||||||
|
|
||||||
if (!analysisExists) {
|
if (!readingExists) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
error: '分析记录不存在或无权限访问'
|
error: '分析记录不存在或无权限访问'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已存在AI解读记录
|
// 检查是否已存在AI解读记录(1对1关系)
|
||||||
const existingInterpretation = db.prepare(
|
const existingInterpretation = db.prepare(
|
||||||
'SELECT id FROM ai_interpretations WHERE analysis_id = ? AND user_id = ?'
|
'SELECT id FROM ai_interpretations WHERE reading_id = ? AND user_id = ?'
|
||||||
).get(analysis_id, user_id);
|
).get(reading_id, user_id);
|
||||||
|
|
||||||
if (existingInterpretation) {
|
if (existingInterpretation) {
|
||||||
// 更新现有记录
|
// 更新现有记录
|
||||||
@@ -50,10 +50,10 @@ router.post('/save', authenticate, async (req, res) => {
|
|||||||
} else {
|
} else {
|
||||||
// 创建新记录
|
// 创建新记录
|
||||||
const insertStmt = db.prepare(`
|
const insertStmt = db.prepare(`
|
||||||
INSERT INTO ai_interpretations (user_id, analysis_id, analysis_type, content, model, tokens_used, success, error_message)
|
INSERT INTO ai_interpretations (user_id, reading_id, content, model, tokens_used, success, error_message)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
`);
|
`);
|
||||||
const result = insertStmt.run(user_id, analysis_id, analysis_type, content, model, tokens_used, success ? 1 : 0, error_message);
|
const result = insertStmt.run(user_id, reading_id, content, model, tokens_used, success ? 1 : 0, error_message);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -71,21 +71,22 @@ router.post('/save', authenticate, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 获取AI解读结果
|
// 获取AI解读结果
|
||||||
router.get('/get/:analysis_id', authenticate, async (req, res) => {
|
router.get('/get/:reading_id', authenticate, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { analysis_id } = req.params;
|
const { reading_id } = req.params;
|
||||||
const user_id = req.user.id;
|
const user_id = req.user.id;
|
||||||
const db = getDB();
|
const db = getDB();
|
||||||
|
|
||||||
|
// 获取AI解读记录及关联的分析记录信息
|
||||||
const interpretation = db.prepare(`
|
const interpretation = db.prepare(`
|
||||||
SELECT ai.*, nr.name, nr.reading_type, nr.created_at as analysis_created_at
|
SELECT ai.*, nr.name, nr.reading_type, nr.created_at as analysis_created_at
|
||||||
FROM ai_interpretations ai
|
FROM ai_interpretations ai
|
||||||
JOIN numerology_readings nr ON ai.analysis_id = nr.id
|
JOIN numerology_readings nr ON ai.reading_id = nr.id
|
||||||
WHERE ai.analysis_id = ? AND ai.user_id = ?
|
WHERE ai.reading_id = ? AND ai.user_id = ?
|
||||||
ORDER BY ai.created_at DESC
|
ORDER BY ai.created_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get(analysis_id, user_id);
|
`).get(reading_id, user_id);
|
||||||
|
|
||||||
if (!interpretation) {
|
if (!interpretation) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
error: 'AI解读结果不存在'
|
error: 'AI解读结果不存在'
|
||||||
@@ -96,8 +97,7 @@ router.get('/get/:analysis_id', authenticate, async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
id: interpretation.id,
|
id: interpretation.id,
|
||||||
analysis_id: interpretation.analysis_id,
|
reading_id: interpretation.reading_id,
|
||||||
analysis_type: interpretation.analysis_type,
|
|
||||||
content: interpretation.content,
|
content: interpretation.content,
|
||||||
model: interpretation.model,
|
model: interpretation.model,
|
||||||
tokens_used: interpretation.tokens_used,
|
tokens_used: interpretation.tokens_used,
|
||||||
@@ -106,6 +106,7 @@ router.get('/get/:analysis_id', authenticate, async (req, res) => {
|
|||||||
created_at: interpretation.created_at,
|
created_at: interpretation.created_at,
|
||||||
updated_at: interpretation.updated_at,
|
updated_at: interpretation.updated_at,
|
||||||
analysis_name: interpretation.name,
|
analysis_name: interpretation.name,
|
||||||
|
analysis_type: interpretation.reading_type,
|
||||||
analysis_created_at: interpretation.analysis_created_at
|
analysis_created_at: interpretation.analysis_created_at
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -122,22 +123,22 @@ router.get('/get/:analysis_id', authenticate, async (req, res) => {
|
|||||||
router.get('/list', authenticate, async (req, res) => {
|
router.get('/list', authenticate, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const user_id = req.user.id;
|
const user_id = req.user.id;
|
||||||
const { page = 1, limit = 20, analysis_type } = req.query;
|
const { page = 1, limit = 20, reading_type } = req.query;
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
const db = getDB();
|
const db = getDB();
|
||||||
|
|
||||||
let whereClause = 'WHERE ai.user_id = ?';
|
let whereClause = 'WHERE ai.user_id = ?';
|
||||||
let params = [user_id];
|
let params = [user_id];
|
||||||
|
|
||||||
if (analysis_type) {
|
if (reading_type) {
|
||||||
whereClause += ' AND ai.analysis_type = ?';
|
whereClause += ' AND nr.reading_type = ?';
|
||||||
params.push(analysis_type);
|
params.push(reading_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
const interpretations = db.prepare(`
|
const interpretations = db.prepare(`
|
||||||
SELECT ai.*, nr.name, nr.birth_date, nr.reading_type, nr.created_at as analysis_created_at
|
SELECT ai.*, nr.name, nr.birth_date, nr.reading_type, nr.created_at as analysis_created_at
|
||||||
FROM ai_interpretations ai
|
FROM ai_interpretations ai
|
||||||
JOIN numerology_readings nr ON ai.analysis_id = nr.id
|
JOIN numerology_readings nr ON ai.reading_id = nr.id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY ai.created_at DESC
|
ORDER BY ai.created_at DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
@@ -147,7 +148,7 @@ router.get('/list', authenticate, async (req, res) => {
|
|||||||
const totalResult = db.prepare(`
|
const totalResult = db.prepare(`
|
||||||
SELECT COUNT(*) as count
|
SELECT COUNT(*) as count
|
||||||
FROM ai_interpretations ai
|
FROM ai_interpretations ai
|
||||||
JOIN numerology_readings nr ON ai.analysis_id = nr.id
|
JOIN numerology_readings nr ON ai.reading_id = nr.id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
`).get(...params);
|
`).get(...params);
|
||||||
const total = totalResult.count;
|
const total = totalResult.count;
|
||||||
@@ -156,8 +157,8 @@ router.get('/list', authenticate, async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
data: interpretations.map(item => ({
|
data: interpretations.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
analysis_id: item.analysis_id,
|
reading_id: item.reading_id,
|
||||||
analysis_type: item.analysis_type,
|
analysis_type: item.reading_type,
|
||||||
content: item.content,
|
content: item.content,
|
||||||
model: item.model,
|
model: item.model,
|
||||||
tokens_used: item.tokens_used,
|
tokens_used: item.tokens_used,
|
||||||
@@ -186,16 +187,16 @@ router.get('/list', authenticate, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 删除AI解读结果
|
// 删除AI解读结果
|
||||||
router.delete('/delete/:analysis_id', authenticate, async (req, res) => {
|
router.delete('/delete/:reading_id', authenticate, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { analysis_id } = req.params;
|
const { reading_id } = req.params;
|
||||||
const user_id = req.user.id;
|
const user_id = req.user.id;
|
||||||
const db = getDB();
|
const db = getDB();
|
||||||
|
|
||||||
const deleteStmt = db.prepare(
|
const deleteStmt = db.prepare(
|
||||||
'DELETE FROM ai_interpretations WHERE analysis_id = ? AND user_id = ?'
|
'DELETE FROM ai_interpretations WHERE reading_id = ? AND user_id = ?'
|
||||||
);
|
);
|
||||||
const result = deleteStmt.run(analysis_id, user_id);
|
const result = deleteStmt.run(reading_id, user_id);
|
||||||
|
|
||||||
if (result.changes === 0) {
|
if (result.changes === 0) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
|
|||||||
@@ -26,28 +26,30 @@ router.get('/', authenticate, asyncHandler(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取总数
|
// 获取总数
|
||||||
const countQuery = `SELECT COUNT(*) as total FROM numerology_readings ${whereClause}`;
|
const countQuery = `SELECT COUNT(*) as total FROM numerology_readings nr ${whereClause.replace('WHERE', 'WHERE nr.')}`;
|
||||||
const { total } = db.prepare(countQuery).get(...params);
|
const { total } = db.prepare(countQuery).get(...params);
|
||||||
|
|
||||||
// 获取分页数据
|
// 获取分页数据,包含AI解读状态
|
||||||
const dataQuery = `
|
const dataQuery = `
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
nr.id,
|
||||||
reading_type,
|
nr.reading_type,
|
||||||
name,
|
nr.name,
|
||||||
birth_date,
|
nr.birth_date,
|
||||||
birth_time,
|
nr.birth_time,
|
||||||
birth_place,
|
nr.birth_place,
|
||||||
gender,
|
nr.gender,
|
||||||
input_data,
|
nr.input_data,
|
||||||
results,
|
nr.results,
|
||||||
analysis,
|
nr.analysis,
|
||||||
status,
|
nr.status,
|
||||||
created_at,
|
nr.created_at,
|
||||||
updated_at
|
nr.updated_at,
|
||||||
FROM numerology_readings
|
CASE WHEN ai.id IS NOT NULL THEN 1 ELSE 0 END as has_ai_interpretation
|
||||||
${whereClause}
|
FROM numerology_readings nr
|
||||||
ORDER BY created_at DESC
|
LEFT JOIN ai_interpretations ai ON (ai.reading_id = nr.id AND ai.user_id = nr.user_id)
|
||||||
|
${whereClause.replace('WHERE', 'WHERE nr.')}
|
||||||
|
ORDER BY nr.created_at DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
103
server/scripts/migrateAiInterpretations.cjs
Normal file
103
server/scripts/migrateAiInterpretations.cjs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
const { getDB } = require('../database/index.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 迁移ai_interpretations表,将analysis_id字段从INTEGER改为TEXT
|
||||||
|
* 这样可以支持字符串类型的analysis_id
|
||||||
|
*/
|
||||||
|
function migrateAiInterpretationsTable() {
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('开始迁移ai_interpretations表...');
|
||||||
|
|
||||||
|
// 检查表是否存在
|
||||||
|
const tableExists = db.prepare(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='ai_interpretations'"
|
||||||
|
).get();
|
||||||
|
|
||||||
|
if (!tableExists) {
|
||||||
|
console.log('ai_interpretations表不存在,跳过迁移');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查analysis_id字段的类型
|
||||||
|
const columnInfo = db.prepare("PRAGMA table_info(ai_interpretations)").all();
|
||||||
|
const analysisIdColumn = columnInfo.find(col => col.name === 'analysis_id');
|
||||||
|
|
||||||
|
if (analysisIdColumn && analysisIdColumn.type === 'TEXT') {
|
||||||
|
console.log('analysis_id字段已经是TEXT类型,无需迁移');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始事务
|
||||||
|
db.exec('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
// 1. 创建新的临时表
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE ai_interpretations_new (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
analysis_id TEXT NOT NULL,
|
||||||
|
analysis_type TEXT NOT NULL CHECK (analysis_type IN ('bazi', 'ziwei', 'yijing')),
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
model TEXT,
|
||||||
|
tokens_used INTEGER,
|
||||||
|
success BOOLEAN DEFAULT 1,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 2. 复制数据到新表(将INTEGER转换为TEXT)
|
||||||
|
db.exec(`
|
||||||
|
INSERT INTO ai_interpretations_new
|
||||||
|
(id, user_id, analysis_id, analysis_type, content, model, tokens_used, success, error_message, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
id, user_id, CAST(analysis_id AS TEXT), analysis_type, content, model, tokens_used, success, error_message, created_at, updated_at
|
||||||
|
FROM ai_interpretations
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 3. 删除旧表
|
||||||
|
db.exec('DROP TABLE ai_interpretations');
|
||||||
|
|
||||||
|
// 4. 重命名新表
|
||||||
|
db.exec('ALTER TABLE ai_interpretations_new RENAME TO ai_interpretations');
|
||||||
|
|
||||||
|
// 5. 重新创建索引
|
||||||
|
db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_user_id ON ai_interpretations(user_id)');
|
||||||
|
db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_analysis_id ON ai_interpretations(analysis_id)');
|
||||||
|
db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_created_at ON ai_interpretations(created_at DESC)');
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
db.exec('COMMIT');
|
||||||
|
|
||||||
|
console.log('ai_interpretations表迁移完成');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// 回滚事务
|
||||||
|
try {
|
||||||
|
db.exec('ROLLBACK');
|
||||||
|
} catch (rollbackError) {
|
||||||
|
console.error('回滚失败:', rollbackError);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('迁移失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
try {
|
||||||
|
migrateAiInterpretationsTable();
|
||||||
|
console.log('迁移成功完成');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('迁移失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { migrateAiInterpretationsTable };
|
||||||
171
server/scripts/refactorAiInterpretations.cjs
Normal file
171
server/scripts/refactorAiInterpretations.cjs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
const { getDB } = require('../database/index.cjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重构AI解读记录表,建立与分析报告记录的正确1对1关系
|
||||||
|
* 消除字符串analysis_id,使用正确的外键关联
|
||||||
|
*/
|
||||||
|
function refactorAiInterpretations() {
|
||||||
|
const db = getDB();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('=== 开始重构AI解读记录表 ===\n');
|
||||||
|
|
||||||
|
// 开始事务
|
||||||
|
db.exec('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
// 1. 分析现有数据
|
||||||
|
console.log('1. 分析现有数据...');
|
||||||
|
const allAI = db.prepare(`
|
||||||
|
SELECT id, analysis_id, analysis_type, content, model, tokens_used,
|
||||||
|
success, error_message, created_at, updated_at, user_id
|
||||||
|
FROM ai_interpretations
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log(`总AI解读记录: ${allAI.length}`);
|
||||||
|
|
||||||
|
const stringIds = allAI.filter(r => typeof r.analysis_id === 'string');
|
||||||
|
const numericIds = allAI.filter(r => typeof r.analysis_id === 'number');
|
||||||
|
|
||||||
|
console.log(`字符串ID记录: ${stringIds.length}`);
|
||||||
|
console.log(`数字ID记录: ${numericIds.length}`);
|
||||||
|
|
||||||
|
if (stringIds.length === 0) {
|
||||||
|
console.log('没有需要重构的字符串ID记录');
|
||||||
|
db.exec('ROLLBACK');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 创建新的临时表
|
||||||
|
console.log('\n2. 创建新的AI解读表结构...');
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE ai_interpretations_new (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
reading_id INTEGER NOT NULL, -- 直接关联到numerology_readings表的id
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
model TEXT,
|
||||||
|
tokens_used INTEGER,
|
||||||
|
success BOOLEAN DEFAULT 1,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (reading_id) REFERENCES numerology_readings(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(reading_id) -- 确保1对1关系
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 3. 迁移数字ID记录(如果有的话)
|
||||||
|
if (numericIds.length > 0) {
|
||||||
|
console.log(`\n3. 迁移 ${numericIds.length} 条数字ID记录...`);
|
||||||
|
const insertStmt = db.prepare(`
|
||||||
|
INSERT INTO ai_interpretations_new
|
||||||
|
(user_id, reading_id, content, model, tokens_used, success, error_message, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`);
|
||||||
|
|
||||||
|
for (const record of numericIds) {
|
||||||
|
// 验证关联的记录是否存在
|
||||||
|
const readingExists = db.prepare(
|
||||||
|
'SELECT id FROM numerology_readings WHERE id = ? AND user_id = ?'
|
||||||
|
).get(record.analysis_id, record.user_id);
|
||||||
|
|
||||||
|
if (readingExists) {
|
||||||
|
insertStmt.run(
|
||||||
|
record.user_id,
|
||||||
|
record.analysis_id,
|
||||||
|
record.content,
|
||||||
|
record.model,
|
||||||
|
record.tokens_used,
|
||||||
|
record.success,
|
||||||
|
record.error_message,
|
||||||
|
record.created_at,
|
||||||
|
record.updated_at
|
||||||
|
);
|
||||||
|
console.log(` 迁移记录: AI_ID=${record.id} -> reading_id=${record.analysis_id}`);
|
||||||
|
} else {
|
||||||
|
console.log(` 跳过无效记录: AI_ID=${record.id}, analysis_id=${record.analysis_id} (关联记录不存在)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 处理字符串ID记录 - 删除无效记录
|
||||||
|
console.log(`\n4. 处理 ${stringIds.length} 条字符串ID记录...`);
|
||||||
|
console.log('这些记录使用了临时生成的字符串ID,无法建立正确的关联关系,将被删除:');
|
||||||
|
|
||||||
|
stringIds.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. AI_ID=${record.id}, analysis_id="${record.analysis_id}", type=${record.analysis_type}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. 删除旧表,重命名新表
|
||||||
|
console.log('\n5. 更新表结构...');
|
||||||
|
db.exec('DROP TABLE ai_interpretations');
|
||||||
|
db.exec('ALTER TABLE ai_interpretations_new RENAME TO ai_interpretations');
|
||||||
|
|
||||||
|
// 6. 重新创建索引
|
||||||
|
console.log('6. 重新创建索引...');
|
||||||
|
db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_user_id ON ai_interpretations(user_id)');
|
||||||
|
db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_reading_id ON ai_interpretations(reading_id)');
|
||||||
|
db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_created_at ON ai_interpretations(created_at DESC)');
|
||||||
|
|
||||||
|
// 7. 重新创建触发器
|
||||||
|
console.log('7. 重新创建触发器...');
|
||||||
|
db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS update_ai_interpretations_timestamp
|
||||||
|
AFTER UPDATE ON ai_interpretations
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE ai_interpretations SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
db.exec('COMMIT');
|
||||||
|
|
||||||
|
// 8. 验证结果
|
||||||
|
console.log('\n=== 重构完成 ===');
|
||||||
|
const newCount = db.prepare('SELECT COUNT(*) as count FROM ai_interpretations').get();
|
||||||
|
console.log(`新表记录数: ${newCount.count}`);
|
||||||
|
|
||||||
|
const sampleRecords = db.prepare(`
|
||||||
|
SELECT ai.id, ai.reading_id, ai.user_id, nr.name, nr.reading_type
|
||||||
|
FROM ai_interpretations ai
|
||||||
|
JOIN numerology_readings nr ON ai.reading_id = nr.id
|
||||||
|
LIMIT 5
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
console.log('\n示例关联记录:');
|
||||||
|
sampleRecords.forEach((record, index) => {
|
||||||
|
console.log(` ${index + 1}. AI_ID=${record.id} -> reading_id=${record.reading_id} (${record.name}, ${record.reading_type})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n✅ AI解读记录表重构成功!');
|
||||||
|
console.log('现在AI解读记录与分析报告记录建立了正确的1对1关系');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// 回滚事务
|
||||||
|
try {
|
||||||
|
db.exec('ROLLBACK');
|
||||||
|
} catch (rollbackError) {
|
||||||
|
console.error('回滚失败:', rollbackError);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (require.main === module) {
|
||||||
|
try {
|
||||||
|
const { dbManager } = require('../database/index.cjs');
|
||||||
|
dbManager.init();
|
||||||
|
refactorAiInterpretations();
|
||||||
|
console.log('\n🎉 重构完成!');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ 重构失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { refactorAiInterpretations };
|
||||||
@@ -451,13 +451,14 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
|||||||
userId={userId}
|
userId={userId}
|
||||||
divinationMethod={divinationMethod}
|
divinationMethod={divinationMethod}
|
||||||
analysisData={preAnalysisData}
|
analysisData={preAnalysisData}
|
||||||
|
recordId={recordId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于紫微斗数,如果有 birthDate 参数,直接返回 CompleteZiweiAnalysis 组件(不添加额外容器)
|
// 对于紫微斗数,如果有 birthDate 参数,直接返回 CompleteZiweiAnalysis 组件(不添加额外容器)
|
||||||
if (analysisType === 'ziwei' && birthDate) {
|
if (analysisType === 'ziwei' && birthDate) {
|
||||||
return <CompleteZiweiAnalysis birthDate={birthDate} analysisData={preAnalysisData} />;
|
return <CompleteZiweiAnalysis birthDate={birthDate} analysisData={preAnalysisData} recordId={recordId} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有分析结果数据
|
// 如果没有分析结果数据
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate,
|
|||||||
<AIInterpretationButton
|
<AIInterpretationButton
|
||||||
analysisData={analysisData}
|
analysisData={analysisData}
|
||||||
analysisType="bazi"
|
analysisType="bazi"
|
||||||
analysisId={recordId?.toString()}
|
recordId={recordId}
|
||||||
onConfigClick={() => setShowAIConfig(true)}
|
onConfigClick={() => setShowAIConfig(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ const CompleteYijingAnalysis: React.FC<CompleteYijingAnalysisProps> = ({
|
|||||||
<AIInterpretationButton
|
<AIInterpretationButton
|
||||||
analysisData={analysisData}
|
analysisData={analysisData}
|
||||||
analysisType="yijing"
|
analysisType="yijing"
|
||||||
analysisId={recordId?.toString()}
|
recordId={recordId}
|
||||||
onConfigClick={() => setShowAIConfig(true)}
|
onConfigClick={() => setShowAIConfig(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -590,7 +590,7 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
|
|||||||
<AIInterpretationButton
|
<AIInterpretationButton
|
||||||
analysisData={analysisData}
|
analysisData={analysisData}
|
||||||
analysisType="ziwei"
|
analysisType="ziwei"
|
||||||
analysisId={recordId?.toString()}
|
recordId={recordId}
|
||||||
onConfigClick={() => setShowAIConfig(true)}
|
onConfigClick={() => setShowAIConfig(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface AIInterpretationButtonProps {
|
|||||||
analysisData?: any; // 分析数据对象(可选)
|
analysisData?: any; // 分析数据对象(可选)
|
||||||
analysisMarkdown?: string; // 直接传递的MD内容(可选)
|
analysisMarkdown?: string; // 直接传递的MD内容(可选)
|
||||||
analysisType: 'bazi' | 'ziwei' | 'yijing';
|
analysisType: 'bazi' | 'ziwei' | 'yijing';
|
||||||
analysisId?: string; // 用于缓存解读结果
|
recordId?: number; // 分析记录ID,用于AI解读
|
||||||
className?: string;
|
className?: string;
|
||||||
variant?: 'default' | 'outline' | 'ghost';
|
variant?: 'default' | 'outline' | 'ghost';
|
||||||
size?: 'sm' | 'md' | 'lg';
|
size?: 'sm' | 'md' | 'lg';
|
||||||
@@ -33,7 +33,7 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
|
|||||||
analysisData,
|
analysisData,
|
||||||
analysisMarkdown,
|
analysisMarkdown,
|
||||||
analysisType,
|
analysisType,
|
||||||
analysisId,
|
recordId,
|
||||||
className,
|
className,
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
@@ -55,56 +55,14 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
|
|||||||
setIsConfigValid(validateAIConfig(config));
|
setIsConfigValid(validateAIConfig(config));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 生成唯一的分析ID,包含分析数据的时间戳
|
// 如果没有recordId,则无法进行AI解读
|
||||||
const generateAnalysisId = () => {
|
const canPerformAI = !!recordId;
|
||||||
if (analysisId) {
|
|
||||||
return analysisId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试从分析数据中提取时间戳
|
|
||||||
let timestamp = '';
|
|
||||||
if (analysisData) {
|
|
||||||
// 检查多种可能的时间戳字段
|
|
||||||
const timeFields = [
|
|
||||||
analysisData.created_at,
|
|
||||||
analysisData.timestamp,
|
|
||||||
analysisData.analysis_time,
|
|
||||||
analysisData.basic_info?.created_at,
|
|
||||||
analysisData.basic_info?.timestamp,
|
|
||||||
analysisData.basic_info?.analysis_time
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const field of timeFields) {
|
|
||||||
if (field) {
|
|
||||||
timestamp = new Date(field).getTime().toString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有找到时间戳,使用数据的哈希值作为标识
|
|
||||||
if (!timestamp) {
|
|
||||||
const dataString = JSON.stringify(analysisData);
|
|
||||||
// 使用简单的哈希算法替代btoa,避免Unicode字符问题
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < dataString.length; i++) {
|
|
||||||
const char = dataString.charCodeAt(i);
|
|
||||||
hash = ((hash << 5) - hash) + char;
|
|
||||||
hash = hash & hash; // 转换为32位整数
|
|
||||||
}
|
|
||||||
timestamp = Math.abs(hash).toString(36).slice(0, 16); // 使用36进制表示
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${analysisType}-${timestamp || Date.now()}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const uniqueAnalysisId = generateAnalysisId();
|
|
||||||
|
|
||||||
// 加载已保存的解读结果
|
// 加载已保存的解读结果
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadSavedInterpretation = async () => {
|
const loadSavedInterpretation = async () => {
|
||||||
if (uniqueAnalysisId) {
|
if (recordId) {
|
||||||
const savedInterpretation = await getAIInterpretation(uniqueAnalysisId);
|
const savedInterpretation = await getAIInterpretation(recordId);
|
||||||
if (savedInterpretation) {
|
if (savedInterpretation) {
|
||||||
setInterpretation(savedInterpretation);
|
setInterpretation(savedInterpretation);
|
||||||
}
|
}
|
||||||
@@ -112,7 +70,7 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadSavedInterpretation();
|
loadSavedInterpretation();
|
||||||
}, [uniqueAnalysisId]);
|
}, [recordId]);
|
||||||
|
|
||||||
// 处理AI解读请求
|
// 处理AI解读请求
|
||||||
const handleAIInterpretation = async () => {
|
const handleAIInterpretation = async () => {
|
||||||
@@ -157,9 +115,9 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
|
|||||||
setStreamingContent(''); // 清空流式内容,使用最终结果
|
setStreamingContent(''); // 清空流式内容,使用最终结果
|
||||||
|
|
||||||
// 保存解读结果
|
// 保存解读结果
|
||||||
if (uniqueAnalysisId) {
|
if (recordId) {
|
||||||
try {
|
try {
|
||||||
await saveAIInterpretation(uniqueAnalysisId, result, analysisType);
|
await saveAIInterpretation(recordId, result);
|
||||||
} catch (saveError) {
|
} catch (saveError) {
|
||||||
// 保存失败不影响用户体验,静默处理
|
// 保存失败不影响用户体验,静默处理
|
||||||
}
|
}
|
||||||
@@ -213,7 +171,7 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
|
|||||||
handleAIInterpretation();
|
handleAIInterpretation();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={isLoading || (!isConfigValid && !interpretation)}
|
disabled={isLoading || !canPerformAI || (!isConfigValid && !interpretation)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'min-h-[40px] min-w-[100px] px-3 sm:px-6 text-xs sm:text-sm flex-shrink-0 whitespace-nowrap',
|
'min-h-[40px] min-w-[100px] px-3 sm:px-6 text-xs sm:text-sm flex-shrink-0 whitespace-nowrap',
|
||||||
!isConfigValid && !interpretation && 'opacity-50 cursor-not-allowed'
|
!isConfigValid && !interpretation && 'opacity-50 cursor-not-allowed'
|
||||||
@@ -263,7 +221,17 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 配置提示 */}
|
{/* 配置提示 */}
|
||||||
{!isConfigValid && !interpretation && (
|
{!canPerformAI && (
|
||||||
|
<div className="flex items-center space-x-2 p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||||
|
<AlertCircle className="h-4 w-4 text-red-600 flex-shrink-0" />
|
||||||
|
<div className="text-sm text-red-800">
|
||||||
|
<p className="font-medium">无法使用AI解读</p>
|
||||||
|
<p className="text-xs mt-1">此分析记录没有有效的ID,无法保存AI解读结果</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{canPerformAI && !isConfigValid && !interpretation && (
|
||||||
<div className="flex items-center space-x-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
<div className="flex items-center space-x-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||||
<AlertCircle className="h-4 w-4 text-yellow-600 flex-shrink-0" />
|
<AlertCircle className="h-4 w-4 text-yellow-600 flex-shrink-0" />
|
||||||
<div className="text-sm text-yellow-800">
|
<div className="text-sm text-yellow-800">
|
||||||
|
|||||||
@@ -353,18 +353,17 @@ class LocalApiClient {
|
|||||||
|
|
||||||
// AI解读相关方法
|
// AI解读相关方法
|
||||||
aiInterpretation = {
|
aiInterpretation = {
|
||||||
// 获取AI解读状态
|
// 获取AI解读结果
|
||||||
get: async (analysisId: number): Promise<ApiResponse<any>> => {
|
get: async (readingId: number): Promise<ApiResponse<any>> => {
|
||||||
return this.request<any>(`/ai-interpretation/get/${analysisId}`);
|
return this.request(`/ai-interpretation/get/${readingId}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 保存AI解读结果
|
// 保存AI解读结果
|
||||||
save: async (analysisId: number, content: string, analysisType: string, model?: string, tokensUsed?: number): Promise<ApiResponse<any>> => {
|
save: async (readingId: number, content: string, model?: string, tokensUsed?: number): Promise<ApiResponse<any>> => {
|
||||||
return this.request<any>('/ai-interpretation/save', {
|
return this.request<any>('/ai-interpretation/save', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
analysis_id: analysisId,
|
reading_id: readingId,
|
||||||
analysis_type: analysisType,
|
|
||||||
content,
|
content,
|
||||||
model,
|
model,
|
||||||
tokens_used: tokensUsed,
|
tokens_used: tokensUsed,
|
||||||
@@ -373,16 +372,16 @@ class LocalApiClient {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取用户的所有AI解读记录
|
// 获取AI解读列表
|
||||||
list: async (params?: { page?: number; limit?: number; analysis_type?: string }): Promise<ApiResponse<any[]>> => {
|
list: async (params?: { page?: number; limit?: number; reading_type?: string }): Promise<ApiResponse<any[]>> => {
|
||||||
const queryParams = new URLSearchParams();
|
const queryParams = new URLSearchParams();
|
||||||
if (params?.page) queryParams.append('page', params.page.toString());
|
if (params?.page) queryParams.append('page', params.page.toString());
|
||||||
if (params?.limit) queryParams.append('limit', params.limit.toString());
|
if (params?.limit) queryParams.append('limit', params.limit.toString());
|
||||||
if (params?.analysis_type) queryParams.append('analysis_type', params.analysis_type);
|
if (params?.reading_type) queryParams.append('reading_type', params.reading_type);
|
||||||
|
|
||||||
const endpoint = `/ai-interpretation/list${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
|
const endpoint = `/ai-interpretation/list${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
|
||||||
return this.request<any[]>(endpoint);
|
return this.request<any[]>(endpoint);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 兼容Supabase的functions.invoke方法
|
// 兼容Supabase的functions.invoke方法
|
||||||
|
|||||||
@@ -180,24 +180,6 @@ const AnalysisPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// 分析完成后,保存历史记录
|
|
||||||
try {
|
|
||||||
const inputData = analysisType === 'yijing' ?
|
|
||||||
{ question: formData.question, divination_method: 'time' } :
|
|
||||||
{
|
|
||||||
name: formData.name,
|
|
||||||
birth_date: formData.birth_date,
|
|
||||||
birth_time: formData.birth_time,
|
|
||||||
birth_place: formData.birth_place,
|
|
||||||
gender: formData.gender
|
|
||||||
};
|
|
||||||
|
|
||||||
await localApi.analysis.saveHistory(analysisType, analysisData, inputData);
|
|
||||||
// 历史记录保存成功
|
|
||||||
} catch (historyError: any) {
|
|
||||||
// 静默处理历史记录保存错误
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success('分析完成!');
|
toast.success('分析完成!');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error('分析失败:' + (error.message || '未知错误'));
|
toast.error('分析失败:' + (error.message || '未知错误'));
|
||||||
|
|||||||
@@ -90,15 +90,11 @@ const HistoryPage: React.FC = () => {
|
|||||||
|
|
||||||
setReadings(processedData);
|
setReadings(processedData);
|
||||||
|
|
||||||
// 检查每个记录的AI解读状态
|
// 从后端返回的数据中提取AI解读状态
|
||||||
const aiStatus: {[key: number]: boolean} = {};
|
const aiStatus: {[key: number]: boolean} = {};
|
||||||
for (const reading of processedData) {
|
for (const reading of processedData) {
|
||||||
try {
|
// 使用后端返回的has_ai_interpretation字段
|
||||||
const aiResponse = await localApi.aiInterpretation.get(reading.id);
|
aiStatus[reading.id] = !!(reading as any).has_ai_interpretation;
|
||||||
aiStatus[reading.id] = !aiResponse.error && !!aiResponse.data;
|
|
||||||
} catch {
|
|
||||||
aiStatus[reading.id] = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setAiInterpretations(aiStatus);
|
setAiInterpretations(aiStatus);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -124,8 +120,8 @@ const HistoryPage: React.FC = () => {
|
|||||||
throw new Error(response.error.message);
|
throw new Error(response.error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
setReadings(prev => prev.filter(r => r.id !== readingId));
|
setReadings(prev => prev.filter(r => r.id !== parseInt(readingId)));
|
||||||
if (selectedReading?.id === readingId) {
|
if (selectedReading?.id === parseInt(readingId)) {
|
||||||
setSelectedReading(null);
|
setSelectedReading(null);
|
||||||
setViewingResult(false);
|
setViewingResult(false);
|
||||||
}
|
}
|
||||||
@@ -227,7 +223,7 @@ const HistoryPage: React.FC = () => {
|
|||||||
divinationMethod={selectedReading.reading_type === 'yijing' ?
|
divinationMethod={selectedReading.reading_type === 'yijing' ?
|
||||||
getInputDataValue(selectedReading.input_data, 'divination_method', 'time') : undefined}
|
getInputDataValue(selectedReading.input_data, 'divination_method', 'time') : undefined}
|
||||||
preAnalysisData={selectedReading.analysis}
|
preAnalysisData={selectedReading.analysis}
|
||||||
recordId={parseInt(selectedReading.id)}
|
recordId={selectedReading.id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -345,7 +341,7 @@ const HistoryPage: React.FC = () => {
|
|||||||
<ChineseButton
|
<ChineseButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="md"
|
size="md"
|
||||||
onClick={() => handleDeleteReading(reading.id)}
|
onClick={() => handleDeleteReading(reading.id.toString())}
|
||||||
className="min-h-[40px] text-red-600 hover:text-red-700 hover:bg-red-50 px-2 sm:px-3 flex-shrink-0"
|
className="min-h-[40px] text-red-600 hover:text-red-700 hover:bg-red-50 px-2 sm:px-3 flex-shrink-0"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3 sm:h-4 sm:w-4" />
|
<Trash2 className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
|
|||||||
@@ -875,7 +875,7 @@ export const requestAIInterpretation = async (request: AIInterpretationRequest):
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 保存AI解读结果到数据库
|
// 保存AI解读结果到数据库
|
||||||
export const saveAIInterpretation = async (analysisId: string, result: AIInterpretationResult, analysisType: string): Promise<void> => {
|
export const saveAIInterpretation = async (readingId: number, result: AIInterpretationResult): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -893,8 +893,7 @@ export const saveAIInterpretation = async (analysisId: string, result: AIInterpr
|
|||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
analysis_id: parseInt(analysisId),
|
reading_id: readingId,
|
||||||
analysis_type: analysisType,
|
|
||||||
content: result.content,
|
content: result.content,
|
||||||
model: result.model,
|
model: result.model,
|
||||||
tokens_used: result.tokensUsed,
|
tokens_used: result.tokensUsed,
|
||||||
@@ -908,12 +907,12 @@ export const saveAIInterpretation = async (analysisId: string, result: AIInterpr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 同时保存到localStorage作为备份
|
// 同时保存到localStorage作为备份
|
||||||
const key = `ai-interpretation-${analysisId}`;
|
const key = `ai-interpretation-${readingId}`;
|
||||||
localStorage.setItem(key, JSON.stringify(result));
|
localStorage.setItem(key, JSON.stringify(result));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 如果数据库保存失败,至少保存到localStorage
|
// 如果数据库保存失败,至少保存到localStorage
|
||||||
try {
|
try {
|
||||||
const key = `ai-interpretation-${analysisId}`;
|
const key = `ai-interpretation-${readingId}`;
|
||||||
localStorage.setItem(key, JSON.stringify(result));
|
localStorage.setItem(key, JSON.stringify(result));
|
||||||
} catch (localError) {
|
} catch (localError) {
|
||||||
// 静默处理存储错误
|
// 静默处理存储错误
|
||||||
@@ -922,7 +921,7 @@ export const saveAIInterpretation = async (analysisId: string, result: AIInterpr
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 从数据库或本地存储获取AI解读结果
|
// 从数据库或本地存储获取AI解读结果
|
||||||
export const getAIInterpretation = async (analysisId: string): Promise<AIInterpretationResult | null> => {
|
export const getAIInterpretation = async (readingId: number): Promise<AIInterpretationResult | null> => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
if (token) {
|
if (token) {
|
||||||
@@ -931,7 +930,7 @@ export const getAIInterpretation = async (analysisId: string): Promise<AIInterpr
|
|||||||
(import.meta.env.DEV ? 'http://localhost:3001/api' :
|
(import.meta.env.DEV ? 'http://localhost:3001/api' :
|
||||||
(window.location.hostname.includes('koyeb.app') ? `${window.location.origin}/api` : `${window.location.origin}/api`));
|
(window.location.hostname.includes('koyeb.app') ? `${window.location.origin}/api` : `${window.location.origin}/api`));
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/ai-interpretation/get/${analysisId}`, {
|
const response = await fetch(`${API_BASE_URL}/ai-interpretation/get/${readingId}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@@ -953,7 +952,7 @@ export const getAIInterpretation = async (analysisId: string): Promise<AIInterpr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果数据库获取失败,尝试从localStorage获取
|
// 如果数据库获取失败,尝试从localStorage获取
|
||||||
const key = `ai-interpretation-${analysisId}`;
|
const key = `ai-interpretation-${readingId}`;
|
||||||
const saved = localStorage.getItem(key);
|
const saved = localStorage.getItem(key);
|
||||||
if (saved) {
|
if (saved) {
|
||||||
return JSON.parse(saved);
|
return JSON.parse(saved);
|
||||||
@@ -961,7 +960,7 @@ export const getAIInterpretation = async (analysisId: string): Promise<AIInterpr
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 如果数据库获取失败,尝试从localStorage获取
|
// 如果数据库获取失败,尝试从localStorage获取
|
||||||
try {
|
try {
|
||||||
const key = `ai-interpretation-${analysisId}`;
|
const key = `ai-interpretation-${readingId}`;
|
||||||
const saved = localStorage.getItem(key);
|
const saved = localStorage.getItem(key);
|
||||||
if (saved) {
|
if (saved) {
|
||||||
return JSON.parse(saved);
|
return JSON.parse(saved);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface UserProfile {
|
export interface UserProfile {
|
||||||
id: string;
|
id: number;
|
||||||
user_id: string;
|
user_id: number;
|
||||||
username?: string;
|
username?: string;
|
||||||
full_name: string;
|
full_name: string;
|
||||||
birth_date: string;
|
birth_date: string;
|
||||||
@@ -13,8 +13,8 @@ export interface UserProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AnalysisRecord {
|
export interface AnalysisRecord {
|
||||||
id: string;
|
id: number;
|
||||||
user_id: string;
|
user_id: number;
|
||||||
analysis_type: 'bazi' | 'ziwei' | 'yijing';
|
analysis_type: 'bazi' | 'ziwei' | 'yijing';
|
||||||
name: string;
|
name: string;
|
||||||
birth_date: string;
|
birth_date: string;
|
||||||
@@ -29,8 +29,8 @@ export interface AnalysisRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NumerologyReading {
|
export interface NumerologyReading {
|
||||||
id: string;
|
id: number;
|
||||||
user_id: string;
|
user_id: number;
|
||||||
profile_id?: string;
|
profile_id?: string;
|
||||||
reading_type: 'bazi' | 'ziwei' | 'yijing' | 'comprehensive';
|
reading_type: 'bazi' | 'ziwei' | 'yijing' | 'comprehensive';
|
||||||
name: string;
|
name: string;
|
||||||
@@ -56,7 +56,7 @@ export interface NumerologyReading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AnalysisRequest {
|
export interface AnalysisRequest {
|
||||||
user_id: string;
|
user_id: number;
|
||||||
birth_data: {
|
birth_data: {
|
||||||
name: string;
|
name: string;
|
||||||
birth_date: string;
|
birth_date: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user