prisma-php 0.0.10 → 0.0.12
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/.github/copilot-instructions.md +18 -5
- package/dist/docs/backend-only.md +204 -0
- package/dist/docs/commands.md +458 -0
- package/dist/docs/email.md +6 -0
- package/dist/docs/env.md +224 -0
- package/dist/docs/error-handling.md +35 -23
- package/dist/docs/fetching-data.md +3 -1
- package/dist/docs/get-started-ia.md +482 -0
- package/dist/docs/index.md +62 -2
- package/dist/docs/installation.md +21 -2
- package/dist/docs/layouts-and-pages.md +13 -1
- package/dist/docs/mcp.md +4 -1
- package/dist/docs/metadata-and-og-images.md +34 -22
- package/dist/docs/prisma-php-orm.md +20 -16
- package/dist/docs/project-structure.md +47 -1
- package/dist/docs/pulsepoint.md +14 -1
- package/dist/docs/route-handlers.md +18 -2
- package/dist/docs/swagger-docs.md +189 -0
- package/dist/docs/typescript.md +195 -0
- package/dist/docs/upgrading.md +13 -9
- package/dist/docs/websocket.md +4 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Error Handling
|
|
3
|
-
description: Learn how
|
|
3
|
+
description: Learn how Prisma PHP handles expected errors and uncaught exceptions so AI agents can keep validation failures separate from crashes.
|
|
4
4
|
related:
|
|
5
5
|
title: API Reference
|
|
6
6
|
description: Learn more about the features mentioned on this page by reading the Prisma PHP docs.
|
|
@@ -17,7 +17,19 @@ Errors in Prisma PHP can be thought of in two broad categories:
|
|
|
17
17
|
1. **Expected errors** — validation failures, missing records, failed form submissions, or known request problems
|
|
18
18
|
2. **Unexpected errors** — uncaught exceptions, fatal errors, parse errors, or framework-level failures
|
|
19
19
|
|
|
20
|
-
Prisma PHP’s documented error model is centered around the `ErrorHandler` class, which acts as the application safety net. It captures exceptions, fatal errors, and parse errors, then converts them into structured responses depending on the request context. For AJAX-style requests it can return JSON; for browser rendering it can show a richer diagnostic UI.
|
|
20
|
+
Prisma PHP’s documented error model is centered around the `ErrorHandler` class, which acts as the application safety net. It captures exceptions, fatal errors, and parse errors, then converts them into structured responses depending on the request context. For AJAX-style requests it can return JSON; for browser rendering it can show a richer diagnostic UI.
|
|
21
|
+
|
|
22
|
+
If a task involves expected validation failures, missing records, `Boom` responses, `error.php`, `not-found.php`, or uncaught exceptions, AI agents should read this page first and keep routine failures separate from true crashes.
|
|
23
|
+
|
|
24
|
+
## AI rule: read the error-handling docs first
|
|
25
|
+
|
|
26
|
+
Before generating Prisma PHP error flows, use this order:
|
|
27
|
+
|
|
28
|
+
1. Decide whether the failure is expected or unexpected.
|
|
29
|
+
2. Use `PP\Validator` and structured return values for expected input problems.
|
|
30
|
+
3. Use `Boom`, `error.php`, `not-found.php`, or `ErrorHandler` for request and app-level error behavior.
|
|
31
|
+
4. Keep `route.php`, `pp.fetchFunction(...)`, and form responses explicit instead of turning routine validation into exceptions.
|
|
32
|
+
5. Read `validator.md` and `route-handlers.md` when the error starts from request validation.
|
|
21
33
|
|
|
22
34
|
## Handling expected errors
|
|
23
35
|
|
|
@@ -197,7 +209,7 @@ The docs describe `ErrorHandler` as the safety net for the application. It captu
|
|
|
197
209
|
|
|
198
210
|
- exceptions
|
|
199
211
|
- fatal errors
|
|
200
|
-
- parse errors
|
|
212
|
+
- parse errors
|
|
201
213
|
|
|
202
214
|
This makes it the Prisma PHP equivalent of a central application error boundary, but implemented at the PHP framework level rather than with React client components.
|
|
203
215
|
|
|
@@ -206,13 +218,13 @@ This makes it the Prisma PHP equivalent of a central application error boundary,
|
|
|
206
218
|
Prisma PHP’s `ErrorHandler` automatically detects the request context and chooses the appropriate output format. The docs explicitly say it can return:
|
|
207
219
|
|
|
208
220
|
- a JSON payload for AJAX requests
|
|
209
|
-
- a rich diagnostic UI for browser requests
|
|
221
|
+
- a rich diagnostic UI for browser requests
|
|
210
222
|
|
|
211
223
|
This is one of the biggest differences from Next.js. Instead of defining client-side error boundaries with `error.js`, Prisma PHP handles failures centrally on the server and formats the response according to the request type.
|
|
212
224
|
|
|
213
225
|
## Rich diagnostics
|
|
214
226
|
|
|
215
|
-
The Prisma PHP docs emphasize that `ErrorHandler` does more than show a generic PHP crash page. It can generate context-aware diagnostics for framework-specific failures.
|
|
227
|
+
The Prisma PHP docs emphasize that `ErrorHandler` does more than show a generic PHP crash page. It can generate context-aware diagnostics for framework-specific failures.
|
|
216
228
|
|
|
217
229
|
### Component validation diagnostics
|
|
218
230
|
|
|
@@ -221,7 +233,7 @@ When a component receives invalid props, the docs say the error UI can show:
|
|
|
221
233
|
- the specific component name
|
|
222
234
|
- the invalid property
|
|
223
235
|
- quick fixes such as adding a missing public property
|
|
224
|
-
- a list of currently available props
|
|
236
|
+
- a list of currently available props
|
|
225
237
|
|
|
226
238
|
### Template compilation diagnostics
|
|
227
239
|
|
|
@@ -229,7 +241,7 @@ When the template engine fails to parse a file, the docs say the error UI can:
|
|
|
229
241
|
|
|
230
242
|
- identify syntax errors in PulsePoint-specific tags
|
|
231
243
|
- highlight the specific line in the view file
|
|
232
|
-
- provide suggestions on how to correct the syntax
|
|
244
|
+
- provide suggestions on how to correct the syntax
|
|
233
245
|
|
|
234
246
|
This means Prisma PHP’s error page is intended to be an actionable developer tool, not only a fallback screen.
|
|
235
247
|
|
|
@@ -241,7 +253,7 @@ Prisma PHP allows you to override the default error output by creating:
|
|
|
241
253
|
APP_PATH . '/error.php'
|
|
242
254
|
```
|
|
243
255
|
|
|
244
|
-
The docs state that if this file exists, `ErrorHandler` will render it inside your main layout wrapper instead of using the default output.
|
|
256
|
+
The docs state that if this file exists, `ErrorHandler` will render it inside your main layout wrapper instead of using the default output.
|
|
245
257
|
|
|
246
258
|
That makes `error.php` the main file convention for customizing browser-visible application errors.
|
|
247
259
|
|
|
@@ -256,9 +268,9 @@ Example:
|
|
|
256
268
|
|
|
257
269
|
## Output buffering behavior
|
|
258
270
|
|
|
259
|
-
The docs note an important implementation detail: when a fatal error occurs, the handler calls `ob_end_clean()` to remove any partial HTML that was generated before the crash. This prevents broken layouts or incomplete output from being rendered.
|
|
271
|
+
The docs note an important implementation detail: when a fatal error occurs, the handler calls `ob_end_clean()` to remove any partial HTML that was generated before the crash. This prevents broken layouts or incomplete output from being rendered.
|
|
260
272
|
|
|
261
|
-
The docs also mention that if you are debugging with `echo` or `var_dump` immediately before a crash, you may need to temporarily disable this buffer cleaning in `modifyOutputLayoutForError` in order to see that debug output.
|
|
273
|
+
The docs also mention that if you are debugging with `echo` or `var_dump` immediately before a crash, you may need to temporarily disable this buffer cleaning in `modifyOutputLayoutForError` in order to see that debug output.
|
|
262
274
|
|
|
263
275
|
## Key methods
|
|
264
276
|
|
|
@@ -272,15 +284,15 @@ Registers:
|
|
|
272
284
|
- `register_shutdown_function`
|
|
273
285
|
- `set_error_handler`
|
|
274
286
|
|
|
275
|
-
The docs say this is usually called in `bootstrap.php`.
|
|
287
|
+
The docs say this is usually called in `bootstrap.php`.
|
|
276
288
|
|
|
277
289
|
### `checkFatalError(): void`
|
|
278
290
|
|
|
279
|
-
Manually checks `error_get_last()`. The docs describe this as useful for catching shutdown-time errors that are not regular exceptions.
|
|
291
|
+
Manually checks `error_get_last()`. The docs describe this as useful for catching shutdown-time errors that are not regular exceptions.
|
|
280
292
|
|
|
281
293
|
### `formatExceptionForDisplay(Throwable $e): string`
|
|
282
294
|
|
|
283
|
-
Formats an exception into the rich HTML diagnostic output. The docs say it determines whether the error is a standard PHP exception or a specific PulsePoint framework error.
|
|
295
|
+
Formats an exception into the rich HTML diagnostic output. The docs say it determines whether the error is a standard PHP exception or a specific PulsePoint framework error.
|
|
284
296
|
|
|
285
297
|
## Bootstrap registration
|
|
286
298
|
|
|
@@ -315,7 +327,7 @@ try {
|
|
|
315
327
|
}
|
|
316
328
|
```
|
|
317
329
|
|
|
318
|
-
This is useful when you want to catch an exception yourself but still use the framework’s standard rich diagnostic output.
|
|
330
|
+
This is useful when you want to catch an exception yourself but still use the framework’s standard rich diagnostic output.
|
|
319
331
|
|
|
320
332
|
## `index.php` vs `route.php` error handling
|
|
321
333
|
|
|
@@ -323,7 +335,7 @@ Error handling in Prisma PHP still follows the route model:
|
|
|
323
335
|
|
|
324
336
|
- in `index.php`, errors usually affect rendered page output
|
|
325
337
|
- in `route.php`, errors usually affect JSON or direct handler responses
|
|
326
|
-
- the `ErrorHandler` adapts the response format based on request context
|
|
338
|
+
- the `ErrorHandler` adapts the response format based on request context
|
|
327
339
|
|
|
328
340
|
For full-stack projects, expected user-facing problems should usually be handled gracefully in `index.php` or in direct function responses. For API-style routes, structured JSON error responses are usually the right choice for expected failures. Uncaught exceptions should be left to the framework-level `ErrorHandler`.
|
|
329
341
|
|
|
@@ -343,14 +355,14 @@ The main difference is that Prisma PHP’s documented error handling is server-d
|
|
|
343
355
|
|
|
344
356
|
## Good to know
|
|
345
357
|
|
|
346
|
-
- Prisma PHP’s documented error system is `ErrorHandler`.
|
|
347
|
-
- It captures exceptions, fatal errors, and parse errors.
|
|
348
|
-
- It automatically switches output based on request context.
|
|
349
|
-
- AJAX-style requests can receive JSON errors.
|
|
350
|
-
- Browser requests can receive a rich diagnostic UI.
|
|
351
|
-
- You can override the default error view with `APP_PATH . '/error.php'`.
|
|
352
|
-
- Fatal-error handling clears partial output with `ob_end_clean()`.
|
|
353
|
-
- `registerHandlers()` is usually called in `bootstrap.php`.
|
|
358
|
+
- Prisma PHP’s documented error system is `ErrorHandler`.
|
|
359
|
+
- It captures exceptions, fatal errors, and parse errors.
|
|
360
|
+
- It automatically switches output based on request context.
|
|
361
|
+
- AJAX-style requests can receive JSON errors.
|
|
362
|
+
- Browser requests can receive a rich diagnostic UI.
|
|
363
|
+
- You can override the default error view with `APP_PATH . '/error.php'`.
|
|
364
|
+
- Fatal-error handling clears partial output with `ob_end_clean()`.
|
|
365
|
+
- `registerHandlers()` is usually called in `bootstrap.php`.
|
|
354
366
|
|
|
355
367
|
## Validation vs unexpected failures
|
|
356
368
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Fetching Data
|
|
3
|
-
description: Learn how
|
|
3
|
+
description: Learn how AI agents should fetch and stream data in Prisma PHP using `pp.fetchFunction`, `#[Exposed]`, page handlers, SSE responses, and route handlers.
|
|
4
4
|
related:
|
|
5
5
|
title: API Reference
|
|
6
6
|
description: Learn more about the features mentioned in this page by reading the Prisma PHP docs.
|
|
@@ -14,6 +14,8 @@ related:
|
|
|
14
14
|
|
|
15
15
|
This page will walk you through how you can fetch and stream data in Prisma PHP, when to use `pp.fetchFunction(...)`, when to use `index.php`, when to use `route.php`, and how to validate incoming data correctly on the PHP side.
|
|
16
16
|
|
|
17
|
+
If a task involves page-local interactivity, exposed PHP functions, streamed responses, or deciding between `index.php` and `route.php`, AI agents should read this page first and keep the interaction model aligned with Prisma PHP.
|
|
18
|
+
|
|
17
19
|
Prisma PHP has a different mental model from Next.js. Instead of focusing on React Server Components, Suspense-driven HTML streaming, and client data libraries, Prisma PHP gives you direct function invocation from the frontend to PHP, plus file-based routing for page UI and direct route handlers.
|
|
18
20
|
|
|
19
21
|
When using `pp.fetchFunction(...)`, the PHP function must be explicitly exposed with `#[Exposed]`. Functions are private by default, so a non-exposed function cannot be called from the client.
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AI Integration
|
|
3
|
+
description: Build chat, assistant, and streamed AI features in Prisma PHP with PulsePoint, pp.fetchFunction(...), PP\Validator, and MCP when you need agent-facing tools.
|
|
4
|
+
related:
|
|
5
|
+
title: Related docs
|
|
6
|
+
description: Read the core Prisma PHP docs that support provider-backed AI UIs, streaming, and agent tooling.
|
|
7
|
+
links:
|
|
8
|
+
- /docs/env
|
|
9
|
+
- /docs/fetch-function
|
|
10
|
+
- /docs/php-validator
|
|
11
|
+
- /docs/pulsepoint
|
|
12
|
+
- /docs/prisma-php-ai-mcp
|
|
13
|
+
- /docs/websocket-chat-app
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Prisma PHP is a good fit for AI features, but the right Prisma PHP pattern depends on what you are actually building.
|
|
17
|
+
|
|
18
|
+
If a task involves provider-backed chat, prompt forms, streamed output, assistant panels, or deciding between page-local AI, websocket, and MCP, AI agents should read this page first and keep the implementation aligned with the installed Prisma PHP docs.
|
|
19
|
+
|
|
20
|
+
For a normal user-facing chat box, assistant panel, prompt form, streamed summary, or similar page-local AI feature, the recommended Prisma PHP approach is:
|
|
21
|
+
|
|
22
|
+
1. render the UI in `index.php`
|
|
23
|
+
2. keep browser-side interactivity in **PulsePoint**
|
|
24
|
+
3. call backend PHP with **`pp.fetchFunction(...)`**
|
|
25
|
+
4. mark callable PHP functions with **`#[Exposed]`**
|
|
26
|
+
5. validate and normalize incoming data with **`PP\Validator`**
|
|
27
|
+
6. stream incremental output by **yielding** from PHP when the provider supports streaming
|
|
28
|
+
|
|
29
|
+
That keeps the **frontend reactive** while keeping the **AI call, validation, secrets, and business logic on the PHP side**.
|
|
30
|
+
|
|
31
|
+
Do **not** default to `route.php` for page-local chat or assistant UI just because AI is involved. In Prisma PHP, `route.php` is still useful, but it is the better fit for standalone APIs, webhooks, third-party integrations, or endpoints that must exist independently of the current page.
|
|
32
|
+
|
|
33
|
+
If the real goal is to expose **agent-callable tools** instead of a user chat interface, read `mcp.md` and the official `prisma-php-ai-mcp` docs. MCP is about serving tools to agents; it is not the default answer for a page-local chat box.
|
|
34
|
+
|
|
35
|
+
## The default AI architecture in Prisma PHP
|
|
36
|
+
|
|
37
|
+
When the user is building a normal AI feature inside a Prisma PHP app, AI should usually prefer this stack:
|
|
38
|
+
|
|
39
|
+
1. `index.php` for the route UI
|
|
40
|
+
2. PulsePoint for browser-side state
|
|
41
|
+
3. `pp.fetchFunction(...)` for frontend-to-PHP RPC
|
|
42
|
+
4. `#[Exposed]` for callable PHP functions
|
|
43
|
+
5. `PP\Validator` for server-side validation
|
|
44
|
+
6. streamed `yield` responses for incremental assistant output
|
|
45
|
+
|
|
46
|
+
This is the right default for:
|
|
47
|
+
|
|
48
|
+
- single-user chat pages
|
|
49
|
+
- assistant sidebars
|
|
50
|
+
- prompt forms
|
|
51
|
+
- streamed summaries
|
|
52
|
+
- content generation tools
|
|
53
|
+
- classification or extraction actions
|
|
54
|
+
- AI-backed search or suggestion panels
|
|
55
|
+
- route-local progress logs
|
|
56
|
+
|
|
57
|
+
Reserve `route.php` for cases where the endpoint must be called without the page, and reserve websocket-based transport for cases where you need true server push, multi-user realtime chat, or presence-style features.
|
|
58
|
+
|
|
59
|
+
## Pick the right AI surface
|
|
60
|
+
|
|
61
|
+
Prisma PHP has three different AI-related surfaces, and they should not be mixed up.
|
|
62
|
+
|
|
63
|
+
### 1. User-facing AI in a page
|
|
64
|
+
|
|
65
|
+
Use this when a human is interacting with a Prisma PHP page.
|
|
66
|
+
|
|
67
|
+
Default stack:
|
|
68
|
+
|
|
69
|
+
- `index.php`
|
|
70
|
+
- PulsePoint
|
|
71
|
+
- `pp.fetchFunction(...)`
|
|
72
|
+
- `#[Exposed]`
|
|
73
|
+
- `PP\Validator`
|
|
74
|
+
|
|
75
|
+
This is the default for chat, prompt forms, streamed output, inline assistants, or route-local AI actions.
|
|
76
|
+
|
|
77
|
+
### 2. Standalone AI endpoint
|
|
78
|
+
|
|
79
|
+
Use `route.php` when the request should exist independently of the rendered page.
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
|
|
83
|
+
- webhooks
|
|
84
|
+
- provider callbacks
|
|
85
|
+
- public JSON APIs
|
|
86
|
+
- integration endpoints
|
|
87
|
+
- non-UI automation calls
|
|
88
|
+
|
|
89
|
+
### 3. Agent-facing tools
|
|
90
|
+
|
|
91
|
+
Use MCP when an external agent should call business actions in your app.
|
|
92
|
+
|
|
93
|
+
Read:
|
|
94
|
+
|
|
95
|
+
- `mcp.md`
|
|
96
|
+
- official `prisma-php-ai-mcp`
|
|
97
|
+
- official `ai-tools`
|
|
98
|
+
|
|
99
|
+
### Quick rule of thumb
|
|
100
|
+
|
|
101
|
+
- user chat box on a page -> `index.php` + PulsePoint + `pp.fetchFunction(...)`
|
|
102
|
+
- endpoint with no page dependency -> `route.php`
|
|
103
|
+
- tools for agents -> MCP
|
|
104
|
+
|
|
105
|
+
## Install an AI client
|
|
106
|
+
|
|
107
|
+
Prisma PHP is intentionally unopinionated about AI providers. You can use OpenAI, Anthropic, Groq, or another PHP SDK that fits your application.
|
|
108
|
+
|
|
109
|
+
For OpenAI-compatible providers, a common starting point is:
|
|
110
|
+
|
|
111
|
+
```bash package="composer"
|
|
112
|
+
composer require openai-php/client
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use the provider package that matches your model host, but keep the Prisma PHP architecture the same: the browser calls PHP, and PHP talks to the model provider.
|
|
116
|
+
|
|
117
|
+
## Configure secrets in `.env`
|
|
118
|
+
|
|
119
|
+
Keep provider secrets on the server side.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
|
|
123
|
+
```dotenv
|
|
124
|
+
OPENAI_API_KEY="sk-..."
|
|
125
|
+
OPENAI_MODEL="gpt-4o-mini"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Do **not** push provider API keys into PulsePoint state or browser-side JavaScript.
|
|
129
|
+
When reading these values in PHP, prefer `PP\Env` over raw `getenv()` so defaults and typed access stay consistent with Prisma PHP's env helper.
|
|
130
|
+
|
|
131
|
+
## Recommended non-streaming chat pattern
|
|
132
|
+
|
|
133
|
+
For a simple AI response, keep the entire route in `index.php`, expose one PHP function, validate the prompt on the PHP side, and call the provider there.
|
|
134
|
+
|
|
135
|
+
```php filename="src/app/chat/index.php"
|
|
136
|
+
<?php
|
|
137
|
+
|
|
138
|
+
use PP\Attributes\Exposed;
|
|
139
|
+
use PP\Env;
|
|
140
|
+
use PP\MainLayout;
|
|
141
|
+
use PP\Rule;
|
|
142
|
+
use PP\Validator;
|
|
143
|
+
|
|
144
|
+
MainLayout::$title = 'AI Chat';
|
|
145
|
+
MainLayout::$description = 'Chat with a provider from Prisma PHP using PulsePoint and pp.fetchFunction(...).';
|
|
146
|
+
|
|
147
|
+
#[Exposed]
|
|
148
|
+
function sendChatMessage($data)
|
|
149
|
+
{
|
|
150
|
+
$message = Validator::string($data->message ?? '');
|
|
151
|
+
$result = Validator::withRules(
|
|
152
|
+
$message,
|
|
153
|
+
Rule::required()->min(1)->max(4000)
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if ($result !== true) {
|
|
157
|
+
return [
|
|
158
|
+
'success' => false,
|
|
159
|
+
'errors' => [
|
|
160
|
+
'message' => $result,
|
|
161
|
+
],
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
$client = \OpenAI::client(Env::string('OPENAI_API_KEY', ''));
|
|
166
|
+
$model = Env::string('OPENAI_MODEL', 'gpt-4o-mini');
|
|
167
|
+
|
|
168
|
+
$response = $client->chat()->create([
|
|
169
|
+
'model' => $model,
|
|
170
|
+
'messages' => [
|
|
171
|
+
['role' => 'system', 'content' => 'You are a helpful Prisma PHP assistant.'],
|
|
172
|
+
['role' => 'user', 'content' => $message],
|
|
173
|
+
],
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
return [
|
|
177
|
+
'success' => true,
|
|
178
|
+
'errors' => [],
|
|
179
|
+
'assistant' => [
|
|
180
|
+
'role' => 'assistant',
|
|
181
|
+
'content' => trim($response->choices[0]->message->content ?? ''),
|
|
182
|
+
],
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
?>
|
|
186
|
+
|
|
187
|
+
<section pp-component="chat-page">
|
|
188
|
+
<header>
|
|
189
|
+
<h1>AI Chat</h1>
|
|
190
|
+
<p>Send a prompt from PulsePoint and keep the provider call in PHP.</p>
|
|
191
|
+
</header>
|
|
192
|
+
|
|
193
|
+
<div>
|
|
194
|
+
<template pp-for="(item, index) in messages">
|
|
195
|
+
<article key="{`${item.role}-${index}`}">
|
|
196
|
+
<strong>{item.role}</strong>
|
|
197
|
+
<p>{item.content}</p>
|
|
198
|
+
</article>
|
|
199
|
+
</template>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<form onsubmit="event.preventDefault(); submitMessage()">
|
|
203
|
+
<textarea value="{draft}" oninput="setDraft(event.target.value)"></textarea>
|
|
204
|
+
<p hidden="{!error}">{error}</p>
|
|
205
|
+
<button type="submit" disabled="{loading}">Send</button>
|
|
206
|
+
</form>
|
|
207
|
+
|
|
208
|
+
<script>
|
|
209
|
+
const [draft, setDraft] = pp.state('');
|
|
210
|
+
const [messages, setMessages] = pp.state([]);
|
|
211
|
+
const [error, setError] = pp.state('');
|
|
212
|
+
const [loading, setLoading] = pp.state(false);
|
|
213
|
+
|
|
214
|
+
async function submitMessage() {
|
|
215
|
+
const text = draft.trim();
|
|
216
|
+
|
|
217
|
+
if (!text || loading) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
setError('');
|
|
222
|
+
setDraft('');
|
|
223
|
+
setMessages((current) => [...current, { role: 'user', content: text }]);
|
|
224
|
+
setLoading(true);
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const response = await pp.fetchFunction('sendChatMessage', { message: text });
|
|
228
|
+
|
|
229
|
+
if (!response.success) {
|
|
230
|
+
setError(response.errors?.message ?? 'Message failed.');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setMessages((current) => [...current, response.assistant]);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
setError(error.message || 'Request failed.');
|
|
237
|
+
} finally {
|
|
238
|
+
setLoading(false);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
</script>
|
|
242
|
+
</section>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Why this pattern is preferred:
|
|
246
|
+
|
|
247
|
+
- the route UI stays in `index.php`
|
|
248
|
+
- PulsePoint owns the browser state
|
|
249
|
+
- the provider call stays in PHP
|
|
250
|
+
- secrets stay on the server
|
|
251
|
+
- `Validator` remains the authoritative validation layer
|
|
252
|
+
- the route avoids extra endpoint boilerplate when the action belongs to the page
|
|
253
|
+
|
|
254
|
+
## Recommended streaming chat pattern
|
|
255
|
+
|
|
256
|
+
For streamed assistant output, keep the same architecture and change only the backend response shape: make the exposed PHP function **yield** chunks.
|
|
257
|
+
|
|
258
|
+
That gives you incremental SSE delivery through `pp.fetchFunction(...)` without hand-writing a separate transport layer for normal assistant output.
|
|
259
|
+
|
|
260
|
+
```php filename="src/app/chat/index.php"
|
|
261
|
+
<?php
|
|
262
|
+
|
|
263
|
+
use PP\Attributes\Exposed;
|
|
264
|
+
use PP\Env;
|
|
265
|
+
use PP\Rule;
|
|
266
|
+
use PP\Validator;
|
|
267
|
+
|
|
268
|
+
#[Exposed]
|
|
269
|
+
function streamChatMessage($data)
|
|
270
|
+
{
|
|
271
|
+
$message = Validator::string($data->message ?? '');
|
|
272
|
+
$result = Validator::withRules(
|
|
273
|
+
$message,
|
|
274
|
+
Rule::required()->min(1)->max(4000)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if ($result !== true) {
|
|
278
|
+
yield [
|
|
279
|
+
'type' => 'error',
|
|
280
|
+
'message' => $result,
|
|
281
|
+
];
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
$client = \OpenAI::client(Env::string('OPENAI_API_KEY', ''));
|
|
286
|
+
$model = Env::string('OPENAI_MODEL', 'gpt-4o-mini');
|
|
287
|
+
|
|
288
|
+
$stream = $client->chat()->createStreamed([
|
|
289
|
+
'model' => $model,
|
|
290
|
+
'messages' => [
|
|
291
|
+
['role' => 'system', 'content' => 'You are a helpful Prisma PHP assistant.'],
|
|
292
|
+
['role' => 'user', 'content' => $message],
|
|
293
|
+
],
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
foreach ($stream as $response) {
|
|
297
|
+
$delta = $response->choices[0]->delta->content ?? '';
|
|
298
|
+
|
|
299
|
+
if ($delta !== '') {
|
|
300
|
+
yield [
|
|
301
|
+
'type' => 'chunk',
|
|
302
|
+
'chunk' => $delta,
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
If your provider uses a different streaming API, keep the Prisma PHP shape the same:
|
|
310
|
+
|
|
311
|
+
1. validate in PHP first
|
|
312
|
+
2. start the provider stream in PHP
|
|
313
|
+
3. loop over provider deltas
|
|
314
|
+
4. yield strings or arrays back to the client
|
|
315
|
+
|
|
316
|
+
### Client-side streaming with PulsePoint
|
|
317
|
+
|
|
318
|
+
```html
|
|
319
|
+
<script>
|
|
320
|
+
const [draft, setDraft] = pp.state('');
|
|
321
|
+
const [messages, setMessages] = pp.state([]);
|
|
322
|
+
const [error, setError] = pp.state('');
|
|
323
|
+
const [streaming, setStreaming] = pp.state(false);
|
|
324
|
+
|
|
325
|
+
async function sendStream() {
|
|
326
|
+
const text = draft.trim();
|
|
327
|
+
|
|
328
|
+
if (!text || streaming) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
setError('');
|
|
333
|
+
setDraft('');
|
|
334
|
+
setStreaming(true);
|
|
335
|
+
setMessages((current) => [
|
|
336
|
+
...current,
|
|
337
|
+
{ role: 'user', content: text },
|
|
338
|
+
{ role: 'assistant', content: '' },
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
await pp.fetchFunction(
|
|
343
|
+
'streamChatMessage',
|
|
344
|
+
{ message: text },
|
|
345
|
+
{
|
|
346
|
+
onStream(data) {
|
|
347
|
+
if (data.type === 'error') {
|
|
348
|
+
setError(data.message || 'Invalid message.');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const delta = data.chunk || '';
|
|
353
|
+
|
|
354
|
+
if (delta === '') {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
setMessages((current) => {
|
|
359
|
+
if (current.length === 0) {
|
|
360
|
+
return current;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const next = current.slice();
|
|
364
|
+
const last = next[next.length - 1];
|
|
365
|
+
|
|
366
|
+
next[next.length - 1] = {
|
|
367
|
+
...last,
|
|
368
|
+
content: `${last.content}${delta}`,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
return next;
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
onStreamError(error) {
|
|
375
|
+
setError(error.message || 'Stream failed.');
|
|
376
|
+
},
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
} finally {
|
|
380
|
+
setStreaming(false);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
</script>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Important streaming rule:
|
|
387
|
+
|
|
388
|
+
- do not wait for one final JSON object to render the reply
|
|
389
|
+
- update the UI from `onStream`
|
|
390
|
+
- use `onStreamError` and `onStreamComplete` for stream lifecycle handling
|
|
391
|
+
|
|
392
|
+
For the current SSE behavior and low-level streaming helpers, read `fetching-data.md`.
|
|
393
|
+
|
|
394
|
+
## Validate everything on the PHP side
|
|
395
|
+
|
|
396
|
+
When building AI features, do not trust browser-side checks as the final source of truth.
|
|
397
|
+
|
|
398
|
+
Use this split:
|
|
399
|
+
|
|
400
|
+
- PulsePoint for local UX state
|
|
401
|
+
- `pp.fetchFunction(...)` for calling PHP
|
|
402
|
+
- `PP\Validator` for sanitization and casting
|
|
403
|
+
- `Validator::withRules(...)` for business rules
|
|
404
|
+
- structured return values for expected validation failures
|
|
405
|
+
|
|
406
|
+
Good server-side validation targets include:
|
|
407
|
+
|
|
408
|
+
- prompt text
|
|
409
|
+
- prompt length
|
|
410
|
+
- selected model name
|
|
411
|
+
- message arrays or prior history
|
|
412
|
+
- temperature or option values
|
|
413
|
+
- tool selection input
|
|
414
|
+
- database IDs tied to retrieval or conversation threads
|
|
415
|
+
|
|
416
|
+
If you are sending prior messages or advanced options back to PHP, validate those values before they are stored, queried against the database, or forwarded to the provider.
|
|
417
|
+
|
|
418
|
+
## Conversation state and persistence
|
|
419
|
+
|
|
420
|
+
For small page-local chat flows, PulsePoint can hold the visible message list while PHP handles the provider call.
|
|
421
|
+
|
|
422
|
+
When you need durable chat history or retrieval-backed context:
|
|
423
|
+
|
|
424
|
+
- load and store conversation records on the PHP side
|
|
425
|
+
- use Prisma ORM from PHP, not from the browser
|
|
426
|
+
- validate inbound message payloads before persistence
|
|
427
|
+
- shape the final provider request in PHP, not in client-side JavaScript
|
|
428
|
+
|
|
429
|
+
That keeps the conversation contract authoritative on the server.
|
|
430
|
+
|
|
431
|
+
## SSE vs websocket vs MCP
|
|
432
|
+
|
|
433
|
+
These three features solve different problems.
|
|
434
|
+
|
|
435
|
+
### Use streamed `pp.fetchFunction(...)` when
|
|
436
|
+
|
|
437
|
+
- the user triggered a request from the current page
|
|
438
|
+
- the assistant response should appear incrementally
|
|
439
|
+
- the page owns the request lifecycle
|
|
440
|
+
- normal request-response semantics are still correct
|
|
441
|
+
|
|
442
|
+
This is the default for AI text streaming.
|
|
443
|
+
|
|
444
|
+
### Use websocket when
|
|
445
|
+
|
|
446
|
+
- the server must push updates without a fresh page request
|
|
447
|
+
- multiple users must see shared realtime changes
|
|
448
|
+
- you need presence, rooms, or bidirectional messaging
|
|
449
|
+
|
|
450
|
+
Read `websocket.md` when that is the actual requirement.
|
|
451
|
+
|
|
452
|
+
### Use MCP when
|
|
453
|
+
|
|
454
|
+
- an external agent needs callable tools
|
|
455
|
+
- the goal is tool exposure, not page-local chat UI
|
|
456
|
+
- you want agent workflows to query your app or database
|
|
457
|
+
|
|
458
|
+
Read `mcp.md` when that is the actual requirement.
|
|
459
|
+
|
|
460
|
+
## When `route.php` is still correct
|
|
461
|
+
|
|
462
|
+
`route.php` still makes sense for AI-related work when:
|
|
463
|
+
|
|
464
|
+
- the endpoint must be called from outside the current page
|
|
465
|
+
- the request is an integration or webhook
|
|
466
|
+
- you are exposing a standalone API
|
|
467
|
+
- the response is not part of a page-local interactive workflow
|
|
468
|
+
|
|
469
|
+
For example, provider webhooks, background callbacks, or public machine-facing AI endpoints are reasonable `route.php` use cases.
|
|
470
|
+
|
|
471
|
+
What should change is the **default assumption**: a chat screen inside a Prisma PHP page should usually start with `index.php` + PulsePoint + `pp.fetchFunction(...)`, not with a separate endpoint first.
|
|
472
|
+
|
|
473
|
+
## Good to know
|
|
474
|
+
|
|
475
|
+
- Prisma PHP is unopinionated about AI SDK choice.
|
|
476
|
+
- For page-local AI UI, keep the browser reactive and keep the provider call in PHP.
|
|
477
|
+
- Use `#[Exposed]` for any PHP function called from `pp.fetchFunction(...)`.
|
|
478
|
+
- Use `PP\Validator` as the default validation layer for AI request payloads.
|
|
479
|
+
- Stream assistant replies by yielding from PHP and consuming chunks with `onStream`.
|
|
480
|
+
- Prefer `route.php` only when the endpoint must exist independently of the page.
|
|
481
|
+
- Use websocket for true server push or multi-user realtime behavior.
|
|
482
|
+
- Use MCP for agent-facing tools, not as the default transport for a page-local chat box.
|