Fix AI interpretation button issues and animation conflicts

- Fix AI interpretation recordId parameter passing for all analysis types
- Resolve double border issue in AI interpretation results display
- Fix animation conflicts causing visual duplication
- Update component interfaces to support recordId parameter
- Add AI interpretation status indicators in history page
- Disable conflicting animations in AI result containers
This commit is contained in:
patdelphi
2025-08-23 12:40:31 +08:00
parent 865d4c7a15
commit a3bd20c31e
8 changed files with 374 additions and 35 deletions

View File

@@ -17,6 +17,7 @@ interface AnalysisResultDisplayProps {
userId?: string;
divinationMethod?: string;
preAnalysisData?: any; // 预先分析的数据,用于历史记录
recordId?: number; // 历史记录ID用于AI解读
}
const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
@@ -26,7 +27,8 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
question,
userId,
divinationMethod,
preAnalysisData
preAnalysisData,
recordId
}) => {
// 安全地获取数据的辅助函数
const safeGet = (obj: any, path: string, defaultValue: any = '暂无数据') => {
@@ -62,7 +64,7 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
const renderBaziAnalysis = () => {
// 如果有 birthDate使用新的 CompleteBaziAnalysis 组件
if (birthDate) {
return <CompleteBaziAnalysis birthDate={birthDate} analysisData={preAnalysisData} />;
return <CompleteBaziAnalysis birthDate={birthDate} analysisData={preAnalysisData} recordId={recordId} />;
}
// 如果有分析结果但没有 birthDate尝试从结果中提取出生信息
if (analysisResult && analysisResult.data) {
@@ -74,7 +76,7 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
name: basicInfo.personal_data.name || '',
gender: basicInfo.personal_data.gender === '男性' ? 'male' : 'female'
};
return <CompleteBaziAnalysis birthDate={extractedBirthDate} analysisData={preAnalysisData} />;
return <CompleteBaziAnalysis birthDate={extractedBirthDate} analysisData={preAnalysisData} recordId={recordId} />;
}
}
// 如果没有足够的数据,返回错误提示
@@ -89,7 +91,7 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
const renderZiweiAnalysis = () => {
// 如果有 birthDate使用新的 CompleteZiweiAnalysis 组件
if (birthDate) {
return <CompleteZiweiAnalysis birthDate={birthDate} analysisData={preAnalysisData} />;
return <CompleteZiweiAnalysis birthDate={birthDate} analysisData={preAnalysisData} recordId={recordId} />;
}
// 如果有分析结果但没有 birthDate尝试从结果中提取出生信息
if (analysisResult && analysisResult.data) {
@@ -101,7 +103,7 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
name: basicInfo.personal_data.name || '',
gender: basicInfo.personal_data.gender === '男性' ? 'male' : 'female'
};
return <CompleteZiweiAnalysis birthDate={extractedBirthDate} analysisData={preAnalysisData} />;
return <CompleteZiweiAnalysis birthDate={extractedBirthDate} analysisData={preAnalysisData} recordId={recordId} />;
}
}
@@ -261,6 +263,8 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
question={question}
userId={userId}
divinationMethod={divinationMethod}
analysisData={preAnalysisData}
recordId={recordId}
/>
);
}
@@ -274,6 +278,8 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
question={basicInfo.divination_data.question || '综合运势如何?'}
userId={userId || 'user123'}
divinationMethod={divinationMethod || 'time'}
analysisData={preAnalysisData}
recordId={recordId}
/>
);
}

View File

@@ -16,9 +16,10 @@ interface CompleteBaziAnalysisProps {
gender?: string;
};
analysisData?: any; // 可选的预先分析的数据
recordId?: number; // 历史记录ID用于AI解读
}
const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate, analysisData: propAnalysisData }) => {
const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate, analysisData: propAnalysisData, recordId }) => {
const [isLoading, setIsLoading] = useState(!propAnalysisData);
const [error, setError] = useState<string | null>(null);
const [analysisData, setAnalysisData] = useState<any>(propAnalysisData || null);
@@ -286,6 +287,7 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate,
<AIInterpretationButton
analysisData={analysisData}
analysisType="bazi"
analysisId={recordId?.toString()}
onConfigClick={() => setShowAIConfig(true)}
/>
</div>

View File

@@ -12,13 +12,15 @@ interface CompleteYijingAnalysisProps {
userId?: string;
divinationMethod?: string;
analysisData?: any; // 可选的预先分析的数据
recordId?: number; // 历史记录ID用于AI解读
}
const CompleteYijingAnalysis: React.FC<CompleteYijingAnalysisProps> = ({
question,
userId = 'user123',
divinationMethod = 'time',
analysisData: propAnalysisData
analysisData: propAnalysisData,
recordId
}) => {
const [isLoading, setIsLoading] = useState(!propAnalysisData);
const [error, setError] = useState<string | null>(null);
@@ -274,6 +276,7 @@ const CompleteYijingAnalysis: React.FC<CompleteYijingAnalysisProps> = ({
<AIInterpretationButton
analysisData={analysisData}
analysisType="yijing"
analysisId={recordId?.toString()}
onConfigClick={() => setShowAIConfig(true)}
/>
</div>

View File

@@ -19,9 +19,10 @@ interface CompleteZiweiAnalysisProps {
gender?: string;
};
analysisData?: any; // 可选的预先分析的数据
recordId?: number; // 历史记录ID用于AI解读
}
const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate, analysisData: propAnalysisData }) => {
const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate, analysisData: propAnalysisData, recordId }) => {
const [isLoading, setIsLoading] = useState(!propAnalysisData);
const [error, setError] = useState<string | null>(null);
const [analysisData, setAnalysisData] = useState<any>(propAnalysisData || null);
@@ -589,6 +590,7 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
<AIInterpretationButton
analysisData={analysisData}
analysisType="ziwei"
analysisId={recordId?.toString()}
onConfigClick={() => setShowAIConfig(true)}
/>
</div>

View File

@@ -277,19 +277,19 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
{/* AI解读结果显示 */}
{(interpretation || streamingContent) && showResult && (
<ChineseCard className="w-full border-2 border-purple-200 bg-gradient-to-br from-purple-50 to-blue-50">
<ChineseCardHeader>
<ChineseCardTitle className="flex items-center space-x-2 text-purple-800">
<div className="w-full border-2 border-purple-200 bg-gradient-to-br from-purple-50 to-blue-50 rounded-lg shadow-sm" style={{transform: 'none', animation: 'none', transition: 'none'}}>
<div className="p-4 border-b border-purple-200">
<div className="flex items-center space-x-2 text-purple-800">
{isLoading ? (
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<Sparkles className="h-5 w-5" />
)}
<span>AI智能解读 - {getAnalysisTypeName(analysisType)}</span>
<span className="text-lg font-semibold">AI智能解读 - {getAnalysisTypeName(analysisType)}</span>
{isLoading && streamingContent && (
<span className="ml-2 text-sm font-normal text-purple-600">...</span>
)}
</ChineseCardTitle>
</div>
{interpretation && (
<div className="flex items-center space-x-4 text-xs text-gray-500 mt-2">
<span>: {new Date(interpretation.timestamp).toLocaleString('zh-CN')}</span>
@@ -297,8 +297,8 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
{interpretation.tokensUsed && <span>Token: {interpretation.tokensUsed}</span>}
</div>
)}
</ChineseCardHeader>
<ChineseCardContent>
</div>
<div className="p-4">
{interpretation && !interpretation.success ? (
<div className="flex items-center space-x-2 p-4 bg-red-50 border border-red-200 rounded-lg">
<AlertCircle className="h-4 w-4 text-red-600 flex-shrink-0" />
@@ -371,11 +371,10 @@ const AIInterpretationButton: React.FC<AIInterpretationButtonProps> = ({
</ReactMarkdown>
{isLoading && streamingContent && (
<span className="inline-block w-2 h-5 bg-purple-600 animate-pulse ml-1"></span>
)} </div>
)}
</div>
)}
</ChineseCardContent>
</ChineseCard>
</div>
)}
</div>
);

View File

@@ -0,0 +1,278 @@
import React, { useState, useEffect } from 'react';
import { ChineseInput } from './ChineseInput';
import { ChineseSelect } from './ChineseSelect';
import { ChineseButton } from './ChineseButton';
import { Lightbulb, RefreshCw } from 'lucide-react';
interface YijingQuestionSelectorProps {
value: string;
onChange: (value: string) => void;
className?: string;
}
// 问题分类和预设问题数据
const questionCategories = {
career: {
name: '事业发展',
icon: '💼',
questions: [
'我的事业发展前景如何?',
'现在是否适合换工作?',
'我应该选择哪个职业方向?',
'创业的时机是否成熟?',
'如何提升我的职场竞争力?',
'我的工作能力能否得到认可?',
'是否应该接受这个工作机会?',
'我的事业何时能有突破?'
]
},
love: {
name: '感情婚姻',
icon: '💕',
questions: [
'我的感情运势如何?',
'现在的恋情能否修成正果?',
'我何时能遇到真爱?',
'这段感情是否值得继续?',
'如何改善我们的关系?',
'我应该主动表白吗?',
'婚姻生活会幸福吗?',
'如何处理感情中的矛盾?'
]
},
wealth: {
name: '财运投资',
icon: '💰',
questions: [
'我的财运发展如何?',
'这项投资是否明智?',
'如何改善我的财务状况?',
'现在适合创业吗?',
'我的理财方向是否正确?',
'何时能实现财务自由?',
'这个商业机会值得把握吗?',
'如何增加我的收入来源?'
]
},
health: {
name: '健康养生',
icon: '🏥',
questions: [
'我的健康状况如何?',
'如何改善我的身体状况?',
'这个治疗方案是否有效?',
'我需要注意哪些健康问题?',
'如何调理我的身心状态?',
'什么运动最适合我?',
'我的饮食习惯需要调整吗?',
'如何预防疾病的发生?'
]
},
study: {
name: '学业考试',
icon: '📚',
questions: [
'我的学习成绩能否提升?',
'这次考试能否顺利通过?',
'应该选择哪个专业方向?',
'如何提高学习效率?',
'是否应该继续深造?',
'我的学习方法是否正确?',
'何时是最佳的考试时机?',
'如何克服学习中的困难?'
]
},
family: {
name: '家庭生活',
icon: '🏠',
questions: [
'我的家庭关系如何?',
'如何处理家庭矛盾?',
'子女教育应该注意什么?',
'如何改善与父母的关系?',
'家庭财务规划是否合理?',
'搬家的时机是否合适?',
'如何营造和谐的家庭氛围?',
'家人的健康状况如何?'
]
},
general: {
name: '综合运势',
icon: '🔮',
questions: [
'我的整体运势如何?',
'近期需要注意什么?',
'如何把握人生机遇?',
'我的人生方向是否正确?',
'如何化解当前的困境?',
'什么时候运势会好转?',
'我应该如何规划未来?',
'如何提升我的整体运势?'
]
}
};
export const YijingQuestionSelector: React.FC<YijingQuestionSelectorProps> = ({
value,
onChange,
className
}) => {
const [selectedCategory, setSelectedCategory] = useState<string>('');
const [selectedQuestion, setSelectedQuestion] = useState<string>('');
const [showPresets, setShowPresets] = useState<boolean>(true);
// 分类选项
const categoryOptions = Object.entries(questionCategories).map(([key, category]) => ({
value: key,
label: `${category.icon} ${category.name}`
}));
// 当选择分类时,更新问题选项
const questionOptions = selectedCategory && questionCategories[selectedCategory as keyof typeof questionCategories]
? questionCategories[selectedCategory as keyof typeof questionCategories].questions.map((question, index) => ({
value: question,
label: question
}))
: [];
// 处理分类选择
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
setSelectedQuestion('');
// 选择分类后自动显示预设问题
if (category) {
setShowPresets(true);
}
};
// 处理预设问题选择
const handleQuestionSelect = (question: string) => {
setSelectedQuestion(question);
onChange(question);
};
// 随机选择问题
const handleRandomQuestion = () => {
const allQuestions = Object.values(questionCategories).flatMap(category => category.questions);
const randomQuestion = allQuestions[Math.floor(Math.random() * allQuestions.length)];
onChange(randomQuestion);
setSelectedQuestion(randomQuestion);
// 找到对应的分类
const categoryKey = Object.entries(questionCategories).find(([_, category]) =>
category.questions.includes(randomQuestion)
)?.[0];
if (categoryKey) {
setSelectedCategory(categoryKey);
}
};
// 切换预设问题显示
const togglePresets = () => {
setShowPresets(!showPresets);
};
return (
<div className={className}>
{/* 预设问题选择区域 */}
<div className="mb-6 space-y-4">
{/* 控制按钮 */}
<div className="flex items-center space-x-3">
<ChineseButton
variant="outline"
size="sm"
onClick={togglePresets}
className="flex items-center space-x-2"
>
<Lightbulb className="h-4 w-4" />
<span>{showPresets ? '隐藏预设问题' : '选择预设问题'}</span>
</ChineseButton>
<ChineseButton
variant="outline"
size="sm"
onClick={handleRandomQuestion}
className="flex items-center space-x-2"
>
<RefreshCw className="h-4 w-4" />
<span></span>
</ChineseButton>
</div>
{/* 预设问题选择界面 */}
{showPresets && (
<div className="bg-gradient-to-br from-amber-50 to-yellow-50 p-4 rounded-lg border border-amber-200">
<h4 className="font-semibold text-amber-800 mb-3 flex items-center">
<span className="mr-2">🎯</span>
</h4>
{/* 分类选择 */}
<div className="mb-4">
<ChineseSelect
label="问题类别"
value={selectedCategory}
onChange={(e) => handleCategoryChange(e.target.value)}
options={[
{ value: '', label: '请选择问题类别' },
...categoryOptions
]}
variant="default"
className="mb-3 [&_select]:!bg-blue-50 [&_select]:!border-blue-200 [&_select:hover]:!bg-blue-100 [&_select:focus]:!bg-white [&_select:focus]:!border-blue-500 [&_select:focus]:!ring-blue-500/20"
/>
</div>
{/* 预设问题选择 */}
{selectedCategory && questionOptions.length > 0 && (
<div className="mb-4">
<ChineseSelect
label="预设问题"
value={selectedQuestion}
onChange={(e) => handleQuestionSelect(e.target.value)}
options={[
{ value: '', label: '请选择预设问题' },
...questionOptions
]}
variant="filled"
/>
</div>
)}
{/* 快速选择按钮 */}
{selectedCategory && questionOptions.length > 0 && (
<div className="space-y-2">
<p className="text-sm text-amber-700 font-medium"></p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{questionOptions.slice(0, 6).map((option, index) => (
<button
key={index}
onClick={() => handleQuestionSelect(option.value)}
className="text-left p-2 text-sm bg-white hover:bg-amber-100 border border-amber-200 rounded-lg transition-colors duration-200 text-amber-800 hover:text-amber-900"
>
{option.label}
</button>
))}
</div>
</div>
)}
</div>
)}
</div>
{/* 主要问题输入框 */}
<ChineseInput
label="占卜问题"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="请输入您希望占卜的具体问题,或选择上方预设问题"
required
variant="filled"
helperText="💡 提示:问题越具体,占卜结果越准确。您可以使用预设问题或自行输入。"
/>
</div>
);
};
export default YijingQuestionSelector;

View File

@@ -5,6 +5,7 @@ import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseInput } from '../components/ui/ChineseInput';
import { ChineseSelect } from '../components/ui/ChineseSelect';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import YijingQuestionSelector from '../components/ui/YijingQuestionSelector';
import AnalysisResultDisplay from '../components/AnalysisResultDisplay';
import { toast } from 'sonner';
import { Sparkles, Star, Compass, Calendar, MapPin, User, Loader2 } from 'lucide-react';
@@ -24,7 +25,7 @@ const AnalysisPage: React.FC = () => {
birth_time: '',
gender: 'male' as 'male' | 'female',
birth_place: '',
question: '财运'
question: ''
});
const [loading, setLoading] = useState(false);
const [analysisResult, setAnalysisResult] = useState<any>(null);
@@ -56,7 +57,7 @@ const AnalysisPage: React.FC = () => {
birth_time: data.birth_time || '',
gender: data.gender || 'male',
birth_place: data.birth_location || '',
question: '财运'
question: ''
});
}
} catch (error) {
@@ -139,10 +140,38 @@ const AnalysisPage: React.FC = () => {
// 后端返回格式: { data: { analysis } }
const analysisData = data.analysis;
// 保存历史记录
try {
const saveResponse = await localApi.request('/analysis/save-history', {
method: 'POST',
body: JSON.stringify({
analysis_type: analysisType,
analysis_data: analysisData,
input_data: analysisType === 'yijing' ? { question: formData.question } : birthData
})
});
if (saveResponse.data?.record_id) {
// 将record_id添加到分析结果中用于AI解读
setAnalysisResult({
type: analysisType,
data: analysisData,
recordId: saveResponse.data.record_id
});
} else {
setAnalysisResult({
type: analysisType,
data: analysisData
});
}
} catch (saveError) {
console.error('保存历史记录失败:', saveError);
// 即使保存失败,也显示分析结果
setAnalysisResult({
type: analysisType,
data: analysisData
});
}
// 分析完成后,滚动到结果区域
setTimeout(() => {
@@ -278,14 +307,9 @@ const AnalysisPage: React.FC = () => {
{analysisType === 'yijing' ? (
// 易经占卜表单
<div className="mb-6">
<ChineseInput
label="占卜问题"
<YijingQuestionSelector
value={formData.question}
onChange={(e) => setFormData(prev => ({ ...prev, question: e.target.value }))}
placeholder="请输入您希望占卜的具体问题,如:我的事业发展如何?"
required
variant="filled"
helperText="💡 提示:问题越具体,占卜结果越准确。可以询问事业、感情、财运、健康等方面的问题。"
onChange={(value) => setFormData(prev => ({ ...prev, question: value }))}
/>
</div>
) : (
@@ -402,6 +426,7 @@ const AnalysisPage: React.FC = () => {
question={analysisType === 'yijing' ? formData.question : undefined}
userId={user?.id?.toString()}
divinationMethod="time"
recordId={analysisResult.recordId}
/>
</div>
)}

View File

@@ -21,6 +21,7 @@ const HistoryPage: React.FC = () => {
// 分页相关状态
const [currentPage, setCurrentPage] = useState(1);
const [aiInterpretations, setAiInterpretations] = useState<{[key: number]: boolean}>({});
const itemsPerPage = 10;
// 安全地从input_data中获取值的辅助函数
@@ -88,6 +89,20 @@ const HistoryPage: React.FC = () => {
});
setReadings(processedData);
// 检查每个记录的AI解读状态
const aiStatus: {[key: number]: boolean} = {};
for (const reading of processedData) {
try {
const aiResponse = await localApi.request(`/ai-interpretation/get/${reading.id}`, {
method: 'GET'
});
aiStatus[reading.id] = aiResponse.success && aiResponse.data;
} catch {
aiStatus[reading.id] = false;
}
}
setAiInterpretations(aiStatus);
} catch (error: any) {
toast.error('加载历史记录失败:' + (error.message || '未知错误'));
} finally {
@@ -214,6 +229,7 @@ const HistoryPage: React.FC = () => {
divinationMethod={selectedReading.reading_type === 'yijing' ?
getInputDataValue(selectedReading.input_data, 'divination_method', 'time') : undefined}
preAnalysisData={selectedReading.analysis}
recordId={selectedReading.id}
/>
</div>
@@ -279,9 +295,17 @@ const HistoryPage: React.FC = () => {
<Icon className="h-5 w-5" />
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<h3 className="font-semibold text-gray-900 font-chinese">
{reading.name || '未知姓名'} - {getAnalysisTypeName(reading.reading_type)}
</h3>
{aiInterpretations[reading.id] && (
<div className="flex items-center space-x-1 bg-purple-100 text-purple-700 px-2 py-1 rounded-full text-xs">
<Sparkles className="h-3 w-3" />
<span>AI解读</span>
</div>
)}
</div>
<div className="flex flex-col sm:flex-row sm:items-center sm:space-x-4 text-sm text-gray-600 mt-1 space-y-1 sm:space-y-0">
<div className="flex items-center space-x-1">
<Calendar className="h-3 w-3" />