@ai-matrx/chat 0.7.13 → 0.7.14
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 +12 -0
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.d.ts +6 -1
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +8 -2
- package/dist/ui/markdown-stream/MarkdownStream.d.ts +2 -1
- package/dist/ui/markdown-stream/MarkdownStream.js +5 -20
- package/dist/ui/markdown-stream/markdown-engine.d.ts +19 -0
- package/dist/ui/markdown-stream/markdown-engine.js +44 -0
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.7.14
|
|
4
|
+
### Unreleased
|
|
5
|
+
### An answer never paints header-first (spend register item 32, cold-load CLS at 1280/1440/1920)
|
|
6
|
+
|
|
7
|
+
- **`AgentAssistantMessage`** renders nothing until the markdown engine chunk has settled (`useMarkdownEngineSettled`, new internal
|
|
8
|
+
`ui/markdown-stream/markdown-engine`), so every message of a conversation appears in the same commit. A chunk that fails to load is a
|
|
9
|
+
`console.error` and the message renders anyway. 0.7.12 preloaded the chunk but a message could still render before it landed
|
|
10
|
+
(a 82 px header-only block growing to 385 px: CLS up to 0.32 on a cold load).
|
|
11
|
+
- `MarkdownStream` keeps the loaded engine and renders it synchronously on a new instance's first render.
|
|
12
|
+
- Test: `ui/markdown-stream/__tests__/markdown-engine-first-paint.test.tsx`.
|
|
13
|
+
- **Consumer action:** none.
|
|
14
|
+
|
|
3
15
|
## 0.7.13
|
|
4
16
|
### Unreleased
|
|
5
17
|
### a refused answer fails the run's own row (lane F13)
|
|
@@ -41,4 +41,9 @@ export interface AgentAssistantMessageProps {
|
|
|
41
41
|
*/
|
|
42
42
|
isTurnAnswer?: boolean;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* First-paint rule (item 32): an answer never paints as a header-only block that grows when the
|
|
46
|
+
* markdown engine lands, so a message renders nothing until the engine has settled. Every message of
|
|
47
|
+
* a conversation waits on the same flag, so they appear in the same commit.
|
|
48
|
+
*/
|
|
49
|
+
export declare function AgentAssistantMessage(props: AgentAssistantMessageProps): import("react").JSX.Element | null;
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
useState,
|
|
7
7
|
useEffect
|
|
8
8
|
} from "react";
|
|
9
|
-
import MarkdownStream
|
|
9
|
+
import MarkdownStream from "../../../../ui/markdown-stream/MarkdownStream.js";
|
|
10
|
+
import { preloadMarkdownStream, useMarkdownEngineSettled } from "../../../../ui/markdown-stream/markdown-engine.js";
|
|
10
11
|
import "../rich-document/chat-message-source.js";
|
|
11
12
|
import { RecordAnnotations, annotationRecordOf } from "@ai-matrx/rich-content/host/app-bindings";
|
|
12
13
|
import { useAppDispatch, useAppSelector } from "../../../../store/hooks.js";
|
|
@@ -106,7 +107,12 @@ function providerControlErrorMessage(err) {
|
|
|
106
107
|
if (err instanceof Error) return err.message;
|
|
107
108
|
return "Provider control failed";
|
|
108
109
|
}
|
|
109
|
-
function AgentAssistantMessage({
|
|
110
|
+
function AgentAssistantMessage(props) {
|
|
111
|
+
const engineSettled = useMarkdownEngineSettled();
|
|
112
|
+
if (!engineSettled) return null;
|
|
113
|
+
return /* @__PURE__ */ jsx(AgentAssistantMessageBody, { ...props });
|
|
114
|
+
}
|
|
115
|
+
function AgentAssistantMessageBody({
|
|
110
116
|
conversationId,
|
|
111
117
|
requestId,
|
|
112
118
|
messageId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AnswerEditRemarkMeta } from "../../agents/redux/execution-system/instance-resources/remarks.js";
|
|
2
|
+
import { preloadMarkdownStream } from "./markdown-engine.js";
|
|
2
3
|
import { TypedStreamEvent } from "@ai-matrx/rich-content/display/chat-markdown/types";
|
|
3
4
|
import type { ServerProcessedBlock } from "./EnhancedChatMarkdown.js";
|
|
4
5
|
/**
|
|
@@ -83,7 +84,7 @@ export interface MarkdownStreamProps {
|
|
|
83
84
|
*/
|
|
84
85
|
imagePolicy?: "self" | "other" | "ai" | "inherit";
|
|
85
86
|
}
|
|
86
|
-
export declare function preloadMarkdownStream(): Promise<void>;
|
|
87
87
|
declare function MarkdownStream(props: MarkdownStreamProps): import("react").FunctionComponentElement<import("react").SuspenseProps> | import("react").ReactElement<MarkdownStreamProps, string | import("react").JSXElementConstructor<any>> | null;
|
|
88
|
+
export { preloadMarkdownStream };
|
|
88
89
|
export { MarkdownStream };
|
|
89
90
|
export default MarkdownStream;
|
|
@@ -1,27 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createElement, lazy, Suspense
|
|
2
|
+
import { createElement, lazy, Suspense } from "react";
|
|
3
|
+
import { loadedMarkdownImpl, preloadMarkdownStream, useIsMarkdownClient } from "./markdown-engine.js";
|
|
3
4
|
const MarkdownStreamImpl = lazy(() => import("./MarkdownStreamImpl.js"));
|
|
4
|
-
let loadedImpl = null;
|
|
5
|
-
let loading = null;
|
|
6
|
-
const readyListeners = /* @__PURE__ */ new Set();
|
|
7
|
-
function preloadMarkdownStream() {
|
|
8
|
-
if (typeof window === "undefined") return Promise.resolve();
|
|
9
|
-
loading ??= import("./MarkdownStreamImpl.js").then((m) => {
|
|
10
|
-
loadedImpl = m.default;
|
|
11
|
-
for (const notify of readyListeners) notify();
|
|
12
|
-
});
|
|
13
|
-
return loading;
|
|
14
|
-
}
|
|
15
|
-
const subscribeReady = (notify) => {
|
|
16
|
-
readyListeners.add(notify);
|
|
17
|
-
return () => readyListeners.delete(notify);
|
|
18
|
-
};
|
|
19
|
-
const clientReady = () => true;
|
|
20
|
-
const serverNotReady = () => false;
|
|
21
5
|
function MarkdownStream(props) {
|
|
22
|
-
const client =
|
|
6
|
+
const client = useIsMarkdownClient();
|
|
23
7
|
if (!client) return null;
|
|
24
|
-
|
|
8
|
+
const Loaded = loadedMarkdownImpl();
|
|
9
|
+
if (Loaded) return createElement(Loaded, props);
|
|
25
10
|
void preloadMarkdownStream();
|
|
26
11
|
return createElement(Suspense, { fallback: null }, createElement(MarkdownStreamImpl, props));
|
|
27
12
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The markdown engine's load state (spend register item 32 — first-paint rule).
|
|
3
|
+
*
|
|
4
|
+
* A persisted answer must paint WITH its markdown, never as an empty block that grows once the engine
|
|
5
|
+
* chunk lands (a measured CLS of 0.06-0.32 on the run page). So:
|
|
6
|
+
* - `preloadMarkdownStream()` starts the chunk when a conversation surface's module evaluates;
|
|
7
|
+
* - the resolved component is kept here and rendered synchronously by `MarkdownStream`;
|
|
8
|
+
* - `useMarkdownEngineSettled()` lets an assistant message render NOTHING until the engine has loaded
|
|
9
|
+
* (or failed — a failure is logged and the message renders, never hangs), so every message of a
|
|
10
|
+
* conversation appears in the same commit instead of header-first.
|
|
11
|
+
* Client-only: the server snapshot is "not ready", so a server render never pulls the engine in.
|
|
12
|
+
*/
|
|
13
|
+
import { type ComponentType } from "react";
|
|
14
|
+
export declare function preloadMarkdownStream(): Promise<void>;
|
|
15
|
+
export declare const loadedMarkdownImpl: () => ComponentType<any> | null;
|
|
16
|
+
/** False on the server and during hydration; true on the client. */
|
|
17
|
+
export declare function useIsMarkdownClient(): boolean;
|
|
18
|
+
/** True once the engine chunk has loaded (or failed). False on the server and until then. */
|
|
19
|
+
export declare function useMarkdownEngineSettled(): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
let loadedImpl = null;
|
|
4
|
+
let settled = false;
|
|
5
|
+
let loading = null;
|
|
6
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7
|
+
const notify = () => {
|
|
8
|
+
for (const listener of listeners) listener();
|
|
9
|
+
};
|
|
10
|
+
function preloadMarkdownStream() {
|
|
11
|
+
if (typeof window === "undefined") return Promise.resolve();
|
|
12
|
+
loading ??= import("./MarkdownStreamImpl.js").then(
|
|
13
|
+
(m) => {
|
|
14
|
+
loadedImpl = m.default;
|
|
15
|
+
settled = true;
|
|
16
|
+
notify();
|
|
17
|
+
},
|
|
18
|
+
(error) => {
|
|
19
|
+
console.error("[chat] the markdown engine chunk failed to load; messages render without waiting for it", error);
|
|
20
|
+
settled = true;
|
|
21
|
+
notify();
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
return loading;
|
|
25
|
+
}
|
|
26
|
+
const loadedMarkdownImpl = () => loadedImpl;
|
|
27
|
+
const subscribe = (onChange) => {
|
|
28
|
+
listeners.add(onChange);
|
|
29
|
+
return () => {
|
|
30
|
+
listeners.delete(onChange);
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
function useIsMarkdownClient() {
|
|
34
|
+
return useSyncExternalStore(subscribe, () => true, () => false);
|
|
35
|
+
}
|
|
36
|
+
function useMarkdownEngineSettled() {
|
|
37
|
+
return useSyncExternalStore(subscribe, () => settled, () => false);
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
loadedMarkdownImpl,
|
|
41
|
+
preloadMarkdownStream,
|
|
42
|
+
useIsMarkdownClient,
|
|
43
|
+
useMarkdownEngineSettled
|
|
44
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.14",
|
|
4
4
|
"description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as explicit per-module subpaths @ai-matrx/chat/<domain>/<module> (no wildcards: scripts/public-modules.json) over a host contract (/host, /host/react) whose only required value is a db. One chat, one renderer: the old kernel entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) are retired.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -4682,24 +4682,24 @@
|
|
|
4682
4682
|
"uuid": "^14.0.2",
|
|
4683
4683
|
"zod": "^4.6.5",
|
|
4684
4684
|
"@ai-matrx/alchemy": "latest",
|
|
4685
|
-
"@ai-matrx/associations": "latest",
|
|
4686
4685
|
"@ai-matrx/browser-audio": "latest",
|
|
4687
|
-
"@ai-matrx/
|
|
4686
|
+
"@ai-matrx/agents": "latest",
|
|
4688
4687
|
"@ai-matrx/content-ir": "latest",
|
|
4689
4688
|
"@ai-matrx/content-ir-react": "latest",
|
|
4690
|
-
"@ai-matrx/
|
|
4689
|
+
"@ai-matrx/associations": "latest",
|
|
4690
|
+
"@ai-matrx/data": "latest",
|
|
4691
4691
|
"@ai-matrx/design-system": "latest",
|
|
4692
|
-
"@ai-matrx/diff": "latest",
|
|
4693
4692
|
"@ai-matrx/icons": "latest",
|
|
4694
4693
|
"@ai-matrx/kit": "latest",
|
|
4695
|
-
"@ai-matrx/
|
|
4694
|
+
"@ai-matrx/print": "latest",
|
|
4696
4695
|
"@ai-matrx/realtime": "latest",
|
|
4697
4696
|
"@ai-matrx/records": "latest",
|
|
4698
|
-
"@ai-matrx/
|
|
4697
|
+
"@ai-matrx/records-ui": "latest",
|
|
4699
4698
|
"@ai-matrx/rich-content": "latest",
|
|
4700
|
-
"@ai-matrx/print": "latest",
|
|
4701
4699
|
"@ai-matrx/rich-editor": "latest",
|
|
4702
|
-
"@ai-matrx/
|
|
4700
|
+
"@ai-matrx/diff": "latest",
|
|
4701
|
+
"@ai-matrx/canvas": "latest",
|
|
4702
|
+
"@ai-matrx/media": "latest"
|
|
4703
4703
|
},
|
|
4704
4704
|
"devDependencies": {
|
|
4705
4705
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
@@ -4818,5 +4818,5 @@
|
|
|
4818
4818
|
"check:side-effects": "node ../scripts/check-side-effects-listed.mjs . && node ../scripts/check-side-effects-listed.mjs --self-test",
|
|
4819
4819
|
"check:public-surface": "node ./scripts/sync-public-surface.mjs --check && node ./scripts/sync-public-surface.mjs --self-test"
|
|
4820
4820
|
},
|
|
4821
|
-
"gitHead": "
|
|
4821
|
+
"gitHead": "0e5372354ec84ed85b1779e1d99b6922a20f1798"
|
|
4822
4822
|
}
|