diff --git a/backend/app/api/v1/prompt_templates.py b/backend/app/api/v1/prompt_templates.py index df41d31..2f58a65 100644 --- a/backend/app/api/v1/prompt_templates.py +++ b/backend/app/api/v1/prompt_templates.py @@ -428,4 +428,98 @@ Please improve this prompt to make it more effective for a {request.chatbot_type raise except Exception as e: log_api_request("improve_prompt_with_ai_error", {"error": str(e), "user_id": user_id}) - raise HTTPException(status_code=500, detail=f"Failed to improve prompt: {str(e)}") \ No newline at end of file + raise HTTPException(status_code=500, detail=f"Failed to improve prompt: {str(e)}") + + +@router.post("/seed-defaults") +async def seed_default_templates( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) +): + """Seed default prompt templates for all chatbot types""" + user_id = current_user.get("id") if isinstance(current_user, dict) else current_user.id + log_api_request("seed_default_templates", {"user_id": user_id}) + + # Define default prompts (same as in reset) + default_prompts = { + "assistant": { + "name": "General Assistant", + "description": "A helpful, accurate, and friendly AI assistant", + "prompt": "You are a helpful AI assistant. Provide accurate, concise, and friendly responses. Always aim to be helpful while being honest about your limitations. When you don't know something, say so clearly. Be professional but approachable in your communication style." + }, + "customer_support": { + "name": "Customer Support Agent", + "description": "Professional customer service representative focused on solving problems", + "prompt": "You are a professional customer support representative. Be empathetic, professional, and solution-focused in all interactions. Always try to understand the customer's issue fully before providing solutions. Use the knowledge base to provide accurate information. When you cannot resolve an issue, explain clearly how the customer can escalate or get further help. Maintain a helpful and patient tone even in difficult situations." + }, + "teacher": { + "name": "Educational Tutor", + "description": "Patient and encouraging educational facilitator", + "prompt": "You are an experienced educational tutor and learning facilitator. Break down complex concepts into understandable, digestible parts. Use analogies, examples, and step-by-step explanations to help students learn. Encourage critical thinking through thoughtful questions. Be patient, supportive, and encouraging. Adapt your teaching style to different learning preferences. When a student makes mistakes, guide them to the correct answer rather than just providing it." + }, + "researcher": { + "name": "Research Assistant", + "description": "Thorough researcher focused on evidence-based information", + "prompt": "You are a thorough research assistant with a focus on accuracy and evidence-based information. Provide well-researched, factual information with sources when possible. Be thorough in your analysis and present multiple perspectives when relevant topics have different viewpoints. Always distinguish between established facts, current research, and opinions. When information is uncertain or contested, clearly communicate the level of confidence and supporting evidence." + }, + "creative_writer": { + "name": "Creative Writing Mentor", + "description": "Imaginative storytelling expert and writing coach", + "prompt": "You are an experienced creative writing mentor and storytelling expert. Help with brainstorming ideas, character development, plot structure, dialogue, and creative expression. Be imaginative and inspiring while providing constructive, actionable feedback. Encourage experimentation with different writing styles and techniques. When reviewing work, balance praise for strengths with specific suggestions for improvement. Help writers find their unique voice while mastering fundamental storytelling principles." + }, + "custom": { + "name": "Custom Chatbot", + "description": "Customizable AI assistant with user-defined behavior", + "prompt": "You are a helpful AI assistant. Your personality, expertise, and behavior will be defined by the user through custom instructions. Follow the user's guidance on how to respond, what tone to use, and what role to play. Be adaptable and responsive to the specific needs and preferences outlined in your configuration." + } + } + + created_templates = [] + updated_templates = [] + + try: + for type_key, template_data in default_prompts.items(): + # Check if template already exists + existing = await db.execute( + select(PromptTemplate).where(PromptTemplate.type_key == type_key) + ) + existing_template = existing.scalar_one_or_none() + + if existing_template: + # Only update if it's still the default (version 1) + if existing_template.version == 1 and existing_template.is_default: + existing_template.name = template_data["name"] + existing_template.description = template_data["description"] + existing_template.system_prompt = template_data["prompt"] + existing_template.updated_at = datetime.utcnow() + updated_templates.append(type_key) + else: + # Create new template + new_template = PromptTemplate( + id=str(uuid.uuid4()), + name=template_data["name"], + type_key=type_key, + description=template_data["description"], + system_prompt=template_data["prompt"], + is_default=True, + is_active=True, + version=1, + created_at=datetime.utcnow(), + updated_at=datetime.utcnow() + ) + db.add(new_template) + created_templates.append(type_key) + + await db.commit() + + return { + "message": "Default templates seeded successfully", + "created": created_templates, + "updated": updated_templates, + "total": len(created_templates) + len(updated_templates) + } + + except Exception as e: + await db.rollback() + log_api_request("seed_default_templates_error", {"error": str(e), "user_id": user_id}) + raise HTTPException(status_code=500, detail=f"Failed to seed default templates: {str(e)}") \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ed8b930..8210ba9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -name: enclava - services: # Nginx reverse proxy - Main application entry point enclava-nginx: diff --git a/frontend/src/app/prompt-templates/page.tsx b/frontend/src/app/prompt-templates/page.tsx index c299113..7d33758 100644 --- a/frontend/src/app/prompt-templates/page.tsx +++ b/frontend/src/app/prompt-templates/page.tsx @@ -31,6 +31,7 @@ import { Edit3, RotateCcw, Loader2, Save, AlertTriangle, Plus, Sparkles } from ' import toast from 'react-hot-toast' import { apiClient } from '@/lib/api-client' import { config } from '@/lib/config' +import { useAuth } from '@/contexts/AuthContext' interface PromptTemplate { id: string @@ -54,6 +55,7 @@ interface PromptVariable { } export default function PromptTemplatesPage() { + const { user, isAuthenticated } = useAuth() const [templates, setTemplates] = useState([]) const [variables, setVariables] = useState([]) const [loading, setLoading] = useState(true) @@ -62,6 +64,7 @@ export default function PromptTemplatesPage() { const [showCreateDialog, setShowCreateDialog] = useState(false) const [saving, setSaving] = useState(false) const [improvingWithAI, setImprovingWithAI] = useState(false) + const [mounted, setMounted] = useState(false) // Form state for editing const [editForm, setEditForm] = useState({ @@ -93,34 +96,58 @@ export default function PromptTemplatesPage() { { value: "custom", label: "Custom Chatbot" }, ] + // Fix hydration mismatch useEffect(() => { - loadData() + setMounted(true) }, []) + useEffect(() => { + if (mounted && isAuthenticated) { + loadData() + } else if (mounted) { + setLoading(false) + } + }, [mounted, isAuthenticated]) + const loadData = async () => { try { setLoading(true) - // Get auth token - const token = localStorage.getItem('token') - if (!token) { - throw new Error('No authentication token found') - } - - // Load templates and variables in parallel + // Load templates and variables in parallel using apiClient which handles auth automatically const [templatesResult, variablesResult] = await Promise.allSettled([ apiClient.get('/api-internal/v1/prompt-templates/templates'), apiClient.get('/api-internal/v1/prompt-templates/variables') ]) - if (templatesResult.status === 'rejected' || variablesResult.status === 'rejected') { - throw new Error('Failed to load data') + if (templatesResult.status === 'rejected') { + console.error('Failed to load templates:', templatesResult.reason) + throw new Error('Failed to load templates') + } + + if (variablesResult.status === 'rejected') { + console.error('Failed to load variables:', variablesResult.reason) + throw new Error('Failed to load variables') } - setTemplates(templatesResult.value) + const loadedTemplates = templatesResult.value + setTemplates(loadedTemplates) setVariables(variablesResult.value) + + // If no templates exist, seed the defaults + if (loadedTemplates.length === 0) { + try { + await apiClient.post('/api-internal/v1/prompt-templates/seed-defaults', {}) + // Reload templates after seeding + const newTemplates = await apiClient.get('/api-internal/v1/prompt-templates/templates') + setTemplates(newTemplates) + toast.success('Default prompt templates created successfully') + } catch (error) { + console.error('Failed to seed default templates:', error) + } + } } catch (error) { - toast.error('Failed to load prompt templates') + console.error('Error loading prompt templates:', error) + toast.error(error instanceof Error ? error.message : 'Failed to load prompt templates') } finally { setLoading(false) } @@ -193,13 +220,7 @@ export default function PromptTemplatesPage() { try { setSaving(true) - // Get auth token - const token = localStorage.getItem('token') - if (!token) { - throw new Error('No authentication token found') - } - - const newTemplate = await apiClient.post('/api-internal/v1/prompt-templates/create', { + const newTemplate = await apiClient.post('/api-internal/v1/prompt-templates/templates/create', { name: finalForm.name, type_key: finalForm.type_key, description: finalForm.description, @@ -235,12 +256,6 @@ export default function PromptTemplatesPage() { try { setImprovingWithAI(true) - // Get auth token - const token = localStorage.getItem('token') - if (!token) { - throw new Error('No authentication token found') - } - const result = await apiClient.post('/api-internal/v1/prompt-templates/improve', { current_prompt: currentPrompt, chatbot_type: chatbotType, @@ -275,6 +290,32 @@ export default function PromptTemplatesPage() { return displayNames[typeKey] || typeKey } + // Prevent hydration mismatch by not rendering dynamic content until mounted + if (!mounted) { + return ( +
+
+
+
+
+ ) + } + + // Check authentication after mounting + if (!isAuthenticated) { + return ( +
+ + +

+ Please log in to access prompt templates. +

+
+
+
+ ) + } + if (loading) { return (
diff --git a/frontend/src/components/chatbot/ChatInterface.tsx b/frontend/src/components/chatbot/ChatInterface.tsx index 0d321b1..42c6468 100644 --- a/frontend/src/components/chatbot/ChatInterface.tsx +++ b/frontend/src/components/chatbot/ChatInterface.tsx @@ -1,6 +1,7 @@ "use client" import { useState, useRef, useEffect, useCallback, useMemo } from "react" +import "./ChatInterface.css" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -135,8 +136,8 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface }, []) return ( - - + +
@@ -161,10 +162,10 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface >
{messages.length === 0 && ( -
- -

Start a conversation with your chatbot!

-

Type a message below to begin.

+
+ +

Start a conversation with your chatbot!

+

Type a message below to begin.

)} @@ -172,24 +173,25 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
-
+
{message.role === 'user' ? ( ) : ( - + )}
-
+
{message.role === 'user' ? (
{message.content}
) : ( { const isInline = !className; return isInline ? ( - + {children} ) : ( - + {children} ) }, pre: ({ children }) => ( -
+                                  
                                     {children}
                                   
), @@ -235,7 +237,7 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface {/* Sources for assistant messages */} {message.role === 'assistant' && message.sources && message.sources.length > 0 && (
-

Sources:

+

Sources:

{message.sources.map((source, index) => ( @@ -246,7 +248,7 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
)} -
+
{formatTime(message.timestamp)}
-

+

Press Enter to send, Shift+Enter for new line. Maximum 4000 characters.

diff --git a/frontend/src/components/chatbot/ChatbotManager.tsx b/frontend/src/components/chatbot/ChatbotManager.tsx index b9ac6f9..9d4512f 100644 --- a/frontend/src/components/chatbot/ChatbotManager.tsx +++ b/frontend/src/components/chatbot/ChatbotManager.tsx @@ -237,8 +237,26 @@ export function ChatbotManager() { const loadPromptTemplates = async () => { try { const templates = await apiClient.get('/api-internal/v1/prompt-templates/templates') - setPromptTemplates(templates) + + // If no templates exist, seed the defaults + if (templates.length === 0) { + try { + await apiClient.post('/api-internal/v1/prompt-templates/seed-defaults', {}) + // Reload templates after seeding + const newTemplates = await apiClient.get('/api-internal/v1/prompt-templates/templates') + setPromptTemplates(newTemplates) + toast({ + title: "Templates Initialized", + description: "Default prompt templates have been created" + }) + } catch (error) { + console.error('Failed to seed default templates:', error) + } + } else { + setPromptTemplates(templates) + } } catch (error) { + console.error('Failed to load prompt templates:', error) } } diff --git a/frontend/src/components/plugins/PluginManager.tsx b/frontend/src/components/plugins/PluginManager.tsx index 529d2f2..9a58e10 100644 --- a/frontend/src/components/plugins/PluginManager.tsx +++ b/frontend/src/components/plugins/PluginManager.tsx @@ -217,7 +217,7 @@ const AvailablePluginCard: React.FC = ({ plugin, onIns }; export const PluginManager: React.FC = () => { - const { user, token } = useAuth(); + const { user, isAuthenticated } = useAuth(); const { installedPlugins, availablePlugins, @@ -246,17 +246,17 @@ export const PluginManager: React.FC = () => { // Load initial data only when authenticated useEffect(() => { - if (user && token) { + if (isAuthenticated && user) { refreshInstalledPlugins(); } - }, [user, token, refreshInstalledPlugins]); + }, [isAuthenticated, user, refreshInstalledPlugins]); // Load available plugins when switching to discover tab and authenticated useEffect(() => { - if (activeTab === 'discover' && user && token) { + if (activeTab === 'discover' && isAuthenticated && user) { searchAvailablePlugins(); } - }, [activeTab, user, token, searchAvailablePlugins]); + }, [activeTab, isAuthenticated, user, searchAvailablePlugins]); const handlePluginAction = async (action: string, plugin: PluginInfo) => { try { @@ -306,7 +306,7 @@ export const PluginManager: React.FC = () => { const categories = Array.from(new Set(availablePlugins.map(p => p.category))); // Show authentication required message if not authenticated (client-side only) - if (isClient && (!user || !token)) { + if (isClient && !isAuthenticated) { return (
@@ -319,13 +319,12 @@ export const PluginManager: React.FC = () => { ); } - // Show loading state during hydration + // Prevent hydration mismatch by showing minimal UI during SSR if (!isClient) { return (
- - Loading... +
);