diff --git a/src/App.tsx b/src/App.tsx index 80f2e51..6655203 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { useEffect } from 'react'; // Import global CSS reset first import './index.css'; @@ -10,13 +11,27 @@ import { AuthProvider } from './auth/authProvider'; import { ProtectedRoute } from './auth/ProtectedRoute'; import Home from './pages/Home'; import Dateien from './pages/Dateien/Dateien'; -import Mitglieder from './pages/Mitglieder/Mitglieder'; +import TeamBereich from './pages/Mitglieder/TeamBereich'; import Dashboard from './pages/Dashboard'; import Einstellungen from './pages/Einstellungen/Einstellungen'; // Import the global light theme CSS variables as default import './assets/styles/light.css'; function App() { + // Load saved theme preference on app mount + useEffect(() => { + const savedTheme = localStorage.getItem('theme'); + const prefersDark = savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches); + + if (prefersDark) { + document.documentElement.classList.add('dark-theme'); + document.documentElement.classList.remove('light-theme'); + } else { + document.documentElement.classList.add('light-theme'); + document.documentElement.classList.remove('dark-theme'); + } + document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light'); + }, []); return ( @@ -31,7 +46,7 @@ function App() { }> } /> } /> - } /> + } /> } /> diff --git a/src/components/Dashboard/DashboardChat/DashboardChat.tsx b/src/components/Dashboard/DashboardChat/DashboardChat.tsx index fff930a..76a4e36 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChat.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChat.tsx @@ -27,12 +27,12 @@ const DashboardChat: React.FC = ({ onWorkflowCompletedChange, onWorkflowResume }) => { - const [activeTab, setActiveTab] = useState("Chat Area"); + const [activeTab, setActiveTab] = useState("Chatbereich"); const [resumeWorkflowId, setResumeWorkflowId] = useState(null); const handleWorkflowResume = (workflowId: string) => { // Switch to Chat Area tab first - setActiveTab("Chat Area"); + setActiveTab("Chatbereich"); // Set the workflow ID to resume setResumeWorkflowId(workflowId); // Then call the parent's resume handler @@ -57,7 +57,7 @@ const DashboardChat: React.FC = ({ transition={{ duration: 0.3, ease: "easeOut" }} >
- {["Chat Area", "Workflow History"].map((tab) => ( + {["Chatbereich", "Workflow-Verlauf"].map((tab) => (
= ({ }} style={{ overflow: "hidden" }} > - {activeTab === "Chat Area" ? ( + {activeTab === "Chatbereich" ? ( = ({ workflowCompleted, onStartNewWorkflow }) => { - if (!currentWorkflowId) return null; - return ( - <> - {!workflowCompleted && ( -
-

- Workflow {currentWorkflowId.substring(0, 8)}... is {workflowStatus?.status || 'running'} - {workflowStatus?.currentRound && ` (Round ${workflowStatus.currentRound})`} -

-
- )} - {workflowCompleted && ( -
-

Workflow completed! You can continue the conversation or start a new workflow.

- -
+ + • + + + • + + + • + +
+ )} - + ); }; diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.module.css b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.module.css index 2b81981..7f52473 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.module.css +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.module.css @@ -40,7 +40,7 @@ display: flex; flex-direction: column; justify-content: flex-start; - padding-bottom: 10px; + } .messages_spacer { @@ -54,12 +54,20 @@ align-items: flex-end; flex-shrink: 0; flex-direction: column; + transition: all 0.2s ease; +} + +.chat_input.drag_over { + background-color: var(--color-secondary-disabled); + border: 2px dashed var(--color-secondary); + border-radius: 12px; + padding: 8px; } .input_row { display: flex; gap: 10px; - align-items: center; + align-items: flex-end; width: 100%; } @@ -86,7 +94,8 @@ } .attachment_button { - padding: 11px 11px; + height: 48px; + width: 48px; background-color: var(--color-secondary-disabled); color: var(--color-secondary); border: none; @@ -134,7 +143,7 @@ } .attached_file_icon { - font-size: 14px; + font-size: 16px; } .attached_file_name { @@ -166,7 +175,8 @@ } .send_button { - padding: 12px 12px; + height: 48px; + width: 48px; background-color: var(--color-secondary); color: var(--color-bg); border: 1px solid var(--color-secondary); @@ -185,8 +195,8 @@ } .send_button_icon { - height: 100%; - width: 100%; + height: 60%; + width: 60%; margin: none; padding: none; } @@ -200,6 +210,8 @@ .stop_button { padding: 12px 12px; + height: 48px; + width: 48px; background-color: var(--color-red); color: var(--color-bg); border: none; @@ -315,11 +327,11 @@ } .workflow_status { - padding: 8px 12px; - background-color: var(--color-secondary-disabled); - border-left: 4px solid var(--color-secondary); - border-radius: 4px; - margin-bottom: 10px; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + color: var(--color-secondary); } .workflow_status p { @@ -403,6 +415,8 @@ font-size: 16px; color: var(--color-secondary); flex-shrink: 0; + + } .document_info { diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.tsx b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.tsx index 3ca2631..173815c 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.tsx @@ -58,7 +58,7 @@ const DashboardChatArea: React.FC = ({ } }, [workflowCompleted, onWorkflowCompletedChange]); - const placeholder = workflowCompleted ? "Continue the conversation..." : "Type your message..."; + const placeholder = workflowCompleted ? "Gespräch fortsetzen..." : "Nachricht eingeben..."; return (
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaInput.tsx b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaInput.tsx index cc33f7d..814e3ef 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaInput.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaInput.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { motion } from "framer-motion"; import { LuSendHorizontal } from "react-icons/lu"; import { FaStop } from "react-icons/fa"; @@ -45,6 +45,29 @@ const ChatInput: React.FC = ({ onFilesSelect }) => { const [isUploadModalOpen, setIsUploadModalOpen] = useState(false); + const [isDragOver, setIsDragOver] = useState(false); + + // Auto-resize textarea functionality + useEffect(() => { + if (inputRef?.current) { + const textarea = inputRef.current; + textarea.style.height = 'auto'; + + // Calculate line height - approximately 1.5em per line + const lineHeight = 24; // Adjust this value based on your CSS line-height + const maxHeight = lineHeight * 8; // 8 lines maximum + + const newHeight = Math.min(textarea.scrollHeight, maxHeight); + textarea.style.height = `${newHeight}px`; + + // Enable/disable scroll based on content height + if (textarea.scrollHeight > maxHeight) { + textarea.style.overflowY = 'auto'; + } else { + textarea.style.overflowY = 'hidden'; + } + } + }, [inputValue, inputRef]); const handleAttachmentClick = () => { setIsUploadModalOpen(true); @@ -59,12 +82,78 @@ const ChatInput: React.FC = ({ onFileRemove(fileId); }; + // Handle Enter key press for sending message (without Shift) + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + if (!isDisabled && (inputValue.trim() || attachedFiles.length > 0)) { + onSend(); + } + } + // Call original onKeyPress if it exists (for backward compatibility) + if (onKeyPress && e.key !== 'Enter') { + onKeyPress(e as any); + } + }; + + // Drag and drop handlers + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (!isDisabled && !isWorkflowRunning) { + setIsDragOver(true); + } + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + // Only set drag over to false if we're leaving the entire input area + if (!e.currentTarget.contains(e.relatedTarget as Node)) { + setIsDragOver(false); + } + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + + if (isDisabled || isWorkflowRunning) { + return; + } + + const files = Array.from(e.dataTransfer.files); + if (files.length > 0) { + // Convert File objects to FileInfo objects + const fileInfos: FileInfo[] = files.map((file, index) => ({ + id: Date.now() + index, // Generate unique IDs + name: file.name, + mimeType: file.type, + size: file.size, + creationDate: new Date().toISOString(), + source: 'user_uploaded' + })); + + onFilesSelect(fileInfos); + } + }; + return ( {/* Show attached files if any */} {attachedFiles.length > 0 && ( @@ -91,15 +180,20 @@ const ChatInput: React.FC = ({ {/* Input row with text input, attachment button, and send button */}
- setInputValue(e.target.value)} - onKeyPress={onKeyPress} + onKeyDown={handleKeyDown} placeholder={placeholder} className={styles.message_input} disabled={isDisabled} + rows={1} + style={{ + resize: 'none', + minHeight: '24px', + lineHeight: '24px' + }} /> {/* Attachment button */} @@ -111,7 +205,7 @@ const ChatInput: React.FC = ({ transition={{ duration: 0.2, ease: "easeOut" }} title="Datei anhängen" > - + {/* Send/Stop button */} diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageItem.tsx b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageItem.tsx index 2a67813..c0bbe85 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageItem.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageItem.tsx @@ -3,6 +3,7 @@ import { FaDownload } from "react-icons/fa"; import { MdOutlineRemoveRedEye } from "react-icons/md"; import { Message, Document } from "./dashboardChatAreaTypes"; import FilePreviewPopup from "./FilePreviewPopup"; +import { useFileDownload } from "../../../../hooks/useWorkflows"; import styles from './DashboardChatArea.module.css'; interface MessageItemProps { @@ -63,15 +64,9 @@ const getFileIcon = (type?: string, ext?: string): string => { const MessageItem: React.FC = ({ message, index }) => { const [previewDocument, setPreviewDocument] = useState(null); const [isPreviewOpen, setIsPreviewOpen] = useState(false); + const { downloadFile, isDownloading, error: downloadError } = useFileDownload(); + - // Debug logging to see if documents are present - console.log('MessageItem rendering:', { - messageId: message.id, - role: message.role, - hasDocuments: !!message.documents, - documentsLength: message.documents?.length || 0, - documents: message.documents - }); const handleDocumentClick = (document: Document) => { // If there's a downloadUrl, use it; otherwise try the url @@ -85,19 +80,14 @@ const MessageItem: React.FC = ({ message, index }) => { const handlePreview = (document: Document, e: React.MouseEvent) => { e.stopPropagation(); - console.log('handlePreview called with document:', document); - console.log('document.id:', document.id, 'document.fileId:', document.fileId); // Use fileId if available, otherwise try to use id as fallback const fileId = document.fileId || document.id; if (!fileId) { - console.error('Neither fileId nor id is available on document:', document); return; } - console.log('Using fileId for preview:', fileId, 'type:', typeof fileId); - setPreviewDocument(document); setIsPreviewOpen(true); }; @@ -107,10 +97,20 @@ const MessageItem: React.FC = ({ message, index }) => { setPreviewDocument(null); }; - const handleDownload = (document: Document, e: React.MouseEvent) => { + const handleDownload = async (document: Document, e: React.MouseEvent) => { e.stopPropagation(); - // TODO: Implement download functionality - console.log('Download document:', document.name); + + // Use fileId if available, otherwise try to use id as fallback + const fileId = document.fileId || document.id; + + if (!fileId) { + return; + } + + // Construct filename with extension if available + const fileName = document.ext ? `${document.name}.${document.ext}` : document.name; + + await downloadFile(fileId, fileName); }; return ( @@ -119,8 +119,7 @@ const MessageItem: React.FC = ({ message, index }) => { className={`${styles.message} ${styles[`message_${message.role}`]}`} >
- {message.role === 'user' ? 'You' : - message.role === 'assistant' ? 'Assistant' : 'System'} + {message.role === 'user' ? 'You' : message.agentName}
{message.content} diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageList.tsx b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageList.tsx index fdd40da..626a752 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageList.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageList.tsx @@ -54,7 +54,7 @@ const MessageList: React.FC = ({ /> )) ) : !currentWorkflowId ? ( -

Start a conversation by typing a message, selecting a prompt or continuing a previous workflow...

+

Beginne ein Gespräch, indem du eine Nachricht eingibst, eine Vorlage auswählst oder einen vorherigen Workflow fortsetzt …

) : null} {/* Spacer to push workflow status to bottom when there are fewer messages */} diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.module.css b/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.module.css index 6a9decb..e290e97 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.module.css +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.module.css @@ -18,7 +18,7 @@ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); max-width: 90vw; height: 90vh; - width: 800px; + width: 80vw; display: flex; flex-direction: column; overflow: hidden; diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.tsx b/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.tsx index f838a0a..549e2d5 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.tsx @@ -73,12 +73,12 @@ const FilePreviewPopup: React.FC = ({ document, isOpen, o ); } - // Use metadata from backend response + // Use metadata from backend response, but prioritize file extension over potentially incorrect MIME type const mimeType = fileMetadata?.mimeType; const isBase64Encoded = fileMetadata?.base64Encoded; const fileExtension = document.ext?.toLowerCase(); - // Check if this is a markdown file by extension/MIME type first + // Check if this is a markdown file by extension first (more reliable than backend MIME type) const isMarkdownByType = fileExtension === 'md' || fileExtension === 'markdown' || mimeType === 'text/markdown' || @@ -110,7 +110,20 @@ const FilePreviewPopup: React.FC = ({ document, isOpen, o previewContent.includes('[') && previewContent.includes('](') // Links ); - const isMarkdown = isMarkdownByType || (mimeType === 'text/plain' && hasMarkdownContent); + // For .txt files or text MIME types, check for markdown content + const isTxtWithMarkdown = (fileExtension === 'txt' || mimeType?.startsWith('text/')) && hasMarkdownContent; + const isMarkdown = isMarkdownByType || isTxtWithMarkdown; + + // Debug logging + console.log('FilePreviewPopup preview detection:', { + fileExtension, + mimeType, + isMarkdownByType, + hasMarkdownContent, + isTxtWithMarkdown, + isMarkdown, + isCodeFile + }); if (mimeType?.startsWith('image/')) { // Image preview @@ -162,8 +175,8 @@ const FilePreviewPopup: React.FC = ({ document, isOpen, o
); - } else if (mimeType?.startsWith('text/') || fileExtension === 'txt') { - // Enhanced text preview for all text files + } else if ((mimeType?.startsWith('text/') || fileExtension === 'txt') && !isMarkdown) { + // Enhanced text preview for text files that are not markdown return (
{previewContent?.split('\n').map((line, index) => { diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea/dashboardChatAreaTypes.ts b/src/components/Dashboard/DashboardChat/DashboardChatArea/dashboardChatAreaTypes.ts index cb33f14..e92faf9 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea/dashboardChatAreaTypes.ts +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea/dashboardChatAreaTypes.ts @@ -23,6 +23,7 @@ export interface Document { export interface Message { id?: string; role: 'user' | 'assistant' | 'system'; + agentName: string; content: string; timestamp?: string; documents?: Document[]; @@ -40,7 +41,7 @@ export interface ChatInputProps { onKeyPress: (e: React.KeyboardEvent) => void; isDisabled: boolean; placeholder: string; - inputRef: React.RefObject; + inputRef: React.RefObject; isWorkflowRunning: boolean; onStopWorkflow: () => void; isStoppingWorkflow: boolean; diff --git a/src/components/Dashboard/DashboardChat/DashboardChatHistory/DashboardChatHistory.tsx b/src/components/Dashboard/DashboardChat/DashboardChatHistory/DashboardChatHistory.tsx index 383dc77..5b284b7 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatHistory/DashboardChatHistory.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatHistory/DashboardChatHistory.tsx @@ -28,7 +28,7 @@ const DashboardChatHistory: React.FC = ({ onWorkflowR transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }} >
-
Loading workflows...
+
Workflows werden geladen...
); @@ -43,7 +43,7 @@ const DashboardChatHistory: React.FC = ({ onWorkflowR transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }} >
-
Error loading workflows: {error}
+
Fehler beim Laden der Workflows: {error}
@@ -66,6 +79,13 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
)}
+ + setIsModalOpen(false)} + onSubmit={handleCreatePrompt} + isLoading={creatingPrompt} + />
); } diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.module.css index 9477ad9..1c06dc1 100644 --- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.module.css +++ b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.module.css @@ -114,6 +114,46 @@ color: var(--color-bg); } +.deleteButton.confirm { + background-color: var(--color-red-disabled); + color: var(--color-red); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.deleteButton.confirm:hover:not(:disabled) { + background-color: var(--color-red-hover); + color: var(--color-bg); +} + +.actionText { + font-size: 12px; + color: var(--color-gray); + animation: pulse 1.5s infinite; + white-space: nowrap; + font-family: var(--font-family); + margin-left: 4px; +} + +.deleteButton.confirm .actionText { + color: var(--color-red); + animation: none; +} + +@keyframes pulse { + 0% { + opacity: 0.6; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.6; + } +} + .promptContent { display: flex; flex-direction: column; diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.tsx index 5d0705e..135c241 100644 --- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.tsx +++ b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.tsx @@ -14,18 +14,26 @@ interface DashboardPromptSetItemProps { function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetItemProps) { const { handlePromptDelete, deletingPrompts, deleteError } = usePromptOperations(); const contentRef = useRef(null); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const isDeleting = deletingPrompts.has(prompt.id); - const handleDelete = async () => { - if (window.confirm(`Möchten Sie den Prompt "${prompt.name}" wirklich löschen?`)) { + const handleDeleteClick = async () => { + if (showDeleteConfirm) { const success = await handlePromptDelete(prompt.id); if (success && onDelete) { onDelete(); } + setShowDeleteConfirm(false); + } else { + setShowDeleteConfirm(true); } }; + const handleCancelDelete = () => { + setShowDeleteConfirm(false); + }; + const handleRun = () => { onRun(prompt); }; @@ -76,12 +84,15 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.module.css new file mode 100644 index 0000000..d999125 --- /dev/null +++ b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.module.css @@ -0,0 +1,192 @@ +.overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + padding: 20px; +} + +.modal { + background: var(--color-bg); + border-radius: 12px; + border: 1px solid var(--color-gray); + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 24px 0 24px; + border-bottom: 1px solid var(--color-gray); + margin-bottom: 20px; +} + +.title { + font-size: 20px; + font-weight: 600; + color: var(--color-text); + margin: 0; +} + +.closeButton { + background: none; + border: none; + padding: 8px; + cursor: pointer; + color: var(--color-text); + border-radius: 6px; + transition: all 0.2s; +} + +.closeButton:hover { + background-color: var(--color-gray-disabled); + color: var(--color-text); +} + +.closeButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.form { + padding: 0 24px 24px 24px; + flex: 1; + overflow-y: auto; +} + +.formGroup { + margin-bottom: 20px; +} + +.label { + display: block; + font-size: 14px; + font-weight: 500; + color: var(--color-text); + margin-bottom: 6px; +} + +.input { + width: 100%; + padding: 12px 16px; + border: 1px solid var(--color-gray); + border-radius: 8px; + font-size: 14px; + transition: border-color 0.2s; + box-sizing: border-box; + background-color: var(--color-bg); + color: var(--color-text); +} + +.input:focus { + outline: none; + border-color: var(--color-secondary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.input:disabled { + background-color: var(--color-gray-disabled); + opacity: 0.7; +} + +.textarea { + width: 100%; + padding: 12px 16px; + border: 1px solid var(--color-gray); + border-radius: 8px; + font-size: 14px; + font-family: inherit; + resize: vertical; + min-height: 120px; + transition: border-color 0.2s; + box-sizing: border-box; + background-color: var(--color-bg); + color: var(--color-text); +} + +.textarea:focus { + outline: none; + border-color: var(--color-secondary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + background-color: var(--color-bg); + color: var(--color-text); +} + +.textarea:disabled { + background-color: var(--color-gray-disabled); + opacity: 0.7; +} + +.error { + background-color: var(--color-red-disabled); + border: 1px solid var(--color-red); + padding: 12px 16px; + border-radius: 8px; + font-size: 14px; + margin-bottom: 20px; +} + +.buttons { + display: flex; + gap: 12px; + justify-content: flex-end; + margin-top: 24px; + padding-top: 20px; + border-top: 1px solid var(--color-gray); +} + +.cancelButton { + padding: 10px 20px; + border: 1px solid var(--color-red); + background: var(--color-red); + color: var(--color-text); + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.cancelButton:hover { + background-color: var(--color-red-hover); + border-color: var(--color-red-hover); +} + +.cancelButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.submitButton { + padding: 10px 20px; + background: var(--color-secondary); + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s; +} + +.submitButton:hover { + background: var(--color-secondary-hover); +} + +.submitButton:disabled { + background: var(--color-secondary-disabled); + cursor: not-allowed; +} \ No newline at end of file diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.tsx new file mode 100644 index 0000000..3e3cd9d --- /dev/null +++ b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.tsx @@ -0,0 +1,127 @@ +import React, { useState } from 'react'; +import { FaTimes } from 'react-icons/fa'; +import styles from './DashboardPromptSetModal.module.css'; + +interface DashboardPromptSetModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (promptData: { name: string; content: string }) => Promise; + isLoading?: boolean; +} + +function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false }: DashboardPromptSetModalProps) { + const [name, setName] = useState(''); + const [content, setContent] = useState(''); + const [error, setError] = useState(null); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!name.trim()) { + setError('Name ist erforderlich'); + return; + } + + if (!content.trim()) { + setError('Inhalt ist erforderlich'); + return; + } + + setError(null); + + try { + await onSubmit({ name: name.trim(), content: content.trim() }); + // Reset form on success + setName(''); + setContent(''); + onClose(); + } catch (err: any) { + setError(err.message || 'Fehler beim Erstellen des Prompts'); + } + }; + + const handleClose = () => { + setName(''); + setContent(''); + setError(null); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+
+

Neuen Prompt erstellen

+ +
+ +
+
+ + setName(e.target.value)} + className={styles.input} + placeholder="Geben Sie einen Namen für den Prompt ein" + disabled={isLoading} + maxLength={100} + /> +
+ +
+ +