diff --git a/DELETE_DOCUMENTS_GUIDE.md b/DELETE_DOCUMENTS_GUIDE.md new file mode 100644 index 0000000..adcfe2c --- /dev/null +++ b/DELETE_DOCUMENTS_GUIDE.md @@ -0,0 +1,205 @@ +# Deleting Documents from Code-Forge MCP + +**When you need to remove previously uploaded documents from a knowledge base.** + +--- + +## Method 1: API Endpoint (Recommended) + +The MCP server now supports deleting documents by filename: + +```bash +# Delete a single document by filename +curl -X DELETE "http://localhost:8051/api/kb/{kb_name}/documents/{filename}" + +# Example: Delete api.md from Pistn Agent OS +curl -X DELETE "http://localhost:8051/api/kb/Pistn%20Agent%20OS/documents/api.md" + +# Response on success: +{ + "success": true, + "message": "Deleted 1 document(s)", + "filename": "api.md", + "kb_name": "Pistn Agent OS" +} +``` + +### Notes +- Replace `{kb_name}` with the actual KB name (use URL encoding for spaces: `%20`) +- The filename should match exactly what was uploaded +- Deleting a document removes it and all its chunks from the vector database + +--- + +## Method 2: Manual PostgreSQL Query (Alternative) + +If the API endpoint has issues, you can delete documents directly from PostgreSQL: + +```sql +-- Connect to codeforge database +psql -h localhost -U $USER -d codeforge + +-- Find documents to delete +SELECT id, metadata->>'filename' as filename, kb_id +FROM documents +WHERE metadata->>'filename' LIKE '%api.md%' +LIMIT 10; + +-- Delete specific document(s) +DELETE FROM documents +WHERE kb_id = ( + SELECT id FROM knowledge_bases WHERE name = 'Pistn Agent OS' +) +AND metadata->>'filename' = 'api.md'; + +-- Verify deletion +SELECT COUNT(*) FROM documents +WHERE kb_id = ( + SELECT id FROM knowledge_bases WHERE name = 'Pistn Agent OS' +); +``` + +--- + +## Method 3: Delete All Documents in a KB (Nuclear Option) + +**WARNING**: This deletes everything. Use with caution. + +```bash +# Delete entire KB and recreate it fresh +curl -X DELETE "http://localhost:8051/api/kb/Pistn%20Agent%20OS" +``` + +Or via PostgreSQL: + +```sql +-- Delete KB and all documents +DELETE FROM documents +WHERE kb_id = ( + SELECT id FROM knowledge_bases WHERE name = 'Pistn Agent OS' +); + +DELETE FROM knowledge_bases WHERE name = 'Pistn Agent OS'; + +-- Recreate the KB fresh +INSERT INTO knowledge_bases (name, kb_type, slug) +VALUES ('Pistn Agent OS', 'AGENT_OS', 'pistn-agent-os'); +``` + +--- + +## Common Scenarios + +### Scenario 1: Remove Old Specs Before Re-importing + +```bash +# Delete all spec documents +for spec_file in "spec.md" "requirements.md" "IMPLEMENTATION.md"; do + curl -X DELETE "http://localhost:8051/api/kb/Pistn%20Agent%20OS/documents/$spec_file" +done + +# Then re-import fresh versions +curl -X POST "http://localhost:8051/api/kb/Pistn%20Agent%20OS/import?directory_path=%2FUsers%2Fiamanmp%2FProjects%2Fpistn%2Fagent-os" +``` + +### Scenario 2: Clean Up Old Documentation Before Adding New + +```bash +# List what's in the KB +curl -s "http://localhost:8051/api/kb/Pistn%20Agent%20OS/documents" | python3 -c " +import sys, json +docs = json.load(sys.stdin).get('documents', []) +for doc in docs: + print(f'{doc[\"filename\"]}')" + +# Delete specific old docs one by one +``` + +### Scenario 3: Fresh Start (Delete Everything) + +```bash +# Delete the entire KB +curl -X DELETE "http://localhost:8051/api/kb/Pistn%20Agent%20OS" + +# Recreate it +curl -X POST "http://localhost:8051/api/kb" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Pistn Agent OS", + "kb_type": "agent-os", + "description": "Pistn Agent OS code" + }' + +# Re-import everything fresh +curl -X POST "http://localhost:8051/api/kb/Pistn%20Agent%20OS/import?directory_path=%2FUsers%2Fiamanmp%2FProjects%2Fpistn%2Fagent-os" +``` + +--- + +## Troubleshooting + +### "Document not found" error + +The filename might not match exactly. To find the correct filename: + +```bash +# List all documents in KB +curl -s "http://localhost:8051/api/kb/Pistn%20Agent%20OS/documents" | python3 -m json.tool +``` + +### Delete endpoint returns "Not Found" + +The endpoint might not have loaded properly. Restart the MCP server: + +```bash +# Kill existing server +pkill -f "uvicorn.*8051" + +# Restart +source /Users/iamanmp/Projects/code-forge/venv/bin/activate +python /Users/iamanmp/Projects/code-forge/mcp_server/server.py > /tmp/mcp_server.log 2>&1 & + +# Wait for startup +sleep 3 + +# Test +curl -s "http://localhost:8051/health" | python3 -m json.tool +``` + +### PostgreSQL connection issues + +Make sure PostgreSQL is running: + +```bash +# Check PostgreSQL +brew services list | grep postgresql + +# Start if stopped +brew services start postgresql@16 + +# Test connection +psql -h localhost -U $USER -d codeforge -c "SELECT 1;" +``` + +--- + +## UI Delete Functionality + +The web UI at http://localhost:5173 currently doesn't have a delete button. + +**To add it** (optional enhancement): +1. The frontend would need a "Delete" button in the document list +2. This would call the new `DELETE /api/kb/{kb_name}/documents/{filename}` endpoint +3. See `frontend/src/components/KBManagement.tsx` for where to add this + +For now, use the API or PostgreSQL methods above. + +--- + +## Coming Soon + +- [ ] UI delete button in Knowledge Base Management +- [ ] Bulk delete by date range (delete all docs older than X days) +- [ ] Archive vs Delete (keep metadata but remove embeddings) +- [ ] Delete confirmation dialog with document preview + diff --git a/frontend/src/components/KBManagement.tsx b/frontend/src/components/KBManagement.tsx index e8da46f..68286ed 100644 --- a/frontend/src/components/KBManagement.tsx +++ b/frontend/src/components/KBManagement.tsx @@ -1,8 +1,8 @@ import { useState, useRef } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { motion } from 'framer-motion'; -import { FileText, BarChart3, Clock, Database, Upload, CheckCircle, XCircle, Info, Copy, Link } from 'lucide-react'; -import { getKBStats, listDocuments, uploadDocument, type KBStats } from '../lib/api'; +import { FileText, BarChart3, Clock, Database, Upload, CheckCircle, XCircle, Info, Copy, Link, Trash2 } from 'lucide-react'; +import { getKBStats, listDocuments, uploadDocument, deleteDocument, type KBStats } from '../lib/api'; interface KBManagementProps { kbName: string; @@ -17,6 +17,7 @@ export default function KBManagement({ kbName, kbSlug, kbType }: KBManagementPro const [uploadProgress, setUploadProgress] = useState({ current: 0, total: 0 }); const [isBatchUpload, setIsBatchUpload] = useState(false); const [copiedMCP, setCopiedMCP] = useState(false); + const [deleteConfirm, setDeleteConfirm] = useState<{ filename: string } | null>(null); const fileInputRef = useRef(null); const queryClient = useQueryClient(); @@ -73,6 +74,24 @@ export default function KBManagement({ kbName, kbSlug, kbType }: KBManagementPro }, }); + // Delete mutation + const deleteMutation = useMutation({ + mutationFn: (filename: string) => deleteDocument(kbName, filename), + onSuccess: (data) => { + setUploadStatus('success'); + setUploadMessage(`✅ Deleted ${data.filename}`); + setDeleteConfirm(null); + queryClient.invalidateQueries({ queryKey: ['kb-stats', kbName] }); + queryClient.invalidateQueries({ queryKey: ['kb-documents', kbName] }); + setTimeout(() => setUploadStatus('idle'), 3000); + }, + onError: (error: any) => { + setUploadStatus('error'); + setUploadMessage(`❌ Delete failed: ${error.response?.data?.detail || error.message}`); + setTimeout(() => setUploadStatus('idle'), 5000); + }, + }); + const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files) { setSelectedFiles(Array.from(e.target.files)); @@ -411,8 +430,18 @@ export default function KBManagement({ kbName, kbSlug, kbType }: KBManagementPro initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} transition={{ delay: idx * 0.05 }} - className="p-5 bg-gradient-to-br from-cool-blue/10 to-electric-teal/5 border-2 border-electric-teal/30 rounded-xl hover:border-electric-teal/60 hover:shadow-xl hover:shadow-electric-teal/20 hover:-translate-y-1 transition-all duration-300" + className="p-5 bg-gradient-to-br from-cool-blue/10 to-electric-teal/5 border-2 border-electric-teal/30 rounded-xl hover:border-electric-teal/60 hover:shadow-xl hover:shadow-electric-teal/20 hover:-translate-y-1 transition-all duration-300 relative group" > + {/* Delete Button */} + +
{/* Large emoji icon */}
{icon}
@@ -452,6 +481,57 @@ export default function KBManagement({ kbName, kbSlug, kbType }: KBManagementPro
)} + + {/* Delete Confirmation Modal */} + {deleteConfirm && ( + setDeleteConfirm(null)} + > + e.stopPropagation()} + className="bg-deep-night border-2 border-blaze-orange/50 rounded-xl p-6 max-w-sm mx-4" + > +
+
+ +
+
+

Delete Document?

+

This action cannot be undone

+
+
+ +
+

+ File: {deleteConfirm.filename} +

+
+ +
+ + +
+
+
+ )} ); } diff --git a/mcp_server/server.py b/mcp_server/server.py index 9175d18..8d3a7ff 100644 --- a/mcp_server/server.py +++ b/mcp_server/server.py @@ -646,6 +646,63 @@ async def api_import_directory(kb_name: str, directory_path: str): raise HTTPException(status_code=500, detail=str(e)) +@app.delete("/api/kb/{kb_name}/documents/{filename}") +async def api_delete_document(kb_name: str, filename: str): + """REST API: Delete a document from a knowledge base by filename.""" + pg_manager = get_pg_manager() + + # Validate KB exists + if not pg_manager.collection_exists(kb_name): + raise HTTPException(status_code=404, detail=f"Knowledge base '{kb_name}' not found") + + try: + # Get the collection ID + conn = pg_manager.get_connection() + try: + with conn.cursor() as cur: + # Get collection ID + cur.execute( + "SELECT id FROM knowledge_bases WHERE name = %s", + (kb_name,) + ) + result = cur.fetchone() + if not result: + raise HTTPException(status_code=404, detail=f"Knowledge base '{kb_name}' not found") + + collection_id = result[0] + + # Delete documents with this filename + cur.execute( + """ + DELETE FROM documents + WHERE kb_id = %s AND metadata->>'filename' = %s + RETURNING id + """, + (collection_id, filename) + ) + deleted_count = len(cur.fetchall()) + conn.commit() + + if deleted_count == 0: + raise HTTPException(status_code=404, detail=f"No documents found with filename '{filename}'") + + logger.info(f"Deleted {deleted_count} document(s) with filename '{filename}' from KB '{kb_name}'") + return { + "success": True, + "message": f"Deleted {deleted_count} document(s)", + "filename": filename, + "kb_name": kb_name + } + finally: + pg_manager.return_connection(conn) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to delete document: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + @app.get("/api/ollama/models") async def api_list_ollama_models(): """REST API: List available Ollama models."""