Skip to main content
Glama
SystemPromptPanel.jsx15.9 kB
import React, { useState, useEffect } from 'react' import { systemPrompts } from '../services/api' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Plus, Copy, MessageSquare, Edit, Save, Hash } from 'lucide-react' // Simple token estimation function (roughly 4 characters per token) const estimateTokens = (text) => { if (!text) return 0 return Math.ceil(text.length / 4) } const SystemPromptPanel = ({ client, copyToClipboard }) => { const [prompts, setPrompts] = useState([]) const [currentPrompt, setCurrentPrompt] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [showGenerateModal, setShowGenerateModal] = useState(false) const [generateLoading, setGenerateLoading] = useState(false) const [userRequirements, setUserRequirements] = useState('') const [generatedPrompt, setGeneratedPrompt] = useState('') const [isRevision, setIsRevision] = useState(false) const [parentVersionId, setParentVersionId] = useState(null) const [isEditingMain, setIsEditingMain] = useState(false) const [editedMainPrompt, setEditedMainPrompt] = useState('') useEffect(() => { loadPrompts() }, [client.id]) const loadPrompts = async () => { try { setLoading(true) const promptList = await systemPrompts.list(client.id) setPrompts(promptList) // Find current prompt (marked as active in backend for display purposes) const current = promptList.find(p => p.is_active) if (current) { const currentDetail = await systemPrompts.get(current.id) setCurrentPrompt(currentDetail) setEditedMainPrompt(currentDetail.prompt_text) } else { setCurrentPrompt(null) setEditedMainPrompt('') } setIsEditingMain(false) } catch (err) { setError(err.message) } finally { setLoading(false) } } const handleGenerate = async () => { if (!userRequirements.trim()) { setError('Please provide requirements for the system prompt') return } try { setGenerateLoading(true) setError('') const response = await systemPrompts.generate(client.id, { user_requirements: userRequirements, is_revision: isRevision, parent_version_id: parentVersionId }) setGeneratedPrompt(response.prompt_text) } catch (err) { setError(err.message) } finally { setGenerateLoading(false) } } const handleSave = async () => { if (!generatedPrompt.trim()) { setError('No prompt to save') return } try { setLoading(true) setError('') const savedPrompt = await systemPrompts.save(client.id, { prompt_text: generatedPrompt, user_requirements: userRequirements, generation_context: { generated_at: new Date().toISOString(), is_revision: isRevision, parent_version_id: parentVersionId }, parent_version_id: parentVersionId }) // Set the new prompt as current (for display purposes) await systemPrompts.setActive(client.id, savedPrompt.id) // Close modal and reload prompts setShowGenerateModal(false) setUserRequirements('') setGeneratedPrompt('') setIsRevision(false) setParentVersionId(null) await loadPrompts() } catch (err) { setError(err.message) } finally { setLoading(false) } } const handleVersionChange = async (promptId) => { try { setLoading(true) await systemPrompts.setActive(client.id, parseInt(promptId)) await loadPrompts() } catch (err) { setError(err.message) } finally { setLoading(false) } } const handleRevise = () => { if (!currentPrompt) return setIsRevision(true) setParentVersionId(currentPrompt.id) setUserRequirements('') setGeneratedPrompt('') setShowGenerateModal(true) } const handleCopyPrompt = () => { if (currentPrompt) { copyToClipboard(currentPrompt.prompt_text, "System prompt copied to clipboard") } } const handleEditMain = () => { setIsEditingMain(true) setEditedMainPrompt(currentPrompt?.prompt_text || '') } const handleSaveMainEdit = async () => { if (!editedMainPrompt.trim() || !currentPrompt) { setError('No prompt content to save') return } try { setLoading(true) setError('') // Save as a new version with the edited content const savedPrompt = await systemPrompts.save(client.id, { prompt_text: editedMainPrompt, user_requirements: `Edited version of v${currentPrompt.version}`, generation_context: { edited_at: new Date().toISOString(), is_edit: true, parent_version_id: currentPrompt.id }, parent_version_id: currentPrompt.id }) // Set as current await systemPrompts.setActive(client.id, savedPrompt.id) // Reload and exit edit mode await loadPrompts() } catch (err) { setError(err.message) } finally { setLoading(false) } } const handleCancelMainEdit = () => { setIsEditingMain(false) setEditedMainPrompt(currentPrompt?.prompt_text || '') setError('') } return ( <Card> <CardHeader className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> <div> <CardTitle className="flex items-center text-lg sm:text-xl"> <MessageSquare className="mr-2 h-4 sm:h-5 w-4 sm:w-5" /> Suggested System Prompt </CardTitle> <CardDescription className="text-sm"> Turn enabled tools and resources into a prompt </CardDescription> </div> <div className="flex gap-2"> {currentPrompt && ( <Button variant="outline" size="sm" onClick={handleRevise} disabled={loading} > <Edit className="mr-2 h-4 w-4" /> Revise </Button> )} <Button onClick={() => setShowGenerateModal(true)} disabled={loading} size="sm" > <Plus className="mr-2 h-4 w-4" /> Generate </Button> </div> </CardHeader> <CardContent> {error && ( <div className="mb-4 p-3 bg-destructive/15 text-destructive text-sm rounded-md"> {error} </div> )} {loading && <div className="text-center py-4 text-sm text-muted-foreground">Loading...</div>} {/* Current Prompt Display */} {currentPrompt && ( <div className="space-y-4"> <div className="flex items-center justify-between"> <div className="flex items-center gap-2"> <h4 className="font-medium">Prompt</h4> <span className="px-2 py-1 text-xs bg-muted rounded"> v{currentPrompt.version} </span> </div> <div className="flex gap-2"> {!isEditingMain ? ( <> <Button variant="outline" size="sm" onClick={handleEditMain} disabled={loading} > <Edit className="h-4 w-4 mr-2" /> Edit </Button> <Button variant="outline" size="sm" onClick={handleCopyPrompt} > <Copy className="h-4 w-4 mr-2" /> Copy </Button> </> ) : ( <> <Button variant="outline" size="sm" onClick={handleCancelMainEdit} disabled={loading} > Cancel </Button> <Button size="sm" onClick={handleSaveMainEdit} disabled={loading || !editedMainPrompt.trim()} > <Save className="h-4 w-4 mr-2" /> Save </Button> </> )} </div> </div> {isEditingMain ? ( <textarea value={editedMainPrompt} onChange={(e) => setEditedMainPrompt(e.target.value)} className="flex min-h-48 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 font-mono" style={{ resize: 'vertical', minHeight: '12rem' }} placeholder="Edit your system prompt..." /> ) : ( <textarea value={currentPrompt.prompt_text} readOnly className="flex min-h-48 w-full rounded-md border border-input bg-muted px-3 py-2 text-sm shadow-sm font-mono whitespace-pre-wrap cursor-default" style={{ resize: 'vertical', minHeight: '12rem' }} /> )} <div className="flex items-center justify-between text-xs text-muted-foreground"> <span>Created: {new Date(currentPrompt.created_at).toLocaleString()}</span> <div className="flex items-center gap-1"> <Hash className="h-3 w-3" /> <span>~{estimateTokens(currentPrompt.prompt_text).toLocaleString()} tokens</span> </div> </div> {/* Version Selector */} {prompts.length > 1 && !isEditingMain && ( <div className="space-y-2"> <Label htmlFor="version-select" className="text-sm font-medium"> Switch Version </Label> <Select value={currentPrompt.id.toString()} onValueChange={handleVersionChange} disabled={loading} > <SelectTrigger> <SelectValue placeholder="Select version..." /> </SelectTrigger> <SelectContent> {prompts.map((prompt) => ( <SelectItem key={prompt.id} value={prompt.id.toString()}> <div className="flex items-center gap-2"> <span>Version {prompt.version}</span> <span className="text-xs text-muted-foreground"> {new Date(prompt.created_at).toLocaleDateString()} </span> </div> </SelectItem> ))} </SelectContent> </Select> </div> )} </div> )} {/* No Prompts State */} {!currentPrompt && !loading && ( <div className="text-center py-8 text-muted-foreground"> <MessageSquare className="mx-auto h-12 w-12 mb-4 opacity-50" /> <p className="text-sm">No system prompts yet.</p> <p className="text-xs mt-2">Generate your first system prompt to get started.</p> </div> )} </CardContent> {/* Generate Modal */} <Dialog open={showGenerateModal} onOpenChange={setShowGenerateModal}> <DialogContent className="w-[95vw] max-w-2xl max-h-[90vh] overflow-y-auto"> <DialogHeader> <DialogTitle> {isRevision ? 'Revise System Prompt' : 'Generate System Prompt'} </DialogTitle> <DialogDescription> {isRevision ? 'Describe how you want to improve the current system prompt.' : 'Describe what you want the AI assistant to do and how it should behave.' } </DialogDescription> </DialogHeader> <div className="space-y-4"> <div> <Label htmlFor="requirements">Requirements & Description</Label> <Textarea id="requirements" value={userRequirements} onChange={(e) => setUserRequirements(e.target.value)} placeholder="Describe what you want the AI assistant to do, how it should behave, and any specific requirements..." rows={4} className="mt-2" disabled={generateLoading} /> </div> {generatedPrompt && ( <div> <div className="flex items-center justify-between mb-2"> <Label>Generated Prompt (Editable)</Label> <div className="flex items-center gap-1 text-xs text-muted-foreground"> <Hash className="h-3 w-3" /> <span>~{estimateTokens(generatedPrompt).toLocaleString()} tokens</span> </div> </div> <Textarea value={generatedPrompt} onChange={(e) => setGeneratedPrompt(e.target.value)} className="mt-2 min-h-64 font-mono text-sm" placeholder="Your generated prompt will appear here..." /> </div> )} {error && ( <div className="p-3 bg-destructive/15 text-destructive text-sm rounded-md"> {error} </div> )} </div> <DialogFooter className="flex-col sm:flex-row gap-2"> <Button variant="outline" onClick={() => { setShowGenerateModal(false) setUserRequirements('') setGeneratedPrompt('') setIsRevision(false) setParentVersionId(null) setError('') }} > Cancel </Button> {!generatedPrompt && ( <Button onClick={handleGenerate} disabled={generateLoading || !userRequirements.trim()} > {generateLoading ? 'Generating...' : 'Generate'} </Button> )} {generatedPrompt && ( <> <Button variant="outline" onClick={handleGenerate} disabled={generateLoading} > {generateLoading ? 'Regenerating...' : 'Regenerate'} </Button> <Button onClick={handleSave} disabled={loading} > <Save className="h-4 w-4 mr-2" /> {loading ? 'Saving...' : 'Save'} </Button> </> )} </DialogFooter> </DialogContent> </Dialog> </Card> ) } export default SystemPromptPanel

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GeorgeStrakhov/mcpeasy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server