lwazi 1.9.1 → 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.9.1",
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": {
@@ -8,9 +8,10 @@ use Lwazi\Core\Services\LwaziLogger;
8
8
  class LogsCommand extends Command
9
9
  {
10
10
  protected $signature = 'lwazi:logs
11
- {action=show : Action to perform (show, clear, trace)}
11
+ {action=show : Action to perform (show, clear, trace, tail, watch)}
12
12
  {trace_id? : Trace ID to show details}
13
- {--limit=20 : Number of recent traces to show}';
13
+ {--limit=20 : Number of recent traces to show}
14
+ {--interval=2 : Refresh interval in seconds for tail/watch}';
14
15
 
15
16
  protected $description = 'View and manage Lwazi trace logs for debugging';
16
17
 
@@ -24,6 +25,8 @@ class LogsCommand extends Command
24
25
  'trace' => $this->showTrace($this->argument('trace_id')),
25
26
  'clear' => $this->clearLogs(),
26
27
  'info' => $this->showLogInfo(),
28
+ 'tail' => $this->tailLogs(),
29
+ 'watch' => $this->tailLogs(),
27
30
  default => $this->showHelp(),
28
31
  };
29
32
  }
@@ -132,6 +135,10 @@ class LogsCommand extends Command
132
135
  $this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
133
136
  $this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
134
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");
135
142
  }
136
143
 
137
144
  protected function showHelp(): void
@@ -146,8 +153,54 @@ class LogsCommand extends Command
146
153
  $this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
147
154
  $this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
148
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");
149
158
  $this->line("");
150
159
  $this->line('The trace logs show the flow of a message through the Lwazi system,');
151
160
  $this->line('including intent detection, data fetching, and response generation.');
152
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
+ }
153
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
+ }
@@ -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;
@@ -46,11 +47,15 @@ class LwaziServiceProvider extends ServiceProvider
46
47
 
47
48
  public function boot(): void
48
49
  {
50
+ new LwaziLogger();
51
+
49
52
  $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'lwazi');
50
53
 
51
54
  Route::prefix(config('lwazi.route_prefix', 'lwazi'))->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class])->group(function () {
52
55
  Route::post('/chat', \Lwazi\Core\Http\Controllers\ChatController::class . '@chat');
53
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');
54
59
  });
55
60
 
56
61
  $this->publishes(
@@ -10,14 +10,13 @@ class LwaziLogger
10
10
  protected static ?string $currentTraceId = null;
11
11
  protected static array $currentSpans = [];
12
12
  protected static array $spansByTrace = [];
13
- protected static string $logPath;
13
+ protected static ?string $logPath = null;
14
14
  protected static bool $enabled = true;
15
15
  protected static int $maxStoredTraces = 100;
16
16
 
17
17
  public function __construct()
18
18
  {
19
- self::$logPath = storage_path('lwazi/logs');
20
- self::ensureLogDirectory();
19
+ self::initLogPath();
21
20
  }
22
21
 
23
22
  public static function setEnabled(bool $enabled): void
@@ -30,8 +29,16 @@ class LwaziLogger
30
29
  return self::$enabled;
31
30
  }
32
31
 
32
+ protected static function initLogPath(): void
33
+ {
34
+ if (self::$logPath === null) {
35
+ self::$logPath = storage_path('lwazi/logs');
36
+ }
37
+ }
38
+
33
39
  protected static function ensureLogDirectory(): void
34
40
  {
41
+ self::initLogPath();
35
42
  if (!is_dir(self::$logPath)) {
36
43
  mkdir(self::$logPath, 0755, true);
37
44
  }
@@ -597,6 +604,7 @@ class LwaziLogger
597
604
 
598
605
  public static function getLogPath(): string
599
606
  {
600
- return self::$logPath;
607
+ self::initLogPath();
608
+ return self::$logPath ?? '';
601
609
  }
602
610
  }