"use client"
import type React from "react"
import ReactMarkdown from "react-markdown"
import { Loader2 } from "lucide-react"
import { useState, useRef, useEffect, useCallback } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent } from "@/components/ui/card"
import { Send, Upload, Download, Check, Info } from "lucide-react"
import ChartRenderer from "./chart-renderer"
import UserRegistrationModal from "./user-registration-modal"
import UsageLimitIndicator from "./usage-limit-indicator"
import CTAMessage from "./cta-message"
import { createConversation, saveMessage, incrementUserMessageCount, getUserMessageCount } from "@/actions/chat-actions"
import { uploadLoadProfile } from "@/actions/file-actions"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
// Add a style object for consistent font sizes
const fontStyles = {
base: {
fontSize: "12px",
},
logo: {
fontSize: "14px",
fontWeight: 500,
},
heading: {
fontSize: "18px",
fontWeight: 600,
},
}
type Message = {
id: string
role: "user" | "assistant" | "cta"
content: string
hasChart?: boolean
chartConfig?: any
chartData?: any
isLoading?: boolean
userId?: string // For CTA messages
isCTA?: boolean // Flag to identify CTA messages
}
// Stage-based content for guiding users
const stageContent = [
{
stage: 1,
description:
"Stellen Sie mir Ihre ersten Frage, und ich zeige Ihnen, wie Sie Energie sparen und effizienter werden können!",
prompts: [
"Wie kann ich Energie effizienter nutzen?",
"Welche vorteile habe ich durch die ISO 50001?",
"Wie senke ich mit ecoplanet meine Energiekosten?",
],
},
{
stage: 2,
description:
"Laden Sie das ausgefüllte Lastgang-Template hoch, und ich analysiere Ihren Energieverbrauch! Ich berechne Grund- und Spitzenlasten und visualisiere Ihren Lastgang für einen Tag. Alternativ können sie den Demo-Lastgang verwenden.",
prompts: [
"Was war mein Energieverbauch im Mai 2024",
"Visualisiere meinen Lastgang am 20.04.2024",
"Was war meine Grundlast in 3. Quartal 2024",
],
},
{
stage: 3,
description:
"Lassen Sie uns Ihre Energieziele und Maßnahmen für die ISO 50001 planen! Ich helfe Ihnen, Ihre Effizienz zu steigern und CO₂ zu reduzieren.",
prompts: [
"Welche Energieziele sind sinnvoll?",
"Welche Maßnahmen schlagen Sie vor?",
"Wie plane ich CO₂-Reduktion?",
],
},
]
const initialMessage: Message = {
id: "initial",
role: "assistant",
content:
"Willkommen bei Ember AI! Ich bin Ihr virtueller Assistent für alle Fragen rund um Energiemanagement, Energiebeschaffung und Energiemanagement-Zertifizierungen. Sie können mich zu diesen Themen befragen oder Ihren Lastgang analysieren lassen. Wie kann ich Ihnen heute helfen?",
}
const MAX_MESSAGES = 2
export default function EmberAIChat() {
const [messages, setMessages] = useState([initialMessage])
const [input, setInput] = useState("")
const [isLoading, setIsLoading] = useState(false)
const [file, setFile] = useState(null)
const [useDemo, setUseDemo] = useState(true)
const messagesEndRef = useRef(null)
const messagesContainerRef = useRef(null)
const fileInputRef = useRef(null)
const [errorMessage, setErrorMessage] = useState(null)
const [isOffline, setIsOffline] = useState(!navigator.onLine)
const chatContainerRef = useRef(null)
const [fileUploadStatus, setFileUploadStatus] = useState<"idle" | "uploading" | "success" | "error">("idle")
const [fileUploadError, setFileUploadError] = useState(null)
// Add this with the other state variables
const [currentStage, setCurrentStage] = useState(1)
// User and conversation state
const [userId, setUserId] = useState(null)
const [conversationId, setConversationId] = useState(null)
const [messageCount, setMessageCount] = useState(0)
const [showRegistrationModal, setShowRegistrationModal] = useState(false)
const [showCTA, setShowCTA] = useState(false)
const [hasAttemptedToSend, setHasAttemptedToSend] = useState(false)
const [pendingMessage, setPendingMessage] = useState(null)
// Flag to prevent duplicate message processing
const [isProcessingPendingMessage, setIsProcessingPendingMessage] = useState(false)
// Flag to track if we're waiting for the last message response
const [isWaitingForLastMessageResponse, setIsWaitingForLastMessageResponse] = useState(false)
// Flag to prevent adding CTA message from useEffect
const [shouldSkipCTACheck, setShouldSkipCTACheck] = useState(false)
// Helper function to check if a CTA message already exists in the messages array
const hasCTAMessage = useCallback(() => {
return messages.some((msg) => msg.isCTA === true)
}, [messages])
// Debug logging function
const logDebug = (message: string, ...args: any[]) => {
console.log(`[EmberAI Debug] ${message}`, ...args)
}
useEffect(() => {
// Offline/Online-Status überwachen
const handleOnline = () => setIsOffline(false)
const handleOffline = () => setIsOffline(true)
window.addEventListener("online", handleOnline)
window.addEventListener("offline", handleOffline)
return () => {
window.removeEventListener("online", handleOffline)
window.removeEventListener("offline", handleOffline)
}
}, [])
// Fixed scrolling issue by storing the scroll position before updating
useEffect(() => {
if (!messagesContainerRef.current) return
// Store current scroll information
const container = messagesContainerRef.current
const isScrolledToBottom = container.scrollHeight - container.clientHeight <= container.scrollTop + 50
// After the DOM updates, restore scroll position if needed
if (isScrolledToBottom) {
scrollToBottom()
}
}, [messages])
// Load user message count when userId changes
useEffect(() => {
if (!userId) return
const loadMessageCount = async () => {
try {
const count = await getUserMessageCount(userId)
logDebug(`Loaded message count for user ${userId}: ${count}`)
setMessageCount(count)
// Only check for CTA if we're not waiting for an API response, not explicitly skipping the check,
// and the user is not registered
if (count >= MAX_MESSAGES && !isLoading && !isWaitingForLastMessageResponse && !shouldSkipCTACheck && !userId) {
logDebug(`User ${userId} has reached the message limit (${MAX_MESSAGES}), showing CTA`)
// This is for Scenario 1 - user already at limit when logging in
logDebug("Scenario 1: User already at message limit when logging in")
// Only add CTA message if it doesn't already exist
if (!hasCTAMessage()) {
logDebug("No CTA message found, adding one")
addCTAMessage()
} else {
logDebug("CTA message already exists, not adding another one")
}
}
// Process any pending message after login
if (pendingMessage && !isProcessingPendingMessage) {
logDebug(`Processing pending message after login: "${pendingMessage}"`)
// Set flag to prevent duplicate processing
setIsProcessingPendingMessage(true)
const messageToSend = pendingMessage
// Clear pending message immediately to prevent duplicate processing
setPendingMessage(null)
// If the user has already reached their limit, don't send the message
if (count >= MAX_MESSAGES) {
logDebug("User already at message limit, not sending pending message")
setInput("") // Clear the input field
setIsProcessingPendingMessage(false)
return
}
// Add a small delay before sending to ensure state updates have completed
setTimeout(() => {
logDebug("Now sending pending message after delay")
processSendMessage(messageToSend).finally(() => {
setIsProcessingPendingMessage(false)
})
}, 300)
}
} catch (error) {
console.error("Error loading message count:", error)
setIsProcessingPendingMessage(false)
}
}
loadMessageCount()
}, [userId, pendingMessage, isLoading, isWaitingForLastMessageResponse, hasCTAMessage, shouldSkipCTACheck])
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth", block: "end" })
}
}
// Add CTA message to the chat
const addCTAMessage = () => {
if (!userId) {
logDebug("Cannot add CTA message: no userId")
return
}
// Double-check that CTA message doesn't already exist
if (hasCTAMessage()) {
logDebug("CTA message already exists, not adding another one")
return
}
logDebug("Adding CTA message to chat")
const ctaMessage: Message = {
id: `cta-${Date.now()}`,
role: "assistant",
content: "cta-special-content", // Special marker to identify this as a CTA message
userId: userId,
isCTA: true, // Flag to identify this as a CTA message
}
// Use a functional update to ensure we're working with the latest state
setMessages((prevMessages) => {
// Double check again inside the update function
const alreadyHasCTA = prevMessages.some((msg) => msg.isCTA === true)
if (alreadyHasCTA) {
logDebug("CTA message already exists (checked inside setMessages), not adding another one")
return prevMessages
}
logDebug("Adding CTA message to messages array")
return [...prevMessages, ctaMessage]
})
// Also save this as a system message in the database if we have a conversation
if (conversationId) {
saveMessage(
conversationId,
"assistant",
"Vielen Dank für Ihr Interesse an unserem KI-gestützten Energie-Experten. Sie haben Ihr Nachrichtenlimit erreicht. Möchten Sie tiefer in Ihre Energiepotenziale eintauchen? Fordern Sie jetzt eine kostenlose Demo an oder lassen Sie sich einen personalisierten Energiebericht erstellen – maßgeschneidert für Ihr Unternehmen!",
)
}
}
// Extract JSON from a string, handling both direct JSON and markdown code blocks
const extractJsonFromString = (content: string): any | null => {
try {
// First try to parse the content directly as JSON
return JSON.parse(content.trim())
} catch (e) {
// If direct parsing fails, look for JSON in markdown code blocks
const jsonCodeBlockRegex = /```(?:json)?\s*([\s\S]*?)```/g
const matches = [...content.matchAll(jsonCodeBlockRegex)]
if (matches.length > 0) {
for (const match of matches) {
try {
return JSON.parse(match[1].trim())
} catch (parseError) {
console.error("Error parsing JSON in code block:", parseError)
}
}
}
return null
}
}
// Validate chart configuration
const validateChartConfig = (config: any): boolean => {
if (!config || typeof config !== "object") {
console.error("Invalid chart config: not an object", config)
return false
}
// If type is missing, we'll default to line chart in the renderer
if (!config.type) {
console.warn("Chart type not specified, will default to line chart")
config.type = "line"
} else {
// Normalize chart type to lowercase
const originalType = config.type
config.type = config.type.toLowerCase()
// Handle variations of chart types
if (config.type === "linechart") config.type = "line"
if (config.type === "barchart") config.type = "bar"
if (config.type === "piechart") config.type = "pie"
if (originalType !== config.type) {
console.log(`Normalized chart type from "${originalType}" to "${config.type}"`)
}
}
// Check if we have series data for line and bar charts
if (
(config.type === "line" || config.type === "bar") &&
(!config.series || !Array.isArray(config.series) || config.series.length === 0)
) {
console.warn("No series defined for line/bar chart, will attempt to auto-generate from data")
}
return true
}
// Validate chart data
const validateChartData = (data: any): boolean => {
if (!data || !Array.isArray(data) || data.length === 0) {
console.error("Invalid chart data: not an array or empty", data)
return false
}
// Check if data items are objects
if (typeof data[0] !== "object") {
console.error("Invalid chart data: items are not objects", data[0])
return false
}
return true
}
const uploadFileAfterRegistration = async () => {
if (userId && file && !useDemo) {
try {
setFileUploadStatus("uploading")
const result = await uploadLoadProfile(userId, file)
if (result.success) {
setFileUploadStatus("success")
logDebug(`File uploaded successfully after registration: ${result.url}`)
} else {
setFileUploadStatus("error")
setFileUploadError(result.error || "Unbekannter Fehler beim Hochladen")
logDebug(`File upload failed after registration: ${result.error}`)
}
} catch (error) {
setFileUploadStatus("error")
setFileUploadError("Fehler beim Hochladen der Datei")
console.error("Error uploading file after registration:", error)
}
}
}
const handleRegistrationSuccess = async (newUserId: string, messageCount: number) => {
logDebug(`Registration success: User ${newUserId} with message count ${messageCount}`)
setUserId(newUserId)
setShowRegistrationModal(false)
// Get the latest message count from the database to ensure accuracy
try {
const currentCount = await getUserMessageCount(newUserId)
logDebug(`Verified message count for user ${newUserId}: ${currentCount}`)
setMessageCount(currentCount)
// Create a new conversation if we don't have one
if (!conversationId) {
try {
const newConversationId = await createConversation(newUserId)
setConversationId(newConversationId)
// Save the initial assistant message
await saveMessage(newConversationId, "assistant", initialMessage.content)
} catch (error) {
console.error("Error creating conversation:", error)
}
}
// Upload file if one was selected before registration
await uploadFileAfterRegistration()
// If the user has already reached their limit, show the CTA instead of sending the message
if (currentCount >= MAX_MESSAGES) {
logDebug(`User ${newUserId} has already reached the message limit (${MAX_MESSAGES})`)
// Clear any pending message attempts
setHasAttemptedToSend(false)
setPendingMessage(null)
setInput("") // Clear the input field to prevent accidental resubmission
// Only add CTA if it doesn't already exist
if (!hasCTAMessage()) {
addCTAMessage()
}
return
}
// Just reset the flag to indicate we've attempted to send
if (hasAttemptedToSend) {
setHasAttemptedToSend(false)
}
} catch (error) {
console.error("Error getting message count after registration:", error)
}
}
// Process and send a message
const processSendMessage = async (messageText: string) => {
if (messageText.trim() === "") return
logDebug(`Processing message: "${messageText}"`)
// Reset error messages
setErrorMessage(null)
try {
// First, get the current message count from the database to ensure accuracy
if (userId) {
const currentCount = await getUserMessageCount(userId)
logDebug(`Current message count for user ${userId}: ${currentCount}`)
setMessageCount(currentCount)
// Only check message limit for unregistered users
if (!userId && currentCount >= MAX_MESSAGES) {
logDebug(`User has reached the message limit (${MAX_MESSAGES})`)
// Only add CTA if it doesn't already exist
if (!hasCTAMessage()) {
addCTAMessage()
}
setInput("") // Clear input to prevent resubmission attempts
return
}
}
const userMessage: Message = {
id: Date.now().toString(),
role: "user",
content: messageText,
}
// Add a loading message
const loadingMessage: Message = {
id: Date.now().toString() + "-loading",
role: "assistant",
content: "Denke nach...",
isLoading: true,
}
setMessages((prev) => [...prev, userMessage, loadingMessage])
// Add this after the line: setMessages((prev) => [...prev, userMessage, loadingMessage])
// Update the stage based on user message count (excluding the initial assistant message)
const userMessageCount = messages.filter((msg) => msg.role === "user").length + 1
const newStage = Math.min(userMessageCount + 1, 3)
setCurrentStage(newStage)
setInput("")
setIsLoading(true)
// Create conversation if it doesn't exist
if (!conversationId && userId) {
const newConversationId = await createConversation(userId)
setConversationId(newConversationId)
}
// Save user message to database
if (conversationId) {
await saveMessage(conversationId, "user", userMessage.content)
}
const formData = new FormData()
formData.append("prompt", messageText)
// Add load profile file if available, but don't add any file for demo option
if (file && !useDemo) {
formData.append("load_profile", file)
}
// Add anti-abuse measures
// Add a timestamp to prevent replay attacks
formData.append("timestamp", Date.now().toString())
// Use proxy endpoint to avoid CORS issues
const response = await fetch("/api/ember-ai", {
method: "POST",
body: formData,
})
if (response.status === 429) {
throw new Error("Zu viele Anfragen. Bitte versuchen Sie es später erneut.")
}
if (!response.ok) {
throw new Error(`Server-Fehler: ${response.status} ${response.statusText}`)
}
let data
try {
data = await response.json()
} catch (jsonError) {
console.error("JSON-Parsing-Fehler:", jsonError)
throw new Error("Die Antwort konnte nicht als JSON verarbeitet werden.")
}
if (!data || typeof data.message !== "string") {
throw new Error("Ungültiges Antwortformat vom Server")
}
// Check if the response contains chart configuration
let hasChart = false
let chartConfig = null
let chartData = null
const originalMessage = data.message
// Process chart data (keeping the existing chart processing code)
// First check for legacy RECHART format
if (
data.message.includes("RECHARTCONFIG_START") &&
data.message.includes("RECHARTCONFIG_END") &&
data.message.includes("RECHARTDATA_START") &&
data.message.includes("RECHARTDATA_END")
) {
try {
// Extract the chart configuration
const configStartTag = "RECHARTCONFIG_START"
const configEndTag = "RECHARTCONFIG_END"
const configStart = data.message.indexOf(configStartTag) + configStartTag.length
const configEnd = data.message.indexOf(configEndTag)
if (configStart > 0 && configEnd > configStart) {
const configContent = data.message.substring(configStart, configEnd).trim()
chartConfig = extractJsonFromString(configContent)
if (chartConfig) {
logDebug("Found chart config:", chartConfig)
}
}
// Extract the chart data
const dataStartTag = "RECHARTDATA_START"
const dataEndTag = "RECHARTDATA_END"
const dataStart = data.message.indexOf(dataStartTag) + dataStartTag.length
const dataEnd = data.message.indexOf(dataEndTag)
if (dataStart > 0 && dataEnd > dataStart) {
const dataContent = data.message.substring(dataStart, dataEnd).trim()
chartData = extractJsonFromString(dataContent)
if (chartData) {
logDebug("Found chart data:", chartData)
}
}
// Validate chart config and data
const isConfigValid = validateChartConfig(chartConfig)
const isDataValid = validateChartData(chartData)
// If we have both valid config and data, we can render a chart
if (isConfigValid && isDataValid) {
hasChart = true
// Clean the message by removing the chart configuration and data
data.message = data.message
.replace(/RECHARTCONFIG_START[\s\S]*?RECHARTCONFIG_END/, "")
.replace(/RECHARTDATA_START[\s_S]*?RECHARTDATA_END/, "")
.trim()
// Add a note about the chart if the message is empty after removing JSON
if (data.message.trim() === "") {
data.message = "*Hier ist die grafische Darstellung der Daten:*"
}
} else {
console.error("Invalid chart config or data:", { chartConfig, chartData })
}
} catch (chartError) {
console.error("Fehler beim Parsen der Diagrammkonfiguration:", chartError)
// If parsing fails, we don't show a chart
hasChart = false
}
} else {
// If not in legacy format, look for JSON code blocks
const jsonCodeBlockRegex = /```(?:json)?\s*([\s\S]*?)```/g
const jsonMatches = [...data.message.matchAll(jsonCodeBlockRegex)]
if (jsonMatches.length > 0) {
try {
// Try to find chart configuration and data in JSON blocks
for (const match of jsonMatches) {
try {
const jsonContent = match[1].trim()
const parsedJson = JSON.parse(jsonContent)
// Check if this is a chart configuration
if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson)) {
// If it has a type property or series property, it's likely a chart config
if (parsedJson.type || parsedJson.series) {
chartConfig = parsedJson
logDebug("Found chart config in code block:", chartConfig)
}
}
// Check if this is chart data (array of objects)
else if (Array.isArray(parsedJson) && parsedJson.length > 0 && typeof parsedJson[0] === "object") {
chartData = parsedJson
logDebug("Found chart data in code block:", chartData)
}
} catch (parseError) {
console.error("Error parsing JSON in code block:", parseError)
}
}
// Validate chart config and data
const isConfigValid = validateChartConfig(chartConfig)
const isDataValid = validateChartData(chartData)
// If we have both valid config and data, we can render a chart
if (isConfigValid && isDataValid) {
hasChart = true
// Remove JSON code blocks from the message
data.message = data.message.replace(jsonCodeBlockRegex, "").trim()
// Add a note about the chart if the message is empty after removing JSON
if (data.message.trim() === "") {
data.message = "*Hier ist die grafische Darstellung der Daten:*"
}
} else {
console.error("Invalid chart config or data:", { chartConfig, chartData })
}
} catch (chartError) {
console.error("Fehler beim Parsen der JSON-Blöcke:", chartError)
// If parsing fails, we don't show a chart
hasChart = false
}
}
}
// If we couldn't parse the chart, restore the original message
if (!hasChart || !chartConfig || !chartData) {
data.message = originalMessage
}
// Ensure chart config has at least an empty object
if (hasChart && (!chartConfig || typeof chartConfig !== "object")) {
chartConfig = { type: "line" }
}
// Add this block after the API response is received, just before creating assistantMessage:
// Increment user message count in the database AFTER receiving the response
if (userId) {
await incrementUserMessageCount(userId)
// Get the updated count after incrementing
const updatedCount = await getUserMessageCount(userId)
logDebug(`Updated message count for user ${userId} after response: ${updatedCount}`)
setMessageCount(updatedCount)
// Direct check: if we've just reached the message limit, ensure we'll show the CTA
if (updatedCount >= MAX_MESSAGES) {
logDebug(`User has now reached the message limit (${updatedCount}/${MAX_MESSAGES})`)
// Set a flag to add the CTA after the message is displayed
setIsWaitingForLastMessageResponse(true)
// This is a fallback in case the other mechanism fails
setTimeout(() => {
logDebug("Fallback CTA check executing")
if (!hasCTAMessage()) {
logDebug("No CTA message found in fallback check, adding one")
addCTAMessage()
}
}, 2000)
}
}
const assistantMessage: Message = {
id: data.thread_id || Date.now().toString() + "-assistant",
role: "assistant",
content: data.message,
hasChart,
chartConfig,
chartData,
}
// Save assistant message to database
if (conversationId) {
await saveMessage(conversationId, "assistant", assistantMessage.content, hasChart, chartConfig, chartData)
}
// Replace the loading message with the actual response
setMessages((prev) => {
// Filter out the loading message and add the assistant message
return prev.filter((msg) => !msg.isLoading).concat(assistantMessage)
})
logDebug("Added assistant message to chat")
// Check if we were waiting for the last message response
// Handle this outside the setMessages callback for more reliability
if (isWaitingForLastMessageResponse) {
logDebug("Last message response received, will add CTA message after delay")
// Add a delay to ensure the message is fully rendered first
setTimeout(() => {
// Check flags again inside the timeout to ensure we have the latest state
logDebug("Timeout executed, checking if we should add CTA message")
logDebug(`Current message count: ${messageCount}, MAX_MESSAGES: ${MAX_MESSAGES}`)
// Only add CTA if it doesn't already exist
if (!hasCTAMessage()) {
logDebug("No CTA message found, adding one now")
addCTAMessage()
} else {
logDebug("CTA message already exists, not adding another one")
}
// Reset the flags after the timeout completes
setIsWaitingForLastMessageResponse(false)
setShouldSkipCTACheck(false)
}, 1500)
}
} catch (error) {
console.error("Fehler beim Senden der Nachricht:", error)
const errorMsg =
error instanceof Error ? error.message : "Unbekannter Fehler bei der Kommunikation mit dem Server"
setErrorMessage(errorMsg)
const errorMessage: Message = {
id: Date.now().toString() + "-error",
role: "assistant",
content:
"Es tut mir leid, aber ich konnte Ihre Anfrage nicht verarbeiten. Bitte versuchen Sie es später noch einmal.",
}
// Replace the loading message with the error message
setMessages((prev) => prev.filter((msg) => !msg.isLoading).concat(errorMessage))
// Reset the waiting flag if there was an error
setIsWaitingForLastMessageResponse(false)
// Reset the skip CTA check flag
setShouldSkipCTACheck(false)
} finally {
setIsLoading(false)
}
}
// Add this function to the component to implement a simple client-side throttling
const useThrottledCallback = (callback: (...args: any[]) => any, delay: number) => {
const [lastCall, setLastCall] = useState(0)
return useCallback(
(...args: any[]) => {
const now = Date.now()
if (now - lastCall > delay) {
setLastCall(now)
return callback(...args)
}
},
[callback, lastCall, delay],
)
}
// Then update the handleSendMessage function to use throttling
// Add this near the other hooks at the top of the component
const throttledProcessSendMessage = useThrottledCallback(processSendMessage, 1000)
// Now modify the handleSendMessage function to add a check to prevent reopening the modal
// And update the handleSendMessage function to use the throttled version
const handleSendMessage = async () => {
if (input.trim() === "") return
logDebug(`User attempting to send message: "${input}"`)
// Get the current number of user messages
const userMessageCount = messages.filter((msg) => msg.role === "user").length
// Check if user is registered - only require registration after 2 messages
if (!userId && userMessageCount >= MAX_MESSAGES) {
logDebug("User has sent 2 messages, storing pending message and showing registration modal")
// Store the message to be sent after registration
setPendingMessage(input)
// Only show registration modal if it's not already showing
if (!showRegistrationModal) {
setShowRegistrationModal(true)
setHasAttemptedToSend(true)
}
return
}
// If we have a userId or haven't reached the message limit yet, send the message
await throttledProcessSendMessage(input)
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
handleSendMessage()
}
}
const handleFileChange = async (e: React.ChangeEvent) => {
if (e.target.files && e.target.files[0]) {
const selectedFile = e.target.files[0]
// Check if the file is an Excel file
if (!selectedFile.name.toLowerCase().endsWith(".xlsx")) {
setErrorMessage("Bitte laden Sie nur Excel-Dateien (.xlsx) hoch.")
// Reset the file input
if (fileInputRef.current) {
fileInputRef.current.value = ""
}
return
}
setFile(selectedFile)
setUseDemo(false)
setErrorMessage(null) // Clear any previous error messages
// Only upload to Supabase if we have a userId
if (userId) {
try {
setFileUploadStatus("uploading")
const result = await uploadLoadProfile(userId, selectedFile)
if (result.success) {
setFileUploadStatus("success")
logDebug(`File uploaded successfully: ${result.url}`)
} else {
setFileUploadStatus("error")
setFileUploadError(result.error || "Unbekannter Fehler beim Hochladen")
logDebug(`File upload failed: ${result.error}`)
}
} catch (error) {
setFileUploadStatus("error")
setFileUploadError("Fehler beim Hochladen der Datei")
console.error("Error uploading file:", error)
}
} else {
// If no userId, we'll need to show the registration modal
// The file will be uploaded after registration
setShowRegistrationModal(true)
}
}
}
const handleExampleClick = (prompt: string) => {
setInput(prompt)
}
const downloadTemplate = () => {
// Direct the user to the Google Drive download URL
window.open("https://drive.google.com/uc?export=download&id=1RjFu-QVMy7eGHFLy4HANAAx2t8mQPp--", "_blank") // MARK: HTTPS URL - correct usage
}
return (
<>
Ihr gesamtes Energiemanagement – Alles an einem Ort
Das ecoplanet Cockpit vereint ganzheitliches Energiemanagement durch das Monitoring und die Steuerung von Energieverbrauchern, die Integration intelligenter Beschaffungsstrategien sowie die Anpassung an regulatorische Anforderungen.
Energieverbrauch verstehen
Maximieren Sie die Energieeffizienz direkt über das benutzerfreundliche Interface des ecoplanet Cockpits. Entdecken Sie unüblichen Verbrauch mit der ecoplanet-Verbrauchstransparenz und erhalten Sie sofort Warnungen bei Veränderungen. Nutzen Sie förderfähige Messtechnik, um Verbrauchsanomalien noch detaillierter zu identifizieren. Ember, die künstliche Intelligenz von ecoplanet, unterstützt Sie dabei, Energieeffizienz zu steigern und Kosten zu senken.
Optimieren Sie Ihre Energiebeschaffung: Im ecoplanet Cockpit können Sie Ihre individuelle Beschaffungsstrategie direkt festlegen, Angebote vergleichen und die automatisierte Marktfolge nutzen. Erzielen Sie mühelos direkte und transparente Einsparungen. Mit Zielpreisalarmen und Marktupdates bleiben Sie stets informiert und können vorausschauend agieren. Je nach Planungshorizont haben Sie die Möglichkeit bereits jetzt Teilmengen für die kommenden Jahre zu fixieren.
Navigieren Sie mühelos durch regulatorische Anforderungen mit dem ecoplanet Energiemanager Pro. Behalten Sie Berichtspflichten im Blick, automatisieren Sie Energieaudits und CO2-Reporting, und bleiben Sie immer informiert. Im Prozess der ISO 50001 unterstützt der Energiemanager Pro Sie umfassend, indem er alle notwendigen Formerfordernisse, wie Energieleistungs-kennzahlen (EnPIs) nach ISO 50006, abbildet.
Zahlreiche Kunden profitieren bereits von ecoplanet
14%
Energiekosten eingespart bis heute
"ecoplanet hat mir nicht nur geholfen, Echtzeit-Transparenz über den Energieverbrauch meines Unternehmens zu erlangen, sondern definiert mir auch einen spezifischen Energieeffizienz-Fahrplan, mit dem ich Nachhaltigkeitsziele erreichen kann."
Jonathan Schmidt
Büchel GmbH
5%
Energiekosten eingespart bis heute
"Unser Ziel ist es, für jedes Fertigungswerkstück einen realistischen Kostensatz zu bestimmen, basierend auf tatsächlichem Stromverbrauch. Dafür sind präzise Einzelmessungen entscheidend. Wir sind gespannt auf die neue Ember AI, um zukünftig intelligente Handlungsempfehlungen erhalten zu können."
Björn Katthage
R&R Formentechnik
7%
Energiekosten eingespart bis heute
"Die Verwaltung des Energieverbrauchs in all unseren Seniorenresidenzen ist keine leichte Aufgabe. Die Unterstützung von ecoplanet bei der Energietransparenz und -beschaffung ist von unschätzbarem Wert für unsere Bemühungen, Kosten zu senken und zu optimieren."
Christoph Mosler
Alloheim Senioren-Residenzen GmbH
Unser nächstes Webinar
Jetzt anmelden!
Aktuell haben wir keine anstehenden Events.
Ihre Einsparungen sind unser Erfolg!
ecoplanet verbindet das fortschrittliche ecoplanet Cockpit, die umfassende Energiemanagement-KI Ember und das Fachwissen eines Teams aus Energieexperten. Unser Ziel ist es, gemeinsam mit Ihnen Pionierarbeit in der Energiewende zu leisten.