fix: delete insights + placeholder cursor insertion (Issue #44)

BUG #44: Analysen löschen schlug fehl (kein Auth-Token)
FIX:
- api.deleteInsight() in api.js hinzugefügt
- Analysis.jsx nutzt jetzt api.js mit Error-Handling
- Nicht mehr raw fetch() ohne Token

BUG: Platzhalter wurden am Ende eingefügt statt an Cursor-Position
FIX:
- useRef für baseTemplateRef hinzugefügt
- Cursor-Position tracking (onClick + onKeyUp)
- Insert at cursor: template.slice(0, pos) + placeholder + template.slice(pos)
- Focus + Cursor-Position nach Insert wiederhergestellt

version: 9.5.2 (bugfix)
module: prompts 2.0.2, insights 1.3.1
This commit is contained in:
Lars 2026-03-26 11:40:19 +01:00
parent 7daa2e40c7
commit c56d2b2201
3 changed files with 29 additions and 10 deletions

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react' import { useState, useEffect, useRef } from 'react'
import { api } from '../utils/api' import { api } from '../utils/api'
import { X, Plus, Trash2, MoveUp, MoveDown, Code } from 'lucide-react' import { X, Plus, Trash2, MoveUp, MoveDown, Code } from 'lucide-react'
import PlaceholderPicker from './PlaceholderPicker' import PlaceholderPicker from './PlaceholderPicker'
@ -39,6 +39,8 @@ export default function UnifiedPromptModal({ prompt, onSave, onClose }) {
const [error, setError] = useState(null) const [error, setError] = useState(null)
const [showPlaceholderPicker, setShowPlaceholderPicker] = useState(false) const [showPlaceholderPicker, setShowPlaceholderPicker] = useState(false)
const [pickerTarget, setPickerTarget] = useState(null) // 'base' or {stage, promptIdx} const [pickerTarget, setPickerTarget] = useState(null) // 'base' or {stage, promptIdx}
const [cursorPosition, setCursorPosition] = useState(null) // Track cursor position for insertion
const baseTemplateRef = useRef(null)
// Test functionality // Test functionality
const [testing, setTesting] = useState(false) const [testing, setTesting] = useState(false)
@ -514,9 +516,12 @@ export default function UnifiedPromptModal({ prompt, onSave, onClose }) {
<div style={{ marginBottom: 24 }}> <div style={{ marginBottom: 24 }}>
<h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 12 }}>Template</h3> <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 12 }}>Template</h3>
<textarea <textarea
ref={baseTemplateRef}
className="form-input" className="form-input"
value={template} value={template}
onChange={e => setTemplate(e.target.value)} onChange={e => setTemplate(e.target.value)}
onClick={e => setCursorPosition(e.target.selectionStart)}
onKeyUp={e => setCursorPosition(e.target.selectionStart)}
rows={12} rows={12}
placeholder="Prompt-Template mit {{placeholders}}..." placeholder="Prompt-Template mit {{placeholders}}..."
style={{ width: '100%', textAlign: 'left', resize: 'vertical', fontFamily: 'monospace', fontSize: 12 }} style={{ width: '100%', textAlign: 'left', resize: 'vertical', fontFamily: 'monospace', fontSize: 12 }}
@ -799,10 +804,22 @@ export default function UnifiedPromptModal({ prompt, onSave, onClose }) {
<PlaceholderPicker <PlaceholderPicker
onSelect={(placeholder) => { onSelect={(placeholder) => {
if (pickerTarget === 'base') { if (pickerTarget === 'base') {
// Insert into base template // Insert into base template at cursor position
setTemplate(template + placeholder) const pos = cursorPosition ?? template.length
const newTemplate = template.slice(0, pos) + placeholder + template.slice(pos)
setTemplate(newTemplate)
// Restore focus and cursor position after insertion
setTimeout(() => {
if (baseTemplateRef.current) {
baseTemplateRef.current.focus()
const newPos = pos + placeholder.length
baseTemplateRef.current.setSelectionRange(newPos, newPos)
setCursorPosition(newPos)
}
}, 0)
} else if (pickerTarget && typeof pickerTarget === 'object') { } else if (pickerTarget && typeof pickerTarget === 'object') {
// Insert into pipeline stage template // Insert into pipeline stage template (append at end for now)
const { stage: stageNum, promptIdx } = pickerTarget const { stage: stageNum, promptIdx } = pickerTarget
setStages(stages.map(s => { setStages(stages.map(s => {
if (s.stage === stageNum) { if (s.stage === stageNum) {

View File

@ -103,12 +103,13 @@ export default function Analysis() {
const deleteInsight = async (id) => { const deleteInsight = async (id) => {
if (!confirm('Analyse löschen?')) return if (!confirm('Analyse löschen?')) return
const pid = localStorage.getItem('bodytrack_active_profile')||'' try {
await fetch(`/api/insights/${id}`, { await api.deleteInsight(id)
method:'DELETE', headers: pid ? {'X-Profile-Id':pid} : {}
})
if (newResult?.id === id) setNewResult(null) if (newResult?.id === id) setNewResult(null)
await loadAll() await loadAll()
} catch (e) {
setError('Löschen fehlgeschlagen: ' + e.message)
}
} }
// Group insights by scope for history view // Group insights by scope for history view

View File

@ -107,6 +107,7 @@ export const api = {
insightPipeline: () => req('/insights/pipeline',{method:'POST'}), insightPipeline: () => req('/insights/pipeline',{method:'POST'}),
listInsights: () => req('/insights'), listInsights: () => req('/insights'),
latestInsights: () => req('/insights/latest'), latestInsights: () => req('/insights/latest'),
deleteInsight: (id) => req(`/insights/${id}`, {method:'DELETE'}),
exportZip: async () => { exportZip: async () => {
const res = await fetch(`${BASE}/export/zip`, {headers: hdrs()}) const res = await fetch(`${BASE}/export/zip`, {headers: hdrs()})
if (!res.ok) throw new Error('Export failed') if (!res.ok) throw new Error('Export failed')