feat: 更新 AI 教练聊天界面和个人信息页面
- 在 AI 教练聊天界面中添加训练记录分析功能,允许用户基于近期训练记录获取分析建议 - 更新 Redux 状态管理,集成每日步数和卡路里目标 - 在个人信息页面中优化用户头像显示,支持从库中选择头像 - 修改首页布局,添加可拖动的教练徽章,提升用户交互体验 - 更新样式以适应新功能的展示和交互
This commit is contained in:
@@ -5,6 +5,7 @@ import React, { useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Image,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
@@ -20,6 +21,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useAppSelector } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import type { CheckinRecord } from '@/store/checkinSlice';
|
||||
|
||||
type Role = 'user' | 'assistant';
|
||||
|
||||
@@ -29,6 +31,8 @@ type ChatMessage = {
|
||||
content: string;
|
||||
};
|
||||
|
||||
const COACH_AVATAR = 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/avatar/imageCoach01.jpeg';
|
||||
|
||||
export default function AICoachChatScreen() {
|
||||
const router = useRouter();
|
||||
const params = useLocalSearchParams<{ name?: string }>();
|
||||
@@ -47,11 +51,13 @@ export default function AICoachChatScreen() {
|
||||
const listRef = useRef<FlatList<ChatMessage>>(null);
|
||||
|
||||
const planDraft = useAppSelector((s) => s.trainingPlan?.draft);
|
||||
const checkin = useAppSelector((s) => (s as any).checkin);
|
||||
|
||||
const chips = useMemo(() => [
|
||||
{ key: 'posture', label: '体态评估', action: () => router.push('/ai-posture-assessment') },
|
||||
{ key: 'plan', label: 'AI制定训练计划', action: () => handleQuickPlan() },
|
||||
], [router, planDraft]);
|
||||
{ key: 'analyze', label: '分析运动记录', action: () => handleAnalyzeRecords() },
|
||||
], [router, planDraft, checkin]);
|
||||
|
||||
function scrollToEnd() {
|
||||
requestAnimationFrame(() => {
|
||||
@@ -109,6 +115,45 @@ export default function AICoachChatScreen() {
|
||||
send(prompt);
|
||||
}
|
||||
|
||||
function buildTrainingSummary(): string {
|
||||
const entries = Object.values(checkin?.byDate || {}) as CheckinRecord[];
|
||||
if (!entries.length) return '';
|
||||
const recent = entries.sort((a: any, b: any) => String(b.date).localeCompare(String(a.date))).slice(0, 14);
|
||||
let totalSessions = 0;
|
||||
let totalExercises = 0;
|
||||
let totalCompleted = 0;
|
||||
const categoryCount: Record<string, number> = {};
|
||||
const exerciseCount: Record<string, number> = {};
|
||||
for (const rec of recent) {
|
||||
if (!rec?.items?.length) continue;
|
||||
totalSessions += 1;
|
||||
for (const it of rec.items) {
|
||||
totalExercises += 1;
|
||||
if (it.completed) totalCompleted += 1;
|
||||
categoryCount[it.category] = (categoryCount[it.category] || 0) + 1;
|
||||
exerciseCount[it.name] = (exerciseCount[it.name] || 0) + 1;
|
||||
}
|
||||
}
|
||||
const topCategories = Object.entries(categoryCount).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([k, v]) => `${k}×${v}`);
|
||||
const topExercises = Object.entries(exerciseCount).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k}×${v}`);
|
||||
return [
|
||||
`统计周期:最近${recent.length}天(按有记录日计 ${totalSessions} 天)`,
|
||||
`记录条目:${totalExercises},完成标记:${totalCompleted}`,
|
||||
topCategories.length ? `高频类别:${topCategories.join(',')}` : '',
|
||||
topExercises.length ? `高频动作:${topExercises.join(',')}` : '',
|
||||
].filter(Boolean).join('\n');
|
||||
}
|
||||
|
||||
function handleAnalyzeRecords() {
|
||||
const summary = buildTrainingSummary();
|
||||
if (!summary) {
|
||||
send('我还没有可分析的打卡记录,请先在“每日打卡”添加并完成一些训练记录,然后帮我分析近期训练表现与改进建议。');
|
||||
return;
|
||||
}
|
||||
const prompt = `请基于以下我的近期训练记录进行分析,输出:1)整体训练负荷与节奏;2)动作与肌群的均衡性(指出偏多/偏少);3)容易忽视的恢复与热身建议;4)后续一周的优化建议(频次/时长/动作方向)。\n\n${summary}`;
|
||||
send(prompt);
|
||||
}
|
||||
|
||||
function renderItem({ item }: { item: ChatMessage }) {
|
||||
const isUser = item.role === 'user';
|
||||
return (
|
||||
@@ -118,9 +163,7 @@ export default function AICoachChatScreen() {
|
||||
style={[styles.row, { justifyContent: isUser ? 'flex-end' : 'flex-start' }]}
|
||||
>
|
||||
{!isUser && (
|
||||
<View style={[styles.avatar, { backgroundColor: theme.primary }]}>
|
||||
<Text style={styles.avatarText}>AI</Text>
|
||||
</View>
|
||||
<Image source={{ uri: COACH_AVATAR }} style={styles.avatar} />
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
@@ -282,7 +325,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
inputRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-end',
|
||||
alignItems: 'center',
|
||||
padding: 8,
|
||||
borderWidth: 1,
|
||||
borderRadius: 16,
|
||||
@@ -292,8 +335,10 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
maxHeight: 120,
|
||||
minHeight: 40,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 6,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
sendBtn: {
|
||||
width: 40,
|
||||
|
||||
Reference in New Issue
Block a user