import React, { useState, useEffect } from 'react'; import { AIEntity } from './components/AIEntity'; import { TopActions } from './components/TopActions'; import { BottomNav } from './components/BottomNav'; import { CustomizationButton } from './components/CustomizationButton'; import { CharacterCustomizationDialog, CharacterPreferences } from './components/CharacterCustomizationDialog'; import { ChatDialog } from './components/ChatDialog'; import { RecordView } from './components/RecordView'; import { CommunityView } from './components/CommunityView'; import { MineView } from './components/MineView'; import { MoodView } from './components/MoodView'; import { InspirationView } from './components/InspirationView'; import { TodoView } from './components/TodoView'; import { HomeInput } from './components/HomeInput'; import { Tab, MoodAction, RecordItem, RecordSource, CommunityPost, Profile, DeviceStatus, MoodItem, MoodType, InspirationItem, TodoItem } from './types'; import { apiService } from './services/api'; import { transformRecord, transformMood, transformInspiration, transformTodo } from './utils/dataTransform'; // Mock Data: Records - 丰富多样的记录数据 const generateMockRecords = (): RecordItem[] => { const now = Date.now(); const hour = 60 * 60 * 1000; const day = 24 * hour; return [ // 今天 - 早晨 { id: '1', content: "早上6点醒来,窗外的鸟鸣声特别清脆。新的一天,充满期待。", createdAt: now - hour * 2, sourceType: RecordSource.MOOD }, { id: '2', content: "晨跑时看到日出,天空从深蓝渐变到橙粉色,美得让人想哭。", createdAt: now - hour * 1.5, sourceType: RecordSource.VOICE }, // 今天 - 上午 { id: '3', content: "咖啡店里的音乐很好听,是一首法语歌。要记得回去搜一下。", createdAt: now - hour, sourceType: RecordSource.INSPIRATION }, { id: '4', content: "工作会议上提出的方案被采纳了,同事们的认可让我很开心。", createdAt: now - hour * 0.5, sourceType: RecordSource.MOOD }, // 昨天 - 下午 { id: '5', content: "午后的阳光透过百叶窗,在桌上投下一道道光影。时间仿佛静止了。", createdAt: now - day - hour * 6, sourceType: RecordSource.MANUAL }, { id: '6', content: "突然想到:如果每个人的记忆都能变成一本书,那会是什么样子?", createdAt: now - day - hour * 5, sourceType: RecordSource.INSPIRATION }, // 昨天 - 傍晚 { id: '7', content: "下班路上遇到一只流浪猫,蹲下来和它对视了很久。它的眼睛很清澈。", createdAt: now - day - hour * 2, sourceType: RecordSource.VOICE }, { id: '8', content: "晚餐做了番茄炒蛋,虽然简单,但吃得很满足。生活的幸福就是这样。", createdAt: now - day - hour, sourceType: RecordSource.MOOD }, // 前天 - 全天 { id: '9', content: "今天有点焦虑,deadline临近,但还有很多工作没完成。深呼吸,一步一步来。", createdAt: now - day * 2 - hour * 8, sourceType: RecordSource.MOOD }, { id: '10', content: "中午和老朋友视频通话,虽然各自忙碌,但友谊依然温暖。", createdAt: now - day * 2 - hour * 6, sourceType: RecordSource.VOICE }, { id: '11', content: "读到一句话:'不要因为走得太远,而忘记为什么出发。' 很有共鸣。", createdAt: now - day * 2 - hour * 3, sourceType: RecordSource.MANUAL }, { id: '12', content: "晚上加班到很晚,但看到项目进展顺利,疲惫中带着一丝成就感。", createdAt: now - day * 2 - hour, sourceType: RecordSource.MOOD }, // 三天前 { id: '13', content: "周末去了郊外,看到一大片向日葵田。金黄色的花海,治愈了整个人。", createdAt: now - day * 3 - hour * 10, sourceType: RecordSource.VOICE }, { id: '14', content: "在书店翻到一本旧书,扉页上有人写的字:'愿你永远保持好奇心'。", createdAt: now - day * 3 - hour * 7, sourceType: RecordSource.INSPIRATION }, { id: '15', content: "下午茶时间,点了一块抹茶蛋糕。甜食真的能让心情变好。", createdAt: now - day * 3 - hour * 4, sourceType: RecordSource.MOOD }, // 四天前 { id: '16', content: "今天状态不太好,做什么都提不起劲。也许只是需要好好休息一下。", createdAt: now - day * 4 - hour * 9, sourceType: RecordSource.MOOD }, { id: '17', content: "妈妈打电话来,聊了很久。她说最近在学广场舞,听起来很开心。", createdAt: now - day * 4 - hour * 5, sourceType: RecordSource.VOICE }, { id: '18', content: "晚上看了一部老电影,《天使爱美丽》。生活需要一些小确幸和浪漫。", createdAt: now - day * 4 - hour * 2, sourceType: RecordSource.MANUAL }, // 五天前 { id: '19', content: "早起去菜市场,看到各种新鲜的蔬菜水果,感受到生活的烟火气。", createdAt: now - day * 5 - hour * 11, sourceType: RecordSource.MOOD }, { id: '20', content: "灵感:设计一个'慢生活'主题的摄影集,记录日常中被忽略的美好瞬间。", createdAt: now - day * 5 - hour * 8, sourceType: RecordSource.INSPIRATION }, { id: '21', content: "下午在公园散步,看到一对老夫妻手牵手。希望自己老了也能这样。", createdAt: now - day * 5 - hour * 4, sourceType: RecordSource.VOICE }, // 六天前 { id: '22', content: "工作上遇到了一些挫折,有点沮丧。但转念一想,这也是成长的机会。", createdAt: now - day * 6 - hour * 10, sourceType: RecordSource.MOOD }, { id: '23', content: "中午吃饭时,餐厅放的背景音乐是《月亮代表我的心》,突然很想家。", createdAt: now - day * 6 - hour * 6, sourceType: RecordSource.MANUAL }, { id: '24', content: "晚上和室友一起做饭,虽然厨艺不精,但笑声不断。这就是生活的乐趣。", createdAt: now - day * 6 - hour * 2, sourceType: RecordSource.VOICE }, // 一周前 { id: '25', content: "今天是周一,新的一周开始。给自己定个小目标:每天进步一点点。", createdAt: now - day * 7 - hour * 9, sourceType: RecordSource.MOOD }, { id: '26', content: "路过花店,买了一束雏菊。白色的花瓣,简单却很美。", createdAt: now - day * 7 - hour * 5, sourceType: RecordSource.MANUAL }, { id: '27', content: "晚上写日记时想到:记录生活不是为了回忆,而是为了更好地活在当下。", createdAt: now - day * 7 - hour * 1, sourceType: RecordSource.INSPIRATION }, // 更早之前 { id: '28', content: "雨后的街道,空气中弥漫着泥土的清香。这是大自然的馈赠。", createdAt: now - day * 10 - hour * 7, sourceType: RecordSource.VOICE }, { id: '29', content: "完成了一个困扰很久的难题,那种豁然开朗的感觉太棒了!", createdAt: now - day * 12 - hour * 4, sourceType: RecordSource.MOOD }, { id: '30', content: "深夜听歌,突然被一句歌词击中:'我们都在时光里跌跌撞撞地成长'。", createdAt: now - day * 14 - hour * 2, sourceType: RecordSource.MANUAL } ]; }; // Mock Data: Community Posts const generateMockPosts = (): CommunityPost[] => { const now = Date.now(); return [ { id: '101', user: { name: '安静的观察者', avatarColor: 'bg-indigo-200' }, content: "有人也觉得自己在等待一些还没发生的事情吗?这是一种对未来的奇怪怀念。", createdAt: now - 1000 * 60 * 10, // 10 mins ago likeCount: 12, isLiked: false, commentCount: 3 }, { id: '102', user: { name: '温柔的灵魂', avatarColor: 'bg-pink-200' }, content: "泡了一杯茶,看着蒸汽升起看了5分钟。这是我今天最美好的时刻。", createdAt: now - 1000 * 60 * 45, likeCount: 28, isLiked: true, commentCount: 5 }, { id: '103', user: { name: '匿名', avatarColor: 'bg-teal-200' }, content: "今天我试着对自己更温柔一些。这比对别人温柔更难。", createdAt: now - 1000 * 60 * 60 * 2, likeCount: 45, isLiked: false, commentCount: 8 }, { id: '104', user: { name: '云中漫步者', avatarColor: 'bg-blue-200' }, content: "今天的日落特别粉。", createdAt: now - 1000 * 60 * 60 * 5, likeCount: 8, isLiked: false, commentCount: 0 } ]; }; // Mock Data: Profile & Device const mockProfile: Profile = { name: "小雅", birthday: "3月12日", moodStatus: "感觉平静而专注", }; const mockDeviceStatus: DeviceStatus = { isConnected: true, batteryLevel: 82, deviceName: "心灵伴侣吊坠" }; // Mock Data: Moods - 包含所有情绪类型 const generateMockMoods = (): MoodItem[] => { const now = Date.now(); const hour = 60 * 60 * 1000; return [ // HAPPY - 开心 { id: 'm1', type: MoodType.HAPPY, date: now - hour * 2, intensity: 0.9, x: 25, y: 30 }, { id: 'm2', type: MoodType.HAPPY, date: now - hour * 5, intensity: 0.7, x: 70, y: 25 }, // CALM - 平静 { id: 'm3', type: MoodType.CALM, date: now - hour * 1, intensity: 0.8, x: 35, y: 45 }, { id: 'm4', type: MoodType.CALM, date: now - hour * 8, intensity: 0.6, x: 55, y: 60 }, { id: 'm5', type: MoodType.CALM, date: now - hour * 12, intensity: 0.75, x: 20, y: 70 }, // TIRED - 疲惫 { id: 'm6', type: MoodType.TIRED, date: now - hour * 3, intensity: 0.5, x: 65, y: 50 }, { id: 'm7', type: MoodType.TIRED, date: now - hour * 10, intensity: 0.4, x: 45, y: 75 }, // ANXIOUS - 焦虑 { id: 'm8', type: MoodType.ANXIOUS, date: now - hour * 4, intensity: 0.6, x: 80, y: 40 }, { id: 'm9', type: MoodType.ANXIOUS, date: now - hour * 6, intensity: 0.55, x: 30, y: 55 }, // HOPEFUL - 充满希望 { id: 'm10', type: MoodType.HOPEFUL, date: now, intensity: 0.85, x: 50, y: 35 }, { id: 'm11', type: MoodType.HOPEFUL, date: now - hour * 7, intensity: 0.7, x: 75, y: 65 }, { id: 'm12', type: MoodType.HOPEFUL, date: now - hour * 11, intensity: 0.65, x: 40, y: 20 } ]; }; // Mock Data: Inspirations - 丰富的灵感数据 const generateMockInspirations = (): InspirationItem[] => { const now = Date.now(); const hour = 60 * 60 * 1000; const day = 24 * hour; return [ { id: 'i1', content: "如果云朵只是地球在做梦呢?", createdAt: now - hour * 2, tags: ['随想', '自然'] }, { id: 'i2', content: "设计概念:一个不显示数字的时钟,只用颜色代表一天的能量。", createdAt: now - hour * 5, tags: ['设计', '创意'] }, { id: 'i3', content: "旧书和咖啡的香气,是时光最温柔的记忆。", createdAt: now - hour * 8, tags: ['生活', '随想'] }, { id: 'i4', content: "记得在接电话前深呼吸,给自己三秒钟的准备时间。", createdAt: now - day, tags: ['提醒', '生活'] }, { id: 'i5', content: "也许每个人都是一座岛屿,而友谊是连接彼此的桥梁。", createdAt: now - day - hour * 3, tags: ['随想', '友情'] }, { id: 'i6', content: "学习新技能时,不要害怕犯错,错误是成长的阶梯。", createdAt: now - day * 2, tags: ['学习', '成长'] }, { id: 'i7', content: "工作灵感:用番茄工作法,25分钟专注,5分钟放松。", createdAt: now - day * 2 - hour * 4, tags: ['工作', '提醒'] }, { id: 'i8', content: "雨后的空气里藏着大地的秘密。", createdAt: now - day * 3, tags: ['自然', '随想'] }, { id: 'i9', content: "创意想法:设计一个记录每天小确幸的应用。", createdAt: now - day * 3 - hour * 6, tags: ['创意', '设计'] }, { id: 'i10', content: "真正的朋友,是那个在你沉默时也能理解你的人。", createdAt: now - day * 4, tags: ['友情', '生活'] }, { id: 'i11', content: "每天写三件感恩的事,心态会慢慢变得更积极。", createdAt: now - day * 5, tags: ['成长', '提醒'] }, { id: 'i12', content: "工作中遇到困难时,试着换个角度思考问题。", createdAt: now - day * 5 - hour * 2, tags: ['工作', '成长'] }, { id: 'i13', content: "学习笔记:费曼学习法 - 用简单的语言解释复杂的概念。", createdAt: now - day * 6, tags: ['学习', '工作'] }, { id: 'i14', content: "窗外的鸟鸣声,是大自然送给城市的礼物。", createdAt: now - day * 7, tags: ['自然', '生活'] }, { id: 'i15', content: "设计理念:少即是多,简洁才是最高级的美。", createdAt: now - day * 8, tags: ['设计', '随想'] } ]; }; // Mock Data: Todos - 丰富的待办数据 const generateMockTodos = (): TodoItem[] => { const now = Date.now(); const hour = 60 * 60 * 1000; const day = 24 * hour; return [ { id: 't1', title: "慢慢喝一杯水", createdAt: now, scheduledAt: now + hour / 2, isDone: false, category: 'health' }, { id: 't2', title: "读《小王子》20页", createdAt: now - hour, scheduledAt: now + hour * 2, isDone: false, category: 'life' }, { id: 't3', title: "回复小雪关于项目的邮件", createdAt: now - hour * 4, scheduledAt: now + day, isDone: false, category: 'work' }, { id: 't4', title: "整理学习笔记", createdAt: now - hour * 6, scheduledAt: now + day, isDone: false, category: 'study' }, { id: 't5', title: "晚上8点运动30分钟", createdAt: now - day, scheduledAt: now + hour * 8, isDone: false, category: 'health' }, { id: 't6', title: "准备明天的会议材料", createdAt: now - day, scheduledAt: now + day, isDone: false, category: 'work' }, { id: 't7', title: "买鲜花", createdAt: now - day, isDone: true, category: 'life' }, { id: 't8', title: "完成英语作业", createdAt: now - day * 2, isDone: true, category: 'study' }, { id: 't9', title: "给植物浇水", createdAt: now - day * 2, isDone: true, category: 'life' } ]; }; export default function App() { const [currentTab, setCurrentTab] = useState(Tab.HOME); const [records, setRecords] = useState(generateMockRecords()); const [posts, setPosts] = useState(generateMockPosts()); const [moods, setMoods] = useState(generateMockMoods()); const [inspirations, setInspirations] = useState(generateMockInspirations()); const [todos, setTodos] = useState(generateMockTodos()); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [characterImageUrl, setCharacterImageUrl] = useState(); const [characterPreferences, setCharacterPreferences] = useState(); const [showCustomizationDialog, setShowCustomizationDialog] = useState(false); const [showChatDialog, setShowChatDialog] = useState(false); // State to manage full-screen action views (like Mood Page) const [activeActionView, setActiveActionView] = useState(null); // Load data from backend on mount useEffect(() => { loadAllData(); }, []); const loadAllData = async () => { try { setLoading(true); setError(null); // Load all data in parallel const [recordsRes, moodsRes, inspirationsRes, todosRes, userConfigRes] = await Promise.all([ apiService.getRecords().catch(() => ({ records: [] })), apiService.getMoods().catch(() => ({ moods: [] })), apiService.getInspirations().catch(() => ({ inspirations: [] })), apiService.getTodos().catch(() => ({ todos: [] })), apiService.getUserConfig().catch(() => null) ]); // Transform and set data if (recordsRes.records.length > 0) { setRecords(recordsRes.records.map(transformRecord)); } if (moodsRes.moods.length > 0) { setMoods(moodsRes.moods.map((m, i) => transformMood(m, i))); } if (inspirationsRes.inspirations.length > 0) { setInspirations(inspirationsRes.inspirations.map(transformInspiration)); } if (todosRes.todos.length > 0) { setTodos(todosRes.todos.map(transformTodo)); } // Set character image if (userConfigRes?.character?.image_url) { setCharacterImageUrl(userConfigRes.character.image_url); } // Set character preferences if (userConfigRes?.character?.preferences) { setCharacterPreferences(userConfigRes.character.preferences); } } catch (err) { console.error('Failed to load data:', err); setError('Failed to load data from server. Using mock data.'); } finally { setLoading(false); } }; // 获取最近的录音记录 const latestVoiceRecord = records .filter(r => r.sourceType === RecordSource.VOICE) .sort((a, b) => b.createdAt - a.createdAt)[0]?.content; // Chat with AI const handleSendMessage = async (message: string): Promise => { try { const response = await apiService.chatWithAI(message); return response; } catch (error) { console.error('Failed to chat:', error); return '抱歉,我现在有点累了,稍后再聊好吗?'; } }; // Background gradient configuration const bgGradient = "bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-purple-100 via-pink-50 to-blue-50"; const handleActionClick = (action: MoodAction) => { console.log(`Action clicked: ${action}`); if (action === MoodAction.MOOD) { setActiveActionView(MoodAction.MOOD); } else if (action === MoodAction.INSPIRATION) { setActiveActionView(MoodAction.INSPIRATION); } else if (action === MoodAction.TODO) { setActiveActionView(MoodAction.TODO); } }; const closeActionView = () => { setActiveActionView(null); }; const handleAddPost = (content: string) => { const newPost: CommunityPost = { id: Date.now().toString(), user: { name: 'Me', avatarColor: 'bg-purple-300' }, content: content, createdAt: Date.now(), likeCount: 0, isLiked: false, commentCount: 0 }; setPosts([newPost, ...posts]); }; const handleAddInspiration = async (content: string, isVoice: boolean) => { try { // 如果是语音,先转换为文字 if (isVoice) { // 这里应该调用语音转文字API console.log('Voice input:', content); } // 调用后端API处理灵感 const response = await apiService.processInput(undefined, content); // 刷新数据 await loadAllData(); console.log('Inspiration added:', response); } catch (error) { console.error('Failed to add inspiration:', error); alert('添加灵感失败,请重试'); } }; const handleAddTodo = () => { console.log("Add todo clicked"); // Placeholder }; const handleToggleTodo = async (id: string) => { // Optimistic update const todo = todos.find(t => t.id === id); if (!todo) return; const newStatus = todo.isDone ? 'pending' : 'completed'; setTodos(todos.map(t => t.id === id ? { ...t, isDone: !t.isDone } : t )); // Update backend try { await apiService.updateTodoStatus(id, newStatus); } catch (err) { console.error('Failed to update todo:', err); // Revert on error setTodos(todos.map(t => t.id === id ? { ...t, isDone: todo.isDone } : t )); } }; const handleGenerateCharacter = async (preferences: CharacterPreferences) => { try { console.log('Generating character with preferences:', preferences); const result = await apiService.generateCharacter(preferences); console.log('Character generated:', result); // 更新角色形象 setCharacterImageUrl(result.image_url); setCharacterPreferences(result.preferences); // 显示成功提示 alert('AI 形象生成成功!'); } catch (error) { console.error('Failed to generate character:', error); throw error; } }; const handleTabChange = (tab: Tab) => { // 如果有打开的全屏页面,先关闭它 if (activeActionView) { setActiveActionView(null); } // 然后切换标签 setCurrentTab(tab); }; const handleOpenChat = () => { setShowChatDialog(true); }; const isHome = currentTab === Tab.HOME; return (
{/* Ambient noise texture overlay */}
{/* Main Content Area */}
{/* HOME VIEW */} {isHome && !activeActionView && ( <>
I'm here with you
console.log('AI greeting:', greeting)} onOpenChat={handleOpenChat} /> {/* Home Input Component */}
setShowCustomizationDialog(true)} /> )} {/* RECORD VIEW */} {currentTab === Tab.RECORD && !activeActionView && ( )} {/* COMMUNITY VIEW */} {currentTab === Tab.COMMUNITY && !activeActionView && ( )} {/* MINE VIEW */} {currentTab === Tab.MINE && !activeActionView && ( )} {/* --- FULL SCREEN OVERLAYS --- */} {/* MOOD PAGE OVERLAY */} {activeActionView === MoodAction.MOOD && ( )} {/* INSPIRATION PAGE OVERLAY */} {activeActionView === MoodAction.INSPIRATION && ( )} {/* TODO PAGE OVERLAY */} {activeActionView === MoodAction.TODO && ( )} {/* Bottom Navigation - 始终显示,z-index 最高 */}
{/* Character Customization Dialog */} setShowCustomizationDialog(false)} onGenerate={handleGenerateCharacter} onSelectHistory={(imageUrl) => { setCharacterImageUrl(imageUrl); }} currentPreferences={characterPreferences} currentImageUrl={characterImageUrl} /> {/* Chat Dialog */} setShowChatDialog(false)} characterImageUrl={characterImageUrl} onSendMessage={handleSendMessage} />
); }