diff --git a/src/api/projectHandling.jsx b/src/api/projectHandling.jsx index 3f701ca..09261d9 100644 --- a/src/api/projectHandling.jsx +++ b/src/api/projectHandling.jsx @@ -165,22 +165,84 @@ export const fetchDecryptedSecret = async (configId) => { } }; -export const saveChatMessage = async (projectId, message) => { +export const saveChatMessage = async ( + projectId, + message, + { isServerConnected, isAuthenticated, userId } = {}, +) => { try { await axios.post(`${API_URL}/api/projects/${projectId}/chat/save`, { message, }); + + // Sync full chat history to cloud after local save + if (isServerConnected && isAuthenticated && userId) { + try { + const history = await loadChatHistory(projectId); + await axios.put( + `${import.meta.env.VITE_admin_server}/api/ai-chat/${userId}/${projectId}`, + { messages: history }, + { withCredentials: true }, + ); + } catch (syncError) { + console.log("Failed to sync chat to cloud:", syncError.message); + } + } } catch (error) { console.error("Failed to save chat message:", error); } }; -export const loadChatHistory = async (projectId) => { +export const loadChatHistory = async ( + projectId, + { isServerConnected, isAuthenticated, userId } = {}, +) => { try { const response = await axios.get( `${API_URL}/api/projects/${projectId}/chat`, ); - return response.data.history || []; + const localHistory = response.data.history || []; + + if (isServerConnected && isAuthenticated && userId) { + if (localHistory.length === 0) { + // Pull from cloud if local is empty (e.g. new device) + try { + const cloudRes = await axios.get( + `${import.meta.env.VITE_admin_server}/api/ai-chat/${userId}/${projectId}`, + { withCredentials: true }, + ); + const cloudMessages = cloudRes.data.messages || []; + if (cloudMessages.length > 0) { + for (const msg of cloudMessages) { + await axios.post( + `${API_URL}/api/projects/${projectId}/chat/save`, + { message: msg }, + ); + } + return cloudMessages; + } + } catch (cloudError) { + console.log("Failed to fetch chat from cloud:", cloudError.message); + } + } else { + // Push existing local history to cloud (initial sync for pre-existing chat.json) + try { + await axios.put( + `${import.meta.env.VITE_admin_server}/api/ai-chat/${userId}/${projectId}`, + { messages: localHistory }, + { withCredentials: true }, + ); + console.log("✅ Synced existing chat history to cloud"); + } catch (syncError) { + console.log( + "Failed to sync existing chat to cloud:", + syncError.message, + ); + } + } + } + + return localHistory; } catch (error) { console.error("Failed to load chat history:", error); return []; diff --git a/src/components/aiChatPanel.jsx b/src/components/aiChatPanel.jsx index a2ae8ec..8ee06ef 100644 --- a/src/components/aiChatPanel.jsx +++ b/src/components/aiChatPanel.jsx @@ -13,7 +13,7 @@ import ConfirmModal from "./confirmModal"; const AIChatPanel = ({ projectDetails, sections, onApplyChanges, onClose }) => { const { updateProjectDetails } = useContext(projectContext); - const { isAuthenticated } = useAuth(); + const { isAuthenticated, user, isServerConnected } = useAuth(); const { settings } = useSettings(); const navigate = useNavigate(); const [input, setInput] = useState(""); @@ -28,6 +28,13 @@ const AIChatPanel = ({ projectDetails, sections, onApplyChanges, onClose }) => { // Derived: input should be disabled when loading or streaming const isBusy = isLoading || streamingMsgId !== null; + // Auth context for cloud sync + const syncOptions = { + isServerConnected, + isAuthenticated, + userId: user?.userId, + }; + // ---- Typewriter effect component ---- const TypewriterText = ({ text, onComplete }) => { const [displayedText, setDisplayedText] = useState(""); @@ -110,7 +117,7 @@ const AIChatPanel = ({ projectDetails, sections, onApplyChanges, onClose }) => { // 1. Load History on Mount useEffect(() => { if (projectId) { - loadChatHistory(projectId).then((history) => { + loadChatHistory(projectId, syncOptions).then((history) => { if (history.length > 0) { setMessages(history); } else { @@ -124,7 +131,7 @@ const AIChatPanel = ({ projectDetails, sections, onApplyChanges, onClose }) => { }), }; setMessages([welcomeMsg]); - saveChatMessage(projectId, welcomeMsg); + saveChatMessage(projectId, welcomeMsg, syncOptions); } }); } @@ -139,7 +146,7 @@ const AIChatPanel = ({ projectDetails, sections, onApplyChanges, onClose }) => { // Helper to add message to state AND save to backend const addMessage = (msg) => { setMessages((prev) => [...prev, msg]); - saveChatMessage(projectId, msg); + saveChatMessage(projectId, msg, syncOptions); }; const handleSend = async (e) => { @@ -237,7 +244,11 @@ const AIChatPanel = ({ projectDetails, sections, onApplyChanges, onClose }) => { }; setMessages((prev) => [...prev, aiMsg]); setStreamingMsgId(aiMsgId); // Start typewriter - saveChatMessage(projectId, { ...aiMsg, isStreaming: false }); + saveChatMessage( + projectId, + { ...aiMsg, isStreaming: false }, + syncOptions, + ); } } catch (error) { if (error.name === "AbortError") {