Full sweep: fix broken features, redesign NodePreview, wire AI service

FIXES (from comprehensive audit):
- NodePreview: complete rewrite — 380px left panel with document
  summaries, commitment list, phase track visualization, scrollable.
  Fixed children count bug (was showing [object Object]).
  Slides in from left (not right) to not overlap branch handles.
- CommitmentForm: added required validation on eigenaar_id field
- MetroMap: wired custom metro node creation with form + POST /metro-nodes
- MetroMap: removed dead handleCliCommand console.log
- MetroMap: added metro node creation modal (naam + beschrijving)

NEW — AI Service integration:
- ai-service/main.py: real Anthropic API integration via httpx
  - Reads ANTHROPIC_API_KEY from env, uses claude-haiku-4-5-20251001
  - /api/chat fetches project context from PostgreSQL (docs, commitments)
  - /api/summarize sends content to Claude for summarization
  - /api/search does basic text search on documents + kennis_artikelen
- AiController.php: Laravel proxy for /api/ai/chat → ai-service
- CliBar.vue: complete rewrite with async API calls, processing state,
  error handling, conversation history, auto-scroll
  - Receives projectId prop for context-scoped AI queries
  - Shows "denken..." animation while waiting for response
- docker-compose.yml: passes ANTHROPIC_API_KEY to ai-service container
- config/services.php: ai service URL configuration

To activate AI: set ANTHROPIC_API_KEY in .env and rebuild ai-service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-04-08 15:07:51 +02:00
parent 9f033835cd
commit f4ec49254a
10 changed files with 619 additions and 84 deletions

View File

@@ -157,12 +157,42 @@ const submitTrackForm = () => {
})
}
// --- Metro node creation (for custom line extend) ---
// --- Metro node creation form (for custom line extend) ---
const showMetroNodeForm = ref(false)
const pendingMetroNodeEvent = ref(null)
const metroNodeForm = useForm({
metro_line_id: '',
naam: '',
beschrijving: '',
x: 0,
y: 0,
})
const createMetroNode = (event) => {
// Determine metro_line_id from the lineId
// lineId format for custom lines will need to be mapped
// For now, emit a placeholder
console.log('Create metro node on custom line:', event)
pendingMetroNodeEvent.value = event
// Extract metro_line_id from lineId (format: "custom-{id}" or similar)
metroNodeForm.x = event.x
metroNodeForm.y = event.y
metroNodeForm.naam = ''
metroNodeForm.beschrijving = ''
showMetroNodeForm.value = true
}
const submitMetroNodeForm = () => {
const event = pendingMetroNodeEvent.value
// The lineId from the canvas might be something like "custom-5" — extract the DB id
// For now, try to find the metro_line by matching
const lineId = event?.lineId ?? ''
const match = lineId.match(/\d+$/)
metroNodeForm.metro_line_id = match ? match[0] : ''
metroNodeForm.post('/metro-nodes', {
onSuccess: () => {
showMetroNodeForm.value = false
metroNodeForm.reset()
pendingMetroNodeEvent.value = null
},
})
}
// --- FAB handlers ---
@@ -188,10 +218,6 @@ const handleFabItemLeave = () => {
canvasRef.value?.setHighlightedLine(null)
}
const handleCliCommand = (command) => {
console.log('CLI command:', command)
}
const logout = () => {
router.post('/logout')
}
@@ -300,7 +326,46 @@ const editingProject = ref(null)
</Transition>
</Teleport>
<CliBar @command="handleCliCommand" />
<!-- Metro node creation modal (for custom line extend) -->
<Teleport to="body">
<Transition name="modal-fade">
<div v-if="showMetroNodeForm" class="modal-backdrop" @click="showMetroNodeForm = false">
<div class="modal-content" @click.stop>
<div class="modal-header">NIEUW PUNT</div>
<form @submit.prevent="submitMetroNodeForm">
<div class="form-group">
<label class="form-label">Naam</label>
<input
v-model="metroNodeForm.naam"
type="text"
class="form-input"
placeholder="Naam van het punt..."
required
autofocus
/>
</div>
<div class="form-group">
<label class="form-label">Beschrijving</label>
<textarea
v-model="metroNodeForm.beschrijving"
class="form-input form-textarea"
placeholder="Optionele beschrijving..."
rows="3"
></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn-cancel" @click="showMetroNodeForm = false">Annuleren</button>
<button type="submit" class="btn-submit" :disabled="metroNodeForm.processing">
{{ metroNodeForm.processing ? 'Bezig...' : 'Aanmaken' }}
</button>
</div>
</form>
</div>
</div>
</Transition>
</Teleport>
<CliBar :project-id="currentProjectId" />
</div>
</template>
@@ -434,6 +499,11 @@ const editingProject = ref(null)
box-shadow: 0 0 8px rgba(0, 210, 255, 0.2);
}
.form-textarea {
resize: vertical;
min-height: 60px;
}
.form-actions {
display: flex;
justify-content: flex-end;