diff --git a/App.tsx b/App.tsx index c5a6243..cd543b2 100644 --- a/App.tsx +++ b/App.tsx @@ -6,6 +6,10 @@ import StatisticsPage from './components/StatisticsPage'; import EditTransactionModal from './components/EditTransactionModal'; import { Transaction, TransactionType, Message, ExpenseCategory, IncomeCategory, BillReminder } from './types'; import { v4 as uuidv4 } from 'uuid'; +import { + deleteMessage, deleteTransaction, updateTransaction, createTransaction, createMessage, getMessages, getTransactions, loginOrRegister, getUserProfile, + createReminder, getReminders, updateReminder, toggleReminder, deleteReminder +} from './services/api'; type Tab = 'chat' | 'ledger' | 'stats'; @@ -79,457 +83,757 @@ const getCategoryIcon = (category: string, type: TransactionType) => { }; const App: React.FC = () => { - const [activeTab, setActiveTab] = useState('chat'); - const [messages, setMessages] = useState([]); - const [transactions, setTransactions] = useState([]); - const [budgets, setBudgets] = useState>({ '总预算': 3000 }); - const [reminders, setReminders] = useState([]); - const [inputText, setInputText] = useState(''); - const [isProcessing, setIsProcessing] = useState(false); - const [editingTransaction, setEditingTransaction] = useState(null); - const [isEditingFromChat, setIsEditingFromChat] = useState(false); - const [inputMode, setInputMode] = useState<'voice' | 'keyboard'>('voice'); + const [activeTab, setActiveTab] = useState('chat'); + const [messages, setMessages] = useState([]); + const [transactions, setTransactions] = useState([]); + const [budgets, setBudgets] = useState>({ '总预算': 3000 }); + const [reminders, setReminders] = useState([]); + const [inputText, setInputText] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + const [editingTransaction, setEditingTransaction] = useState(null); + const [isEditingFromChat, setIsEditingFromChat] = useState(false); + const [inputMode, setInputMode] = useState<'voice' | 'keyboard'>('voice'); + const [token, setToken] = useState(null); + const [userInfo, setUserInfo] = useState(null); - const [aiSettings, setAiSettings] = useState(DEFAULT_AI_SETTINGS); - const [isSettingsOpen, setIsSettingsOpen] = useState(false); - const [customPreset, setCustomPreset] = useState({ name: '自定义', description: '可以根据你喜欢的方式设定助手的语气和风格' }); - - const fileInputRef = useRef(null); - const avatarInputRef = useRef(null); - const messagesEndRef = useRef(null); + const [aiSettings, setAiSettings] = useState(DEFAULT_AI_SETTINGS); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const [customPreset, setCustomPreset] = useState({ name: '自定义', description: '可以根据你喜欢的方式设定助手的语气和风格' }); - useEffect(() => { - const storedTransactions = localStorage.getItem('cui_transactions'); - const storedMessages = localStorage.getItem('cui_messages'); - const storedBudget = localStorage.getItem('cui_budget'); - const storedReminders = localStorage.getItem('cui_reminders'); - const storedAiSettings = localStorage.getItem('cui_ai_settings'); - const storedCustom = localStorage.getItem('cui_custom_preset_info'); + const fileInputRef = useRef(null); + const avatarInputRef = useRef(null); + const messagesEndRef = useRef(null); + + // 在首页加载后打印当前URL并检查token + useEffect(() => { + console.log('当前URL:', window.location.href); + + // 检查是否存在token,如果不存在则调用登录接口 + checkAndLogin().then((userInfo) => { + // 登录成功后加载数据 + if (userInfo || localStorage.getItem('auth_token')) { + loadInitialData(); + } + }); + }, []); - if (storedTransactions) setTransactions(JSON.parse(storedTransactions)); - if (storedMessages) setMessages(JSON.parse(storedMessages)); - if (storedBudget) { - const parsed = JSON.parse(storedBudget); - setBudgets(typeof parsed === 'number' ? { '总预算': parsed } : parsed); - } - if (storedReminders) setReminders(JSON.parse(storedReminders)); - if (storedAiSettings) setAiSettings(JSON.parse(storedAiSettings)); - if (storedCustom) setCustomPreset(JSON.parse(storedCustom)); - - if (!storedMessages) { - setMessages([{ - id: 'init', - role: 'assistant', - content: `我是${storedAiSettings ? JSON.parse(storedAiSettings).name : '崔经理'}。✨\n想记账?快说!🎤`, - timestamp: Date.now() - }]); - } - }, []); - - useEffect(() => { localStorage.setItem('cui_transactions', JSON.stringify(transactions)); }, [transactions]); - useEffect(() => { localStorage.setItem('cui_budget', JSON.stringify(budgets)); }, [budgets]); - useEffect(() => { localStorage.setItem('cui_reminders', JSON.stringify(reminders)); }, [reminders]); - useEffect(() => { localStorage.setItem('cui_ai_settings', JSON.stringify(aiSettings)); }, [aiSettings]); - useEffect(() => { localStorage.setItem('cui_custom_preset_info', JSON.stringify(customPreset)); }, [customPreset]); - - useEffect(() => { - localStorage.setItem('cui_messages', JSON.stringify(messages)); - if (activeTab === 'chat') scrollToBottom(); - }, [messages, activeTab]); - - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }; - - const handleUpdateTransaction = (updated: Transaction) => { - setTransactions(prev => prev.map(t => t.id === updated.id ? updated : t)); - setEditingTransaction(null); - setIsEditingFromChat(false); - }; - - const handleDeleteTransaction = (id: string) => { - if (confirm('确定要删除这条记录吗?')) { - setTransactions(prev => prev.filter(t => t.id !== id)); - setEditingTransaction(null); - setIsEditingFromChat(false); - } - }; - - const handleAddReminder = (reminder: BillReminder) => { - setReminders(prev => [...prev, reminder]); - }; - - const handleUpdateReminder = (updated: BillReminder) => { - setReminders(prev => prev.map(r => r.id === updated.id ? updated : r)); - }; - - const handleDeleteReminder = (id: string) => { - if (confirm('确定要删除这个提醒吗?')) { - setReminders(prev => prev.filter(r => r.id !== id)); - } - }; - - const handleToggleReminder = (id: string) => { - setReminders(prev => prev.map(r => r.id === id ? { ...r, isCompleted: !r.isCompleted } : r)); - }; - - const handleSendMessage = async (text: string, imageBase64?: string, mimeType?: string) => { - if (!text.trim() && !imageBase64) return; - - const userMsg: Message = { - id: uuidv4(), - role: 'user', - content: imageBase64 ? '已上传图片' : text, - timestamp: Date.now() + // 检查token并登录的函数 + const checkAndLogin = async (): Promise => { + // 从localStorage检查是否存在token + const storedToken = localStorage.getItem('auth_token'); + if (storedToken) { + setToken(storedToken); + // 如果有token,携带token访问 GET /api/wx/profile 获取用户信息 + try { + const userInfo = await getUserProfile(); + // 更新本地缓存 + localStorage.setItem('user_info', JSON.stringify(userInfo)); + setUserInfo(userInfo); + console.log('通过token获取用户信息成功:', userInfo); + return userInfo; + } catch (error) { + console.error('获取用户信息失败:', error); + // 如果token失效,清除本地存储 + localStorage.removeItem('auth_token'); + localStorage.removeItem('user_info'); + setToken(null); + setUserInfo(null); + // 如果请求失败,尝试使用本地缓存 + try { + const cachedUserInfo = JSON.parse(localStorage.getItem('user_info') || '{}'); + if (cachedUserInfo && Object.keys(cachedUserInfo).length > 0) { + setUserInfo(cachedUserInfo); + return cachedUserInfo; + } + } catch (e) { + console.error('解析本地用户信息失败:', e); + } + } + return null; + } + + // 如果没有token,则检查URL参数并调用登录接口 + const urlParams = new URLSearchParams(window.location.search); + const openid = urlParams.get('openid'); + const mobile = urlParams.get('mobile'); + + if (openid && mobile) { + try { + const result = await loginOrRegister(openid, mobile); + // 存储token和用户信息 + localStorage.setItem('user_info', JSON.stringify(result.user)); + setToken(result.token); + setUserInfo(result.user); + console.log('登录成功:', result.user); + return result.user; + } catch (error) { + console.error('登录请求出错:', error); + return null; + } + } else { + console.log('缺少必需的URL参数: openid 或 mobile'); + return null; + } }; - setMessages(prev => [...prev, userMsg]); - setInputText(''); - setIsProcessing(true); - try { - const result = await processUserMessage(text, aiSettings.name, aiSettings.persona, imageBase64, mimeType); - - if (result.transaction) { - const newTransactionId = uuidv4(); - const newTransaction: Transaction = { - id: newTransactionId, - item: result.transaction.item, - amount: result.transaction.amount, - type: result.transaction.type, - category: result.transaction.category, - date: getLocalDateString(), - timestamp: Date.now() + const loadInitialData = async () => { + const storedBudget = localStorage.getItem('cui_budget'); + const storedReminders = localStorage.getItem('cui_reminders'); + const storedAiSettings = localStorage.getItem('cui_ai_settings'); + const storedCustom = localStorage.getItem('cui_custom_preset_info'); + + if (storedBudget) { + const parsed = JSON.parse(storedBudget); + setBudgets(typeof parsed === 'number' ? { '总预算': parsed } : parsed); + } + if (storedReminders) setReminders(JSON.parse(storedReminders)); + if (storedAiSettings) setAiSettings(JSON.parse(storedAiSettings)); + if (storedCustom) setCustomPreset(JSON.parse(storedCustom)); + + // 从数据库获取交易列表 + try { + const response = await getTransactions(); + if (response.items && response.items.length > 0) { + // 将数据库返回的交易转换为本地格式 + const transactionsFromDB: Transaction[] = response.items.map(tx => ({ + id: tx.id, + item: tx.item, + amount: tx.amount, + type: tx.type, + category: tx.category, + date: tx.date, + timestamp: tx.timestamp + })); + // 更新本地缓存 + setTransactions(transactionsFromDB); + console.log('交易列表已从数据库加载:', transactionsFromDB.length); + } else { + console.log('数据库中暂无交易记录'); + } + } catch (error) { + console.error('加载交易列表失败:', error); + // 失败时尝试从本地存储加载 + const storedTransactions = localStorage.getItem('cui_transactions'); + if (storedTransactions) { + setTransactions(JSON.parse(storedTransactions)); + console.log('已从本地缓存加载交易记录'); + } + } + + // 从数据库获取消息列表 + try { + const response = await getMessages(); + if (response.items && response.items.length > 0) { + // 将数据库返回的消息转换为本地格式 + const messagesFromDB: Message[] = response.items.map((msg: any) => ({ + id: msg.id, + role: msg.role, + content: msg.content, + timestamp: msg.timestamp, + // API返回的是transaction_id,需要映射为relatedTransactionId + relatedTransactionId: msg.transaction_id || msg.relatedTransactionId + })); + // 更新本地缓存 + setMessages(messagesFromDB); + console.log('消息列表已从数据库加载:', messagesFromDB.length); + } else { + // 如果数据库中没有消息,显示初始欢迎消息 + const aiName = storedAiSettings ? JSON.parse(storedAiSettings).name : '崔经理'; + setMessages([{ + id: 'init', + role: 'assistant', + content: `我是${aiName}。✨\n想记账?快说!🎤`, + timestamp: Date.now() + }]); + } + } catch (error) { + console.error('加载消息列表失败:', error); + // 失败时尝试从本地存储加载 + const storedMessages = localStorage.getItem('cui_messages'); + if (storedMessages) { + setMessages(JSON.parse(storedMessages)); + } else { + // 显示初始欢迎消息 + const aiName = storedAiSettings ? JSON.parse(storedAiSettings).name : '崔经理'; + setMessages([{ + id: 'init', + role: 'assistant', + content: `我是${aiName}。✨\n想记账?快说!🎤`, + timestamp: Date.now() + }]); + } + } + + // 从数据库获取待办提醒 + try { + const response = await getReminders(); + if (response.items && response.items.length > 0) { + const remindersFromDB: BillReminder[] = response.items.map((r: any) => ({ + id: r.id, + title: r.title, + amount: r.amount, + frequency: r.frequency, + targetDate: r.target_date || r.targetDate, + isCompleted: r.is_completed ?? r.isCompleted ?? false + })); + setReminders(remindersFromDB); + console.log('待办提醒已从数据库加载:', remindersFromDB.length); + } + } catch (error) { + console.error('加载待办提醒失败:', error); + } + }; + + useEffect(() => { localStorage.setItem('cui_transactions', JSON.stringify(transactions)); }, [transactions]); + useEffect(() => { localStorage.setItem('cui_budget', JSON.stringify(budgets)); }, [budgets]); + useEffect(() => { localStorage.setItem('cui_reminders', JSON.stringify(reminders)); }, [reminders]); + useEffect(() => { localStorage.setItem('cui_ai_settings', JSON.stringify(aiSettings)); }, [aiSettings]); + useEffect(() => { localStorage.setItem('cui_custom_preset_info', JSON.stringify(customPreset)); }, [customPreset]); + + useEffect(() => { + localStorage.setItem('cui_messages', JSON.stringify(messages)); + if (activeTab === 'chat') scrollToBottom(); + }, [messages, activeTab]); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + const handleUpdateTransaction = async (updated: Transaction) => { + try { + // 调用 API 更新交易记录 + const result = await updateTransaction(updated.id, { + item: updated.item, + amount: updated.amount, + type: updated.type, + category: updated.category, + date: updated.date, + timestamp: updated.timestamp + }); + console.log('交易记录已在数据库中更新:', result); + + // 更新成功后,更新本地缓存 + setTransactions(prev => prev.map(t => t.id === updated.id ? result : t)); + setEditingTransaction(null); + setIsEditingFromChat(false); + } catch (error) { + console.error('更新交易记录失败:', error); + alert('更新失败,请检查网络连接后重试。'); + throw error; + } + }; + + const handleDeleteTransaction = async (id: string) => { + if (confirm('确定要删除这条记录吗?')) { + try { + // 先尝试查找关联的消息(卡片) + const associatedMessage = messages.find(m => m.relatedTransactionId === id); + + // 调用 API 删除交易记录 + await deleteTransaction(id); + console.log('交易记录已从数据库中删除:', id); + + // 如果有对应消息,也从数据库删除 + if (associatedMessage) { + try { + await deleteMessage(associatedMessage.id); + console.log('关联消息已从数据库中删除:', associatedMessage.id); + } catch (msgErr) { + console.warn('删除关联消息失败:', msgErr); + } + } + + // 删除成功后,更新本地缓存 + setTransactions(prev => prev.filter(t => t.id !== id)); + setMessages(prev => prev.filter(m => m.relatedTransactionId !== id)); + + setEditingTransaction(null); + setIsEditingFromChat(false); + } catch (error) { + console.error('删除交易记录失败:', error); + alert('删除失败,请检查网络连接后重试。'); + } + } + }; + + const handleAddReminder = async (reminder: BillReminder) => { + try { + const result: any = await createReminder({ + title: reminder.title, + amount: reminder.amount, + frequency: reminder.frequency, + target_date: reminder.targetDate + }); + const newReminder: BillReminder = { + id: result.id, + title: result.title, + amount: result.amount, + frequency: result.frequency, + targetDate: result.target_date || result.targetDate, + isCompleted: result.is_completed ?? result.isCompleted ?? false + }; + setReminders(prev => [...prev, newReminder]); + } catch (error) { + console.error('创建待办提醒失败:', error); + alert('创建失败,请稍后重试'); + } + }; + + const handleUpdateReminder = async (updated: BillReminder) => { + try { + const result: any = await updateReminder(updated.id, { + title: updated.title, + amount: updated.amount, + frequency: updated.frequency, + target_date: updated.targetDate + }); + const mappedResult: BillReminder = { + id: result.id, + title: result.title, + amount: result.amount, + frequency: result.frequency, + targetDate: result.target_date || result.targetDate, + isCompleted: result.is_completed ?? result.isCompleted ?? false + }; + setReminders(prev => prev.map(r => r.id === updated.id ? mappedResult : r)); + } catch (error) { + console.error('更新待办提醒失败:', error); + alert('更新失败,请稍后重试'); + } + }; + + const handleDeleteReminder = async (id: string) => { + if (confirm('确定要删除这个提醒吗?')) { + try { + await deleteReminder(id); + setReminders(prev => prev.filter(r => r.id !== id)); + } catch (error) { + console.error('删除待办提醒失败:', error); + alert('删除失败,请稍后重试'); + } + } + }; + + const handleToggleReminder = async (id: string) => { + try { + const result = await toggleReminder(id); + setReminders(prev => prev.map(r => r.id === id ? { ...r, isCompleted: result.is_completed } : r)); + } catch (error) { + console.error('切换待办状态失败:', error); + alert('操作失败,请稍后重试'); + } + }; + + // 辅助函数:先保存消息到数据库,然后更新本地 + const addMessage = async (messageData: Omit & { id?: string }): Promise => { + try { + const savedMessage = await createMessage({ + role: messageData.role, + content: messageData.content, + timestamp: messageData.timestamp, + transaction_id: messageData.relatedTransactionId + }); + console.log('消息已保存到数据库:', savedMessage); + + // 数据库保存成功后,更新本地缓存 + // API返回的是transaction_id,需要映射为relatedTransactionId + const newMessage: Message = { + id: savedMessage.id, + role: savedMessage.role, + content: savedMessage.content, + timestamp: savedMessage.timestamp, + relatedTransactionId: (savedMessage as any).transaction_id || savedMessage.relatedTransactionId + }; + setMessages(prev => [...prev, newMessage]); + return newMessage; + } catch (error) { + console.error('保存消息到数据库失败:', error); + return null; + } + }; + + const handleSendMessage = async (text: string, imageBase64?: string, mimeType?: string) => { + if (!text.trim() && !imageBase64) return; + + // 先保存用户消息到数据库 + const userMessage = await addMessage({ + role: 'user', + content: imageBase64 ? '已上传图片' : text, + timestamp: Date.now() + }); + + if (!userMessage) { + // 如果保存失败,显示错误提示 + console.error('用户消息保存失败'); + return; + } + + setInputText(''); + setIsProcessing(true); + + try { + const result = await processUserMessage(text, aiSettings.name, aiSettings.persona, imageBase64, mimeType); + + if (result.transaction) { + const transactionData = { + item: result.transaction.item, + amount: result.transaction.amount, + type: result.transaction.type, + category: result.transaction.category, + date: getLocalDateString(), + timestamp: Date.now() + }; + + // 先提交到数据库 + try { + const savedTransaction = await createTransaction(transactionData); + console.log('交易记录已保存到数据库:', savedTransaction); + + // 数据库保存成功后,更新本地缓存 + const newTransaction: Transaction = { + id: savedTransaction.id, // 使用数据库返回的ID + ...transactionData + }; + setTransactions(prev => [...prev, newTransaction]); + + // 显示交易卡片和回复消息 + await addMessage({ + role: 'assistant', + content: 'RECEIPT', + timestamp: Date.now(), + relatedTransactionId: savedTransaction.id + }); + + const replySegments = result.reply.split('\n').filter(s => s.trim()).slice(0, 2); + for (let i = 0; i < replySegments.length; i++) { + if (i > 0) await new Promise(r => setTimeout(r, 450)); + await addMessage({ + role: 'assistant', + content: replySegments[i].trim(), + timestamp: Date.now() + }); + } + } catch (dbError) { + console.error('保存交易记录到数据库失败:', dbError); + // 数据库保存失败时,显示错误提示 + await addMessage({ + role: 'assistant', + content: '抱歉,保存交易记录失败,请检查网络连接或稍后重试。', + timestamp: Date.now() + }); + } + } else { + const replySegments = result.reply.split('\n').filter(s => s.trim()).slice(0, 2); + for (let i = 0; i < replySegments.length; i++) { + if (i > 0) await new Promise(r => setTimeout(r, 450)); + await addMessage({ + role: 'assistant', + content: replySegments[i].trim(), + timestamp: Date.now() + }); + } + } + } catch (error) { + await addMessage({ + role: 'assistant', + content: '系统走神了... 🫥\n等会儿再说。', + timestamp: Date.now() + }); + } finally { + setIsProcessing(false); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(inputText); + } + }; + + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onloadend = () => { + const base64Data = (reader.result as string).split(',')[1]; + handleSendMessage("", base64Data, file.type); }; - - setTransactions(prev => [...prev, newTransaction]); - - setMessages(prev => [...prev, { - id: uuidv4(), - role: 'assistant', - content: 'RECEIPT', - timestamp: Date.now(), - relatedTransactionId: newTransactionId - }]); - - const replySegments = result.reply.split('\n').filter(s => s.trim()).slice(0, 2); - for (let i = 0; i < replySegments.length; i++) { - if (i > 0) await new Promise(r => setTimeout(r, 450)); - setMessages(prev => [...prev, { - id: uuidv4(), - role: 'assistant', - content: replySegments[i].trim(), - timestamp: Date.now() - }]); - } - } else { - const replySegments = result.reply.split('\n').filter(s => s.trim()).slice(0, 2); - for (let i = 0; i < replySegments.length; i++) { - if (i > 0) await new Promise(r => setTimeout(r, 450)); - setMessages(prev => [...prev, { - id: uuidv4(), - role: 'assistant', - content: replySegments[i].trim(), - timestamp: Date.now() - }]); - } - } - } catch (error) { - setMessages(prev => [...prev, { id: uuidv4(), role: 'assistant', content: '系统走神了... 🫥\n等会儿再说。', timestamp: Date.now() }]); - } finally { - setIsProcessing(false); + reader.readAsDataURL(file); + if (fileInputRef.current) fileInputRef.current.value = ''; } - }; - const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSendMessage(inputText); + const handleAvatarUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onloadend = () => { + const base64Data = reader.result as string; + setAiSettings(prev => ({ ...prev, avatar: base64Data })); + }; + reader.readAsDataURL(file); + if (avatarInputRef.current) avatarInputRef.current.value = ''; } - }; - const handleFileUpload = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - const reader = new FileReader(); - reader.onloadend = () => { - const base64Data = (reader.result as string).split(',')[1]; - handleSendMessage("", base64Data, file.type); - }; - reader.readAsDataURL(file); - if (fileInputRef.current) fileInputRef.current.value = ''; - } + const todayStr = getLocalDateString(); + const todaySpending = transactions + .filter(t => t.date === todayStr && t.type === TransactionType.EXPENSE) + .reduce((a, b) => a + b.amount, 0); - const handleAvatarUpload = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - const reader = new FileReader(); - reader.onloadend = () => { - const base64Data = reader.result as string; - setAiSettings(prev => ({ ...prev, avatar: base64Data })); - }; - reader.readAsDataURL(file); - if (avatarInputRef.current) avatarInputRef.current.value = ''; - } + const personaPresets = INITIAL_PERSONA_PRESETS.map(p => { + if (p.id === 'custom') { + return { ...p, name: customPreset.name, description: customPreset.description }; + } + return p; + }); - const todayStr = getLocalDateString(); - const todaySpending = transactions - .filter(t => t.date === todayStr && t.type === TransactionType.EXPENSE) - .reduce((a, b) => a + b.amount, 0); + const handleSaveSettings = () => { + const currentPreset = INITIAL_PERSONA_PRESETS.find(p => p.description === aiSettings.persona && p.id !== 'custom'); + if (!currentPreset) { + setCustomPreset({ name: aiSettings.name, description: aiSettings.persona }); + } + setIsSettingsOpen(false); + }; - const personaPresets = INITIAL_PERSONA_PRESETS.map(p => { - if (p.id === 'custom') { - return { ...p, name: customPreset.name, description: customPreset.description }; - } - return p; - }); - - const handleSaveSettings = () => { - const currentPreset = INITIAL_PERSONA_PRESETS.find(p => p.description === aiSettings.persona && p.id !== 'custom'); - if (!currentPreset) { - setCustomPreset({ name: aiSettings.name, description: aiSettings.persona }); - } - setIsSettingsOpen(false); - }; - - return ( -
-
- {/* Chat Tab */} -
-
-
setIsSettingsOpen(true)}> -
- {aiSettings.avatar ? avatar : aiSettings.name.charAt(0)} -
-
-

- {aiSettings.name} - -

- - {aiSettings.persona.split(/[。!]/)[0] + '...'} - -
-
-
- 今日支出 - ¥{todaySpending.toFixed(0)} -
-
- - {/* AI Settings Modal */} - {isSettingsOpen && ( -
-
-
-

管家设定

- + return ( +
+
+ {/* Chat Tab */} +
+
+
setIsSettingsOpen(true)}> +
+ {aiSettings.avatar ? avatar : aiSettings.name.charAt(0)} +
+
+

+ {aiSettings.name} + +

+ + {aiSettings.persona.split(/[。!]/)[0] + '...'} + +
-
-
-
-
- {aiSettings.avatar ? avatar : aiSettings.name.charAt(0)} -
-
+ + {/* AI Settings Modal */} + {isSettingsOpen && ( +
+
+
+

管家设定

+ -
- 更改管家形象 -
+
+
+
+
+ {aiSettings.avatar ? avatar : aiSettings.name.charAt(0)} +
+ + +
+ 更改管家形象 +
-
- - setAiSettings({...aiSettings, name: e.target.value})} className="w-full p-3.5 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none font-bold text-gray-800" /> -
-
- -
- {personaPresets.map(p => ( - - ))} +
+ + setAiSettings({ ...aiSettings, name: e.target.value })} className="w-full p-3.5 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none font-bold text-gray-800" /> +
+
+ +
+ {personaPresets.map(p => ( + + ))} +
+
+
+ +