lwazi 1.8.6 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lwazi",
3
- "version": "1.8.6",
3
+ "version": "1.9.4",
4
4
  "description": "Lwazi is an AI assistant for Laravel. Install with one command to add an AI assistant to your Laravel app.",
5
5
  "main": "bin/lwazi.js",
6
6
  "bin": {
@@ -0,0 +1,206 @@
1
+ <?php
2
+
3
+ namespace Lwazi\Core\Console;
4
+
5
+ use Illuminate\Console\Command;
6
+ use Lwazi\Core\Services\LwaziLogger;
7
+
8
+ class LogsCommand extends Command
9
+ {
10
+ protected $signature = 'lwazi:logs
11
+ {action=show : Action to perform (show, clear, trace, tail, watch)}
12
+ {trace_id? : Trace ID to show details}
13
+ {--limit=20 : Number of recent traces to show}
14
+ {--interval=2 : Refresh interval in seconds for tail/watch}';
15
+
16
+ protected $description = 'View and manage Lwazi trace logs for debugging';
17
+
18
+ public function handle()
19
+ {
20
+ $action = $this->argument('action');
21
+
22
+ match ($action) {
23
+ 'show' => $this->showLogs(),
24
+ 'list' => $this->showLogs(),
25
+ 'trace' => $this->showTrace($this->argument('trace_id')),
26
+ 'clear' => $this->clearLogs(),
27
+ 'info' => $this->showLogInfo(),
28
+ 'tail' => $this->tailLogs(),
29
+ 'watch' => $this->tailLogs(),
30
+ default => $this->showHelp(),
31
+ };
32
+ }
33
+
34
+ protected function showLogs(): void
35
+ {
36
+ $limit = (int) $this->option('limit');
37
+ $traces = LwaziLogger::getRecentTraces($limit);
38
+
39
+ if (empty($traces)) {
40
+ $this->warn('No traces found. Make sure the Lwazi chatbot has been used.');
41
+ $this->info('Log path: ' . LwaziLogger::getLogPath());
42
+ return;
43
+ }
44
+
45
+ $this->info("Recent Traces ({$limit} most recent)");
46
+ $this->line(str_repeat('=', 80));
47
+
48
+ foreach ($traces as $trace) {
49
+ $id = $trace['id'] ?? 'unknown';
50
+ $message = $trace['message'] ?? 'no message';
51
+ $duration = round($trace['duration_ms'] ?? 0, 2);
52
+ $spans = count($trace['spans'] ?? []);
53
+ $events = count($trace['events'] ?? []);
54
+ $startTime = $trace['start_human'] ?? 'unknown';
55
+ $endTime = $trace['end_human'] ?? 'incomplete';
56
+
57
+ $this->line("");
58
+ $this->line("<fg=yellow>ID:</> {$id}");
59
+ $this->line("<fg=cyan>Time:</> {$startTime} → {$endTime}");
60
+ $this->line("<fg=cyan>Duration:</> {$duration}ms");
61
+ $this->line("<fg=cyan>Message:</> " . substr($message, 0, 60) . (strlen($message) > 60 ? '...' : ''));
62
+ $this->line("<fg=cyan>Spans:</> {$spans} | <fg=cyan>Events:</> {$events}");
63
+
64
+ if (!empty($trace['response'])) {
65
+ $response = substr($trace['response'], 0, 100);
66
+ $this->line("<fg=cyan>Response:</> " . $response . (strlen($trace['response']) > 100 ? '...' : ''));
67
+ }
68
+
69
+ $this->line("<fg=gray>View details:</> php artisan lwazi:logs trace {$id}");
70
+ }
71
+
72
+ $this->line("");
73
+ $this->line(str_repeat('=', 80));
74
+ $this->info("Total traces: " . count($traces));
75
+ $this->info("Log path: " . LwaziLogger::getLogPath());
76
+ }
77
+
78
+ protected function showTrace(?string $traceId): void
79
+ {
80
+ if (!$traceId) {
81
+ $this->error('Please provide a trace ID: php artisan lwazi:logs trace <trace_id>');
82
+ return;
83
+ }
84
+
85
+ $trace = LwaziLogger::getTrace($traceId);
86
+
87
+ if (!$trace) {
88
+ $this->error("Trace not found: {$traceId}");
89
+ $this->info("Available traces:");
90
+ $recent = LwaziLogger::getRecentTraces(5);
91
+ foreach ($recent as $t) {
92
+ $this->line(" - {$t['id']}: " . substr($t['message'] ?? '', 0, 50));
93
+ }
94
+ return;
95
+ }
96
+
97
+ $output = LwaziLogger::formatTraceForDebug($traceId);
98
+ $this->line($output);
99
+ }
100
+
101
+ protected function clearLogs(): void
102
+ {
103
+ if (!$this->confirm('Are you sure you want to clear all Lwazi trace logs?')) {
104
+ $this->warn('Cancelled.');
105
+ return;
106
+ }
107
+
108
+ LwaziLogger::clearTraces();
109
+ $this->info('All trace logs cleared.');
110
+ }
111
+
112
+ protected function showLogInfo(): void
113
+ {
114
+ $this->info('Lwazi Trace Logging System');
115
+ $this->line(str_repeat('=', 50));
116
+ $this->line("");
117
+ $this->line("<fg=cyan>Log Path:</> " . LwaziLogger::getLogPath());
118
+ $this->line("<fg=cyan>Enabled:</> " . (LwaziLogger::isEnabled() ? '<fg=green>Yes' : '<fg=red>No'));
119
+
120
+ $traces = LwaziLogger::getRecentTraces(100);
121
+ $this->line("<fg=cyan>Total Traces:</> " . count($traces));
122
+
123
+ $totalDuration = 0;
124
+ foreach ($traces as $t) {
125
+ $totalDuration += $t['duration_ms'] ?? 0;
126
+ }
127
+ $avgDuration = count($traces) > 0 ? round($totalDuration / count($traces), 2) : 0;
128
+ $this->line("<fg=cyan>Avg Duration:</> {$avgDuration}ms");
129
+
130
+ $this->line("");
131
+ $this->line('Commands:');
132
+ $this->line(" <fg=yellow>php artisan lwazi:logs</> Show recent traces");
133
+ $this->line(" <fg=yellow>php artisan lwazi:logs show</> Show recent traces");
134
+ $this->line(" <fg=yellow>php artisan lwazi:logs show --limit=50</> Show more traces");
135
+ $this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
136
+ $this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
137
+ $this->line(" <fg=yellow>php artisan lwazi:logs info</> Show this info");
138
+ $this->line("");
139
+ $this->line('Real-time SSE endpoints:');
140
+ $this->line(" <fg=cyan>GET /lwazi/logs/stream</> Live stream all traces");
141
+ $this->line(" <fg=cyan>GET /lwazi/logs/stream/{id}</> Live stream specific trace");
142
+ }
143
+
144
+ protected function showHelp(): void
145
+ {
146
+ $this->info('Lwazi Logs Command');
147
+ $this->line(str_repeat('=', 50));
148
+ $this->line("");
149
+ $this->line('Usage:');
150
+ $this->line(" <fg=yellow>php artisan lwazi:logs</> Show recent traces");
151
+ $this->line(" <fg=yellow>php artisan lwazi:logs show</> Show recent traces");
152
+ $this->line(" <fg=yellow>php artisan lwazi:logs show --limit=50</> Show more traces");
153
+ $this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
154
+ $this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
155
+ $this->line(" <fg=yellow>php artisan lwazi:logs info</> Show log info");
156
+ $this->line(" <fg=yellow>php artisan lwazi:logs tail</> Live tail new traces");
157
+ $this->line(" <fg=yellow>php artisan lwazi:logs tail --interval=1</> Faster refresh");
158
+ $this->line("");
159
+ $this->line('The trace logs show the flow of a message through the Lwazi system,');
160
+ $this->line('including intent detection, data fetching, and response generation.');
161
+ }
162
+
163
+ protected function tailLogs(): void
164
+ {
165
+ $interval = (int) $this->option('interval');
166
+ $lastCount = 0;
167
+ $lastTraceId = null;
168
+
169
+ $this->info("Live tailing Lwazi traces (Ctrl+C to stop)");
170
+ $this->info("Refresh interval: {$interval}s");
171
+ $this->line(str_repeat('=', 80));
172
+
173
+ while (true) {
174
+ $traces = LwaziLogger::getRecentTraces(10);
175
+ $currentCount = count($traces);
176
+
177
+ if ($currentCount !== $lastCount || ($traces && $traces[0]['id'] ?? null) !== $lastTraceId) {
178
+ $this->newLine();
179
+ $this->info("[" . date('H:i:s') . "] New activity detected ({$currentCount} traces)");
180
+
181
+ foreach ($traces as $trace) {
182
+ $id = $trace['id'] ?? 'unknown';
183
+ $message = substr($trace['message'] ?? 'no message', 0, 50);
184
+ $duration = round($trace['duration_ms'] ?? 0, 2);
185
+ $spans = count($trace['spans'] ?? []);
186
+ $endTime = $trace['end_human'] ?? 'running';
187
+
188
+ $this->line(" <fg=yellow>ID:</> {$id}");
189
+ $this->line(" <fg=cyan>Message:</> {$message}");
190
+ $this->line(" <fg=cyan>Duration:</> {$duration}ms | Spans: {$spans} | Ended: {$endTime}");
191
+
192
+ if (!empty($trace['response'])) {
193
+ $response = substr($trace['response'], 0, 80);
194
+ $this->line(" <fg=green>Response:</> {$response}...");
195
+ }
196
+ $this->line("");
197
+ }
198
+
199
+ $lastCount = $currentCount;
200
+ $lastTraceId = $traces[0]['id'] ?? null;
201
+ }
202
+
203
+ sleep($interval);
204
+ }
205
+ }
206
+ }
@@ -0,0 +1,122 @@
1
+ <?php
2
+
3
+ namespace Lwazi\Core\Http\Controllers;
4
+
5
+ use Illuminate\Routing\Controller;
6
+ use Illuminate\Http\Request;
7
+ use Lwazi\Core\Services\LwaziLogger;
8
+ use Illuminate\Support\Facades\Response;
9
+
10
+ class LogsStreamController extends Controller
11
+ {
12
+ protected array $seenTraceIds = [];
13
+
14
+ public function stream(Request $request): \Symfony\Component\HttpFoundation\StreamedResponse
15
+ {
16
+ $lastCount = 0;
17
+
18
+ return Response::stream(function () {
19
+ $seenTraceIds = [];
20
+ $lastCount = 0;
21
+
22
+ while (true) {
23
+ $traces = LwaziLogger::getRecentTraces(50);
24
+ $currentCount = count($traces);
25
+
26
+ $newTraces = array_filter($traces, function($trace) use (&$seenTraceIds) {
27
+ $id = $trace['id'] ?? null;
28
+ if ($id && !in_array($id, $seenTraceIds)) {
29
+ $seenTraceIds[] = $id;
30
+ return true;
31
+ }
32
+ return false;
33
+ });
34
+
35
+ $updatedTraces = [];
36
+ foreach ($traces as $trace) {
37
+ $id = $trace['id'] ?? null;
38
+ if ($id && isset($trace['end_time'])) {
39
+ $key = "updated_{$id}";
40
+ if (!in_array($key, $seenTraceIds)) {
41
+ $seenTraceIds[] = $key;
42
+ $updatedTraces[] = $trace;
43
+ }
44
+ }
45
+ }
46
+
47
+ if (!empty($newTraces) || !empty($updatedTraces) || $currentCount !== $lastCount) {
48
+ $payload = [
49
+ 'type' => 'update',
50
+ 'timestamp' => date('Y-m-d H:i:s'),
51
+ 'traces' => array_values(array_merge(
52
+ array_map(fn($t) => array_merge($t, ['_event' => 'new']), $newTraces),
53
+ array_map(fn($t) => array_merge($t, ['_event' => 'completed']), $updatedTraces)
54
+ )),
55
+ 'stats' => [
56
+ 'total' => $currentCount,
57
+ ],
58
+ ];
59
+
60
+ echo "data: " . json_encode($payload) . "\n\n";
61
+ ob_flush();
62
+ flush();
63
+
64
+ $lastCount = $currentCount;
65
+ }
66
+
67
+ echo ": heartbeat\n\n";
68
+ ob_flush();
69
+ flush();
70
+
71
+ sleep(2);
72
+ }
73
+ }, 200, [
74
+ 'Content-Type' => 'text/event-stream',
75
+ 'Cache-Control' => 'no-cache',
76
+ 'Connection' => 'keep-alive',
77
+ 'X-Accel-Buffering' => 'no',
78
+ ]);
79
+ }
80
+
81
+ public function trace(string $traceId): \Symfony\Component\HttpFoundation\StreamedResponse
82
+ {
83
+ return Response::stream(function () use ($traceId) {
84
+ $lastData = null;
85
+
86
+ while (true) {
87
+ $trace = LwaziLogger::getTrace($traceId);
88
+
89
+ if ($trace && $trace !== $lastData) {
90
+ echo "data: " . json_encode([
91
+ 'type' => 'trace_update',
92
+ 'trace' => $trace,
93
+ ]) . "\n\n";
94
+ ob_flush();
95
+ flush();
96
+ $lastData = $trace;
97
+ }
98
+
99
+ if ($trace && isset($trace['end_time'])) {
100
+ echo "data: " . json_encode([
101
+ 'type' => 'trace_complete',
102
+ 'trace' => $trace,
103
+ ]) . "\n\n";
104
+ ob_flush();
105
+ flush();
106
+ break;
107
+ }
108
+
109
+ echo ": heartbeat\n\n";
110
+ ob_flush();
111
+ flush();
112
+
113
+ sleep(1);
114
+ }
115
+ }, 200, [
116
+ 'Content-Type' => 'text/event-stream',
117
+ 'Cache-Control' => 'no-cache',
118
+ 'Connection' => 'keep-alive',
119
+ 'X-Accel-Buffering' => 'no',
120
+ ]);
121
+ }
122
+ }
@@ -4,7 +4,7 @@ namespace Lwazi\Core\Installer;
4
4
 
5
5
  use Illuminate\Support\Facades\DB;
6
6
  use Lwazi\Core\Support\ModelScanner;
7
- use Lwazi\Core\Services\NavigationTree;
7
+ use Lwazi\Core\Services\NavigationGraph;
8
8
 
9
9
  class ProjectAnalyzer
10
10
  {
@@ -41,7 +41,7 @@ class ProjectAnalyzer
41
41
  $this->analyzePages();
42
42
  $this->analyzeRoutes();
43
43
  $this->analyzeInformationArchitecture();
44
- $this->buildNavigationTree();
44
+ $this->buildNavigationGraph();
45
45
  $this->analyzeProjectType();
46
46
  $this->generateSummary();
47
47
 
@@ -329,11 +329,11 @@ class ProjectAnalyzer
329
329
  $this->log('IA pages: ' . count($iaPages) . ', links: ' . count($iaLinks));
330
330
  }
331
331
 
332
- protected function buildNavigationTree(): void
332
+ protected function buildNavigationGraph(): void
333
333
  {
334
- $this->log('Building navigation tree...');
334
+ $this->log('Building navigation graph...');
335
335
 
336
- $tree = new NavigationTree();
336
+ $graph = new NavigationGraph();
337
337
 
338
338
  $pages = $this->knowledge['pages'] ?? [];
339
339
  $routes = $this->knowledge['routes'] ?? [];
@@ -352,13 +352,13 @@ class ProjectAnalyzer
352
352
  return true;
353
353
  });
354
354
 
355
- $tree->buildFromPages($pages);
356
- $tree->buildFromRoutes(array_values($publicRoutes));
355
+ $graph->buildFromPages($pages);
356
+ $graph->buildFromRoutes(array_values($publicRoutes));
357
357
 
358
- $this->knowledge['navigation_tree'] = $tree->toArray();
358
+ $this->knowledge['navigation_graph'] = $graph->toArray();
359
359
 
360
- $flatCount = count($tree->getFlatIndex());
361
- $this->log("Navigation tree built with $flatCount indexed paths");
360
+ $flatCount = count($graph->getFlatIndex());
361
+ $this->log("Navigation graph built with $flatCount indexed paths");
362
362
  }
363
363
 
364
364
  protected function normalizePath(string $href): ?string
@@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider;
6
6
  use Illuminate\Support\Facades\Route;
7
7
  use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
8
8
  use Lwazi\Core\Services\LwaziService;
9
+ use Lwazi\Core\Services\LwaziLogger;
9
10
  use Lwazi\Core\Tools\ToolRegistry;
10
11
  use Lwazi\Core\Rag\RagService;
11
12
  use Lwazi\Core\Services\KnowledgeBaseGenerator;
@@ -14,6 +15,7 @@ use Lwazi\Core\Console\AnalyzeProjectCommand;
14
15
  use Lwazi\Core\Console\LwaziIngestCommand;
15
16
  use Lwazi\Core\Console\SetupCommand;
16
17
  use Lwazi\Core\Console\BuildContentIndexCommand;
18
+ use Lwazi\Core\Console\LogsCommand;
17
19
  use Lwazi\Core\Http\Middleware\InjectLwaziChat;
18
20
 
19
21
  class LwaziServiceProvider extends ServiceProvider
@@ -38,17 +40,22 @@ class LwaziServiceProvider extends ServiceProvider
38
40
  SetupCommand::class,
39
41
  LwaziIngestCommand::class,
40
42
  BuildContentIndexCommand::class,
43
+ LogsCommand::class,
41
44
  ]);
42
45
  }
43
46
  }
44
47
 
45
48
  public function boot(): void
46
49
  {
50
+ new LwaziLogger();
51
+
47
52
  $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'lwazi');
48
53
 
49
54
  Route::prefix(config('lwazi.route_prefix', 'lwazi'))->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class])->group(function () {
50
55
  Route::post('/chat', \Lwazi\Core\Http\Controllers\ChatController::class . '@chat');
51
56
  Route::get('/status', \Lwazi\Core\Http\Controllers\ChatController::class . '@status');
57
+ Route::get('/logs/stream', \Lwazi\Core\Http\Controllers\LogsStreamController::class . '@stream');
58
+ Route::get('/logs/stream/{traceId}', \Lwazi\Core\Http\Controllers\LogsStreamController::class . '@trace');
52
59
  });
53
60
 
54
61
  $this->publishes(
@@ -2,8 +2,9 @@
2
2
 
3
3
  namespace Lwazi\Core\Rag;
4
4
 
5
- use Lwazi\Core\Services\NavigationTree;
5
+ use Lwazi\Core\Services\NavigationGraph;
6
6
  use Lwazi\Core\Services\LwaziService;
7
+ use Lwazi\Core\Services\LwaziLogger;
7
8
  use Lwazi\Core\Services\EmbeddingService;
8
9
  use Illuminate\Support\Facades\Http;
9
10
 
@@ -11,7 +12,7 @@ class RagService
11
12
  {
12
13
  protected array $projectKnowledge = [];
13
14
  protected string $knowledgePath;
14
- protected ?NavigationTree $navigationTree = null;
15
+ protected ?NavigationGraph $navigationGraph = null;
15
16
  protected ?EmbeddingService $embeddingService = null;
16
17
 
17
18
  public function __construct()
@@ -35,34 +36,87 @@ class RagService
35
36
  }
36
37
  }
37
38
 
38
- public function getNavigationTree(): ?NavigationTree
39
+ public function getNavigationGraph(): ?NavigationGraph
39
40
  {
40
- if ($this->navigationTree !== null) {
41
- return $this->navigationTree;
41
+ if ($this->navigationGraph !== null) {
42
+ return $this->navigationGraph;
42
43
  }
43
44
 
44
- $treeData = $this->projectKnowledge['navigation_tree'] ?? null;
45
- if (!$treeData) {
45
+ $graphData = $this->projectKnowledge['navigation_graph'] ?? null;
46
+ if (!$graphData) {
46
47
  return null;
47
48
  }
48
49
 
49
- $this->navigationTree = NavigationTree::fromArray($treeData);
50
- return $this->navigationTree;
50
+ $this->navigationGraph = NavigationGraph::fromArray($graphData);
51
+ return $this->navigationGraph;
51
52
  }
52
53
 
53
54
  public function findPath(string $query): ?array
54
55
  {
55
- $tree = $this->getNavigationTree();
56
- if (!$tree) {
56
+ LwaziLogger::step('rag_findPath_start', [
57
+ 'query' => $query,
58
+ 'query_length' => strlen($query),
59
+ ]);
60
+
61
+ LwaziLogger::trackInput('LwaziService', [
62
+ 'method' => 'findPath',
63
+ 'query' => $query,
64
+ ]);
65
+
66
+ $graph = $this->getNavigationGraph();
67
+ if (!$graph) {
68
+ LwaziLogger::step('rag_no_graph');
69
+ LwaziLogger::trackOutput('RagService', [
70
+ 'result' => null,
71
+ 'reason' => 'no navigation graph available',
72
+ ]);
57
73
  return null;
58
74
  }
59
75
 
60
- $result = $tree->search($query);
61
-
62
- if (!$result) {
63
- $result = $tree->searchWithStemming($query);
76
+ $flatIndex = $graph->getFlatIndex();
77
+ $nodeCount = count($flatIndex);
78
+ $nodePaths = array_keys($flatIndex);
79
+
80
+ LwaziLogger::step('rag_graph_loaded', [
81
+ 'node_count' => $nodeCount,
82
+ 'available_paths' => $nodePaths,
83
+ ]);
84
+
85
+ LwaziLogger::trackDataFlow(
86
+ 'RagService',
87
+ 'NavigationGraph::search',
88
+ 'search graph',
89
+ ['query' => $query, 'total_nodes' => $nodeCount]
90
+ );
91
+
92
+ $result = $graph->search($query);
93
+
94
+ if ($result && !empty($result['path'])) {
95
+ LwaziLogger::step('rag_search_found', [
96
+ 'path' => $result['path'],
97
+ 'label' => $result['label'] ?? '',
98
+ 'score' => $result['score'] ?? 0,
99
+ ]);
100
+ } else {
101
+ LwaziLogger::step('rag_search_not_found', ['query' => $query]);
102
+
103
+ $result = $graph->searchWithStemming($query);
104
+ if ($result) {
105
+ LwaziLogger::step('rag_stemming_search_found', [
106
+ 'path' => $result['path'],
107
+ 'label' => $result['label'] ?? '',
108
+ 'score' => $result['score'] ?? 0,
109
+ ]);
110
+ } else {
111
+ LwaziLogger::step('rag_stemming_search_not_found', ['query' => $query]);
112
+ }
64
113
  }
65
114
 
115
+ LwaziLogger::trackOutput('RagService', [
116
+ 'result' => $result,
117
+ 'method' => 'findPath',
118
+ ]);
119
+
66
120
  return $result;
67
121
  }
68
122
 
@@ -31,7 +31,7 @@ class KnowledgeBaseGenerator
31
31
  $database = $this->getDatabaseSchema();
32
32
  $dataActions = $this->inferDataActions($models);
33
33
  $informationArchitecture = $this->buildInformationArchitecture($pages, $routes);
34
- $navigationTree = $this->buildNavigationTree($pages, $routes);
34
+ $navigationGraph = $this->buildNavigationGraphFromRoutes($pages, $routes);
35
35
 
36
36
  return [
37
37
  'generated_at' => now()->toIso8601String(),
@@ -43,13 +43,13 @@ class KnowledgeBaseGenerator
43
43
  'database' => $database,
44
44
  'data_actions' => $dataActions,
45
45
  'information_architecture' => $informationArchitecture,
46
- 'navigation_tree' => $navigationTree,
46
+ 'navigation_graph' => $navigationGraph,
47
47
  ];
48
48
  }
49
49
 
50
- protected function buildNavigationTree(array $pages, array $routes): array
50
+ protected function buildNavigationGraphFromRoutes(array $pages, array $routes): array
51
51
  {
52
- $tree = new NavigationTree();
52
+ $graph = new NavigationGraph();
53
53
 
54
54
  $publicRoutes = array_filter($routes, function($r) {
55
55
  $uri = $r['uri'] ?? '';
@@ -65,9 +65,9 @@ class KnowledgeBaseGenerator
65
65
  return true;
66
66
  });
67
67
 
68
- $tree->buildFromPages($pages);
69
- $tree->buildFromRoutes(array_values($publicRoutes));
70
- return $tree->toArray();
68
+ $graph->buildFromPages($pages);
69
+ $graph->buildFromRoutes(array_values($publicRoutes));
70
+ return $graph->toArray();
71
71
  }
72
72
 
73
73
  /**