diff --git a/frontend/src/app/components/ChatInterface.tsx b/frontend/src/app/components/ChatInterface.tsx index 3f696ff..e455e6b 100644 --- a/frontend/src/app/components/ChatInterface.tsx +++ b/frontend/src/app/components/ChatInterface.tsx @@ -1,3 +1,13 @@ +import React, { useRef, useEffect } from 'react'; +import { Bot, Send, CheckCircle2, AlertCircle } from 'lucide-react'; +import type { Message } from '../../types/chat'; + +interface ChatInterfaceProps { + messages: Message[]; + inputState: string; + setInputState: (value: string) => void; + isTyping: boolean; + handleSendMessage: (e: React.FormEvent) => void; "use client"; import React, { useEffect, useRef, useState } from "react"; @@ -20,6 +30,65 @@ export interface ChatInterfaceProps { export function ChatInterface({ messages, + inputState, + setInputState, + isTyping, + handleSendMessage, +}: ChatInterfaceProps) { + const messagesEndRef = useRef(null); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages, isTyping]); + + return ( +
+
+
+
+ +
+
+

OpenClaw Agent

+
+ Online +
+
+
+ +
+ {messages.map((msg) => ( +
+ {msg.proactive && ( +
+ Proactive Nudge +
+ )} +
{msg.text}
+
+ ))} + {isTyping && ( +
+
+ +
+
+ )} +
+
+ +
+ setInputState(e.target.value)} + /> + +
+
isTyping, onSendMessage, placeholder = "Ask Smasage to adjust goals...", diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index f5f6967..a735270 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,3 +1,32 @@ +"use client" +import React, { useState } from 'react'; +import { Target, Activity } from 'lucide-react'; +import { PortfolioStats } from './components/PortfolioStats'; +import { evaluateGoalStatus, getStatusColor, type GoalData } from '../utils/goalProjection'; +import PortfolioChart from './PortfolioChart'; +import { DashboardHeader } from './components/DashboardHeader'; +import { ConnectWalletButton } from './components/ConnectWalletButton'; +import { useWallet } from './components/WalletContext'; +import { ErrorBoundary } from './components/ErrorBoundary'; +import { useChat } from '../hooks/useChat'; +import { ChatInterface } from './components/ChatInterface'; + +export default function Home() { + const { publicKey, setPublicKey } = useWallet(); + const [showInstallModal, setShowInstallModal] = useState(false); + // Connect Wallet logic + const handleConnectWallet = async () => { + if (!window.freighterApi) { + setShowInstallModal(true); + return; + } + try { + const key = await window.freighterApi.getPublicKey(); + setPublicKey(key); + } catch { + // Optionally handle rejection + } + }; "use client"; import React, { useState, useEffect, useMemo } from "react"; import { Activity } from "lucide-react"; @@ -73,6 +102,13 @@ export default function Home() { }; }, [goalData]); + const { messages, inputState, setInputState, isTyping, handleSendMessage, allocations, wsConnected } = useChat({ + userId: 'user-demo-001', + goalData, + }); + const goalResult = evaluateGoalStatus(goalData); + const progress = goalResult.progressPercentage; + const goalStatus = goalResult.status; // WebSocket notifications const { registerGoal } = useNotifications({ userId: "user-demo-001", @@ -233,6 +269,15 @@ export default function Home() {
+ {/* Right Panel - Chat Agent */} + + {/* Right Panel - Chat Agent */}
([ + { + id: 1, + sender: 'agent', + text: "Welcome to Smasage! 👋 I'm OpenClaw, your personal AI savings assistant natively built on Stellar. What financial goal can we crush today?", + }, + ]); + const [inputState, setInputState] = useState(''); + const [isTyping, setIsTyping] = useState(false); + const [allocations, setAllocations] = useState(getDefaultAllocations()); + const [wsConnected, setWsConnected] = useState(false); + const responseTimeout = useRef(null); + + const { registerGoal } = useNotifications({ + userId, + onNotification: (notification) => { + if (notification.type === 'connected') { + setWsConnected(true); + } else if (notification.type === 'agent-message') { + const payload = notification.payload as { + text: string; + proactive?: boolean; + timestamp?: string; + }; + + const agentMsg: Message = { + id: Date.now(), + sender: 'agent', + text: payload.text, + proactive: payload.proactive, + timestamp: payload.timestamp, + }; + + setMessages((prev) => [...prev, agentMsg]); + + const parsedAllocations = parseAllocationsFromMessage(payload.text); + if (parsedAllocations) { + setAllocations(parsedAllocations); + } + } + }, + onError: (error) => { + console.error('[Chat] WebSocket error:', error); + }, + enabled: true, + }); + + useEffect(() => { + if (wsConnected) { + registerGoal({ + currentBalance: goalData.currentBalance, + targetAmount: goalData.targetAmount, + targetDate: goalData.targetDate, + expectedAPY: goalData.expectedAPY, + monthlyContribution: goalData.monthlyContribution, + }); + } + }, [wsConnected, registerGoal, goalData]); + + useEffect(() => { + return () => { + if (responseTimeout.current !== null) { + window.clearTimeout(responseTimeout.current); + } + }; + }, []); + + const handleSendMessage = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + if (!inputState.trim()) return; + + const userMsg: Message = { id: Date.now(), sender: 'user', text: inputState }; + setMessages((prev) => [...prev, userMsg]); + setInputState(''); + setIsTyping(true); + + responseTimeout.current = window.setTimeout(() => { + setIsTyping(false); + + const agentMsg: Message = { + id: Date.now() + 1, + sender: 'agent', + text: "That's a great goal. I'll allocate 60% to Stellar Blend for safe yield, and the rest to Soroswap XLM/USDC LP to accelerate returns. Does that sound good?", + }; + + setMessages((prev) => [...prev, agentMsg]); + + const parsedAllocations = parseAllocationsFromMessage(agentMsg.text); + if (parsedAllocations) { + setAllocations(parsedAllocations); + } + }, 1800); + }, + [inputState] + ); + + return { + messages, + inputState, + setInputState, + isTyping, + handleSendMessage, + allocations, + wsConnected, + }; +} diff --git a/frontend/src/types/chat.ts b/frontend/src/types/chat.ts new file mode 100644 index 0000000..2c47037 --- /dev/null +++ b/frontend/src/types/chat.ts @@ -0,0 +1,7 @@ +export interface Message { + id: number; + sender: 'agent' | 'user'; + text: string; + proactive?: boolean; + timestamp?: string; +}