diff --git a/.plans/package3_enhancement_plan.md b/.plans/package3_enhancement_plan.md index 58d948d..943a102 100644 --- a/.plans/package3_enhancement_plan.md +++ b/.plans/package3_enhancement_plan.md @@ -2,7 +2,7 @@ **Source**: User request (2026-04-25) **Scope**: System Prompt Configuration Page + Query History Page -**Status**: 🔧 In Progress (3.1 ✅, 3.2 ✅, 3.3 in progress) +**Status**: 🔧 In Progress (3.1 ✅, 3.2 ✅, 3.3 ✅, next: 3.4) --- diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bce1186..2675053 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,6 +5,7 @@ import { ErrorBoundary } from './components/ErrorBoundary' import { NavBar } from './components/NavBar' import { LTTPage } from './pages/LTTPage' import { RAGDatabasePage } from './pages/RAGDatabasePage' +import { SystemPromptsPage } from './pages/SystemPromptsPage' import { PdfViewerPage } from './pages/PdfViewerPage' export default function App(): JSX.Element { @@ -21,6 +22,7 @@ export default function App(): JSX.Element { } /> } /> + } /> diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index acabc5e..84977ae 100644 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -29,6 +29,18 @@ export const NavBar: React.FC = () => { > RAG Database + + `text-sm font-medium transition-colors ${ + isActive + ? 'text-gray-900 border-b-2 border-gray-900' + : 'text-gray-500 hover:text-gray-700 border-b-2 border-transparent' + }` + } + > + System Prompts + ) diff --git a/frontend/src/components/PlaceholderDocs.tsx b/frontend/src/components/PlaceholderDocs.tsx new file mode 100644 index 0000000..e51b28f --- /dev/null +++ b/frontend/src/components/PlaceholderDocs.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react' +import { Info, ChevronDown, ChevronUp } from 'lucide-react' + +interface PlaceholderRow { + placeholder: string + description: string +} + +const PLACEHOLDERS: PlaceholderRow[] = [ + { placeholder: '{question}', description: 'The user\'s input question' }, + { placeholder: '{chunks}', description: 'Retrieved document chunks (filter step only)' }, + { placeholder: '{context}', description: 'Formatted chunks with citations (generate step only)' }, +] + +export const PlaceholderDocs: React.FC = () => { + const [isOpen, setIsOpen] = useState(true) + + return ( +
+ + + {isOpen && ( +
+

+ Use these placeholders in your prompt templates. They will be replaced with actual values at runtime. +

+
+ {PLACEHOLDERS.map((row) => ( +
+ + {row.placeholder} + + {row.description} +
+ ))} +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/ProfileList.tsx b/frontend/src/components/ProfileList.tsx new file mode 100644 index 0000000..ffd0ade --- /dev/null +++ b/frontend/src/components/ProfileList.tsx @@ -0,0 +1,63 @@ +import React from 'react' +import { Pencil } from 'lucide-react' +import type { PromptProfile } from '../types' + +export interface ProfileListProps { + profiles: PromptProfile[] + selectedProfile: string | null + onSelect: (name: string) => void +} + +export const ProfileList: React.FC = ({ profiles, selectedProfile, onSelect }) => { + return ( +
+ {profiles.map((profile) => { + const isActive = profile.is_active + const isSelected = selectedProfile === profile.name + + return ( +
+
+
+ + Profile {profile.name} +
+ {isActive && ( + + Active + + )} +
+ + +
+ ) + })} +
+ ) +} diff --git a/frontend/src/components/PromptEditor.tsx b/frontend/src/components/PromptEditor.tsx new file mode 100644 index 0000000..0dfc702 --- /dev/null +++ b/frontend/src/components/PromptEditor.tsx @@ -0,0 +1,165 @@ +import React, { useState, useEffect, useCallback } from 'react' +import { RotateCcw, AlertTriangle } from 'lucide-react' + +export interface PromptEditorProps { + profileName: string + prompts: Record + isSaving: boolean + onUpdate: (step: string, template: string) => void + onSave: () => void + onResetStep: (step: string) => void + onResetAll: () => void + onCancel: () => void +} + +const STEPS = [ + { key: 'decompose', label: 'Step 1: Query Decomposition', placeholders: ['question'] }, + { key: 'filter', label: 'Step 2: Relevance Filtering', placeholders: ['question', 'chunks'] }, + { key: 'generate', label: 'Step 3: Response Generation', placeholders: ['question', 'context'] }, +] as const + +const VALID_PLACEHOLDERS: Record = { + decompose: ['{question}'], + filter: ['{question}', '{chunks}'], + generate: ['{question}', '{context}'], +} + +const findUnknownPlaceholders = (template: string, stepKey: string): string[] => { + const valid = VALID_PLACEHOLDERS[stepKey] ?? [] + const found = template.match(/\{[a-zA-Z_]+\}/g) ?? [] + return found.filter((p) => !valid.includes(p)) +} + +export const PromptEditor: React.FC = ({ + profileName, + prompts, + isSaving, + onUpdate, + onSave, + onResetStep, + onResetAll, + onCancel, +}) => { + const [localPrompts, setLocalPrompts] = useState>({}) + + useEffect(() => { + setLocalPrompts({ ...prompts }) + }, [prompts]) + + const handleChange = (stepKey: string, value: string) => { + setLocalPrompts((prev) => ({ ...prev, [stepKey]: value })) + onUpdate(stepKey, value) + } + + const handleResetAll = () => { + if (window.confirm('Reset all prompts to defaults? This cannot be undone.')) { + onResetAll() + } + } + + const handleResetStep = (stepKey: string) => { + if (window.confirm(`Reset "${STEPS.find((s) => s.key === stepKey)?.label}" to default?`)) { + onResetStep(stepKey) + } + } + + const isDirty = useCallback(() => { + return STEPS.some((step) => localPrompts[step.key] !== prompts[step.key]) + }, [localPrompts, prompts]) + + return ( +
+
+ Editing Profile + {profileName} +
+ + {STEPS.map((step) => { + const value = localPrompts[step.key] ?? '' + const unknownPlaceholders = findUnknownPlaceholders(value, step.key) + const placeholderBadges = step.placeholders.map((p) => `{${p}}`).join(', ') + + return ( +
+
+
+ + +
+
+ +
+ Placeholders: + {step.placeholders.map((p) => ( + + {'{'}{p}{'}'} + + ))} +
+ +