@checkstack/ai-frontend 0.1.3 → 0.1.5
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/CHANGELOG.md +100 -0
- package/package.json +6 -6
- package/src/lib/use-chat-turn.ts +18 -2
- package/src/pages/ChatPage.tsx +21 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,105 @@
|
|
|
1
1
|
# @checkstack/ai-frontend
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b50916d: Fix "Date cannot be represented in JSON Schema" crashing the AI chat. Zod v4's
|
|
8
|
+
`toJSONSchema()` throws on `z.date()` (and even `z.coerce.date()`) by default,
|
|
9
|
+
and the chat hit this in TWO places:
|
|
10
|
+
|
|
11
|
+
- **`@checkstack/backend-api`** `toJsonSchema()` (the OpenAPI generator and AI
|
|
12
|
+
tool-introspection / MCP substrate) called it with no options.
|
|
13
|
+
- **`@checkstack/ai-backend`** the agent loop hands the Vercel AI SDK the raw
|
|
14
|
+
Zod tool input, and the SDK runs its OWN `toJSONSchema()` (throwing) to build
|
|
15
|
+
the model-facing tool schema - so a single date field in any tool input
|
|
16
|
+
crashed every chat turn (the whole tool list is projected before the model is
|
|
17
|
+
called).
|
|
18
|
+
|
|
19
|
+
Both now render dates as `{ type: "string", format: "date-time" }` (their wire
|
|
20
|
+
shape) and degrade other unrepresentable types to `{}` instead of throwing.
|
|
21
|
+
|
|
22
|
+
For the model boundary, a single `dateSafeModelSchema()` helper hands the SDK a
|
|
23
|
+
ready-made date-safe schema plus a validator that COERCES the ISO strings the
|
|
24
|
+
model emits back into real `Date`s before parsing with the original schema
|
|
25
|
+
(refinements and the downstream RPC client, which expects `Date`s, keep
|
|
26
|
+
working). A single `toModelSchema()` entry point applies this at EVERY point a
|
|
27
|
+
schema is handed to the model - chat tool inputs, the headless agent runner's
|
|
28
|
+
tool inputs (the automation "AI Action"), and `generateObject` structured
|
|
29
|
+
output - gated so non-date schemas are untouched, so individual tool / agent
|
|
30
|
+
definitions never special-case dates. Regression tests cover the converter, the
|
|
31
|
+
AI tool serializer, and the model-schema generation + coercion helper, including
|
|
32
|
+
the full inbound round-trip with the exact ISO shape a live model emits
|
|
33
|
+
(`...T22:00:00Z`, no milliseconds).
|
|
34
|
+
|
|
35
|
+
**Timezone correctness.** Because the model produces dates as text, the chat now
|
|
36
|
+
enforces an unambiguous wire contract: a date-time tool argument MUST be RFC 3339
|
|
37
|
+
with an explicit timezone offset. Zone-less (`2026-07-01T22:00:00`) and date-only
|
|
38
|
+
(`2026-07-01`) values are rejected with a model-readable error (the model
|
|
39
|
+
self-repairs), instead of being silently interpreted in the pod's local zone -
|
|
40
|
+
which would resolve the same string to different instants across pods. To resolve
|
|
41
|
+
an operator's bare "22:00", the browser's IANA timezone is sent with every chat
|
|
42
|
+
turn and folded into the system prompt, so each operator's times are interpreted
|
|
43
|
+
in their own zone by default. When no browser zone is available (a headless
|
|
44
|
+
automation AI Action), the reference zone falls back to the host/container
|
|
45
|
+
timezone (`TZ`), not UTC. A format-matrix test covers every common shape a model
|
|
46
|
+
might emit. The chat UI shows the operator which timezone is in use, and the
|
|
47
|
+
`TZ` override is documented for operators.
|
|
48
|
+
|
|
49
|
+
**Current time in context.** The model has no clock, so the system prompt now
|
|
50
|
+
includes the current instant (UTC plus the reference-zone wall clock), letting it
|
|
51
|
+
resolve relative dates like "today at 10:00" without asking. Applied to both the
|
|
52
|
+
chat and the headless agent runner, computed per turn/run so it is never stale.
|
|
53
|
+
|
|
54
|
+
**Less-strict topic classifier.** The chat's off-topic pre-classifier was
|
|
55
|
+
refusing legitimate requests like "create a maintenance" because maintenances
|
|
56
|
+
(and several other domains) were not listed. The classifier now enumerates the
|
|
57
|
+
full domain set and treats any create/list/update/delete action on a platform
|
|
58
|
+
resource as on-topic by default.
|
|
59
|
+
|
|
60
|
+
## 0.1.4
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- 9d8961c: Fix the double-scrolling on the AI chat page (`/ai/chat`). The page sized its
|
|
65
|
+
layout with a fixed `calc(100vh - 220px)` height, which overshot the available
|
|
66
|
+
space when the page subtitle wrapped to two lines - so the whole page scrolled
|
|
67
|
+
on top of the message list's own scroll.
|
|
68
|
+
|
|
69
|
+
`PageLayout` gains an opt-in `fillHeight` prop that fills the viewport via a
|
|
70
|
+
bounded flex height chain (established in the app shell) instead of viewport
|
|
71
|
+
math; the chat page uses it so only the message list scrolls and the page itself
|
|
72
|
+
never does. Normal document-flow pages are unaffected (they still scroll the
|
|
73
|
+
main area as before).
|
|
74
|
+
|
|
75
|
+
- fb705df: Upgrade React 18 to React 19 across the platform.
|
|
76
|
+
|
|
77
|
+
**BREAKING (runtime frontend plugins):** React is shared as a Module Federation
|
|
78
|
+
singleton, so the host now provides **React 19** to every runtime plugin.
|
|
79
|
+
Frontend plugins built against React 18 must be rebuilt against React 19
|
|
80
|
+
(`react` / `react-dom` `^19`). The scaffold templates and the host/plugin MF
|
|
81
|
+
`requiredVersion` are updated to `^19`. `react` (and now `react-dom`) are pinned
|
|
82
|
+
to a single version across the workspace via syncpack so the singleton can never
|
|
83
|
+
skew (react and react-dom must match exactly).
|
|
84
|
+
|
|
85
|
+
The React 19 removed-API surface was audited - the codebase used only no-arg
|
|
86
|
+
`useRef()` (now `useRef<T | undefined>(undefined)`); no `ReactDOM.render`,
|
|
87
|
+
legacy context, string refs, or function-component `defaultProps`. This also
|
|
88
|
+
clears the `IMPORT_IS_UNDEFINED` build warnings for `React.use` /
|
|
89
|
+
`React.useOptimistic` (react-router 7 feature-detection), which React 19 exports.
|
|
90
|
+
|
|
91
|
+
The downstream `*-frontend` packages (and `@checkstack/infrastructure-common`)
|
|
92
|
+
receive only the mechanical `react` dependency bump (`patch`); the framework
|
|
93
|
+
packages carrying the shared-singleton change are bumped `minor`.
|
|
94
|
+
|
|
95
|
+
- Updated dependencies [9d8961c]
|
|
96
|
+
- Updated dependencies [fb705df]
|
|
97
|
+
- @checkstack/ui@1.15.0
|
|
98
|
+
- @checkstack/frontend-api@0.8.0
|
|
99
|
+
- @checkstack/ai-common@0.1.2
|
|
100
|
+
- @checkstack/common@0.14.1
|
|
101
|
+
- @checkstack/integration-common@0.7.2
|
|
102
|
+
|
|
3
103
|
## 0.1.3
|
|
4
104
|
|
|
5
105
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/ai-frontend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.tsx",
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@checkstack/ai-common": "0.1.2",
|
|
18
18
|
"@checkstack/common": "0.14.1",
|
|
19
|
-
"@checkstack/frontend-api": "0.
|
|
19
|
+
"@checkstack/frontend-api": "0.8.0",
|
|
20
20
|
"@checkstack/integration-common": "0.7.2",
|
|
21
|
-
"@checkstack/ui": "1.
|
|
21
|
+
"@checkstack/ui": "1.15.0",
|
|
22
22
|
"lucide-react": "^1.17.0",
|
|
23
|
-
"react": "
|
|
23
|
+
"react": "19.2.7",
|
|
24
24
|
"react-router-dom": "^7.16.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"typescript": "^5.0.0",
|
|
28
|
-
"@types/react": "^
|
|
28
|
+
"@types/react": "^19.0.0",
|
|
29
29
|
"@checkstack/tsconfig": "0.0.7",
|
|
30
|
-
"@checkstack/scripts": "0.
|
|
30
|
+
"@checkstack/scripts": "0.6.0"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/src/lib/use-chat-turn.ts
CHANGED
|
@@ -18,6 +18,20 @@ function newId(): string {
|
|
|
18
18
|
: `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The operator's IANA timezone (e.g. "Europe/Berlin"), sent with every turn so
|
|
23
|
+
* the backend prompt can resolve bare times like "22:00" into the right offset.
|
|
24
|
+
* The browser is the authoritative source of the user's wall clock. Returns
|
|
25
|
+
* undefined where `Intl` is unavailable (the backend then falls back to UTC).
|
|
26
|
+
*/
|
|
27
|
+
function browserTimeZone(): string | undefined {
|
|
28
|
+
try {
|
|
29
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
/**
|
|
22
36
|
* React hook that drives ONE streaming chat turn against /api/ai/chat. The
|
|
23
37
|
* heavy lifting (SSE parsing, state folding) lives in DOM-free, unit-tested
|
|
@@ -34,7 +48,7 @@ export function useChatTurn({
|
|
|
34
48
|
const [messages, setMessages] = useState<ChatMessage[]>(initialMessages);
|
|
35
49
|
const [streaming, setStreaming] = useState(false);
|
|
36
50
|
const [error, setError] = useState<string | undefined>();
|
|
37
|
-
const aborter = useRef<AbortController | undefined>();
|
|
51
|
+
const aborter = useRef<AbortController | undefined>(undefined);
|
|
38
52
|
|
|
39
53
|
// Shared streaming driver: POST `body` to /chat, start a fresh assistant
|
|
40
54
|
// message (optionally appending a user message first), and fold the SSE
|
|
@@ -65,7 +79,9 @@ export function useChatTurn({
|
|
|
65
79
|
.fetch("/chat", {
|
|
66
80
|
method: "POST",
|
|
67
81
|
headers: { "content-type": "application/json" },
|
|
68
|
-
|
|
82
|
+
// Stamp the operator's timezone on every turn (user message AND
|
|
83
|
+
// confirm-card decision) so the model resolves bare times correctly.
|
|
84
|
+
body: JSON.stringify({ timeZone: browserTimeZone(), ...body }),
|
|
69
85
|
signal: controller.signal,
|
|
70
86
|
});
|
|
71
87
|
if (!response.ok || !response.body) {
|
package/src/pages/ChatPage.tsx
CHANGED
|
@@ -206,6 +206,16 @@ export function ChatPage() {
|
|
|
206
206
|
const [copiedError, setCopiedError] = useState(false);
|
|
207
207
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
208
208
|
|
|
209
|
+
// The operator's browser timezone, surfaced as a hint and sent with each turn
|
|
210
|
+
// so the assistant resolves bare times ("22:00") in this zone by default.
|
|
211
|
+
const browserTimeZone = useMemo<string | undefined>(() => {
|
|
212
|
+
try {
|
|
213
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
214
|
+
} catch {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
209
219
|
const {
|
|
210
220
|
messages,
|
|
211
221
|
setMessages,
|
|
@@ -445,8 +455,12 @@ export function ChatPage() {
|
|
|
445
455
|
title="AI assistant"
|
|
446
456
|
subtitle="Chat with Checkstack's built-in assistant. It can read incidents, health checks, and anomalies, and propose automations for you to confirm."
|
|
447
457
|
icon={Sparkles}
|
|
458
|
+
fillHeight
|
|
448
459
|
>
|
|
449
|
-
|
|
460
|
+
{/* `flex-1 min-h-0` fills the fillHeight content area (no viewport math),
|
|
461
|
+
so only the message list scrolls - the page itself never does, even
|
|
462
|
+
when the subtitle wraps. */}
|
|
463
|
+
<div className="grid grid-cols-1 md:grid-cols-[260px_1fr] gap-4 flex-1 min-h-0">
|
|
450
464
|
{/* Conversation sidebar */}
|
|
451
465
|
<Card className="overflow-hidden flex flex-col">
|
|
452
466
|
<CardContent className="p-2 flex flex-col gap-2 overflow-y-auto">
|
|
@@ -639,6 +653,12 @@ export function ChatPage() {
|
|
|
639
653
|
<Send className="w-4 h-4" />
|
|
640
654
|
</Button>
|
|
641
655
|
</div>
|
|
656
|
+
{browserTimeZone ? (
|
|
657
|
+
<p className="px-2 pb-2 -mt-1 text-xs text-muted-foreground">
|
|
658
|
+
Times you mention are interpreted in your timezone (
|
|
659
|
+
{browserTimeZone}).
|
|
660
|
+
</p>
|
|
661
|
+
) : null}
|
|
642
662
|
</Card>
|
|
643
663
|
</div>
|
|
644
664
|
|