@ai-matrx/chat 0.7.15 → 0.7.16

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 CHANGED
@@ -1,6 +1,18 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## 0.7.16
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/chat/v0.7.15..npm/chat/v0.7.16 -- apps/shared/chat`).
7
+ No source changes intended and no consumer action required.
8
+
3
9
  ## 0.7.15
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/chat/v0.7.14..npm/chat/v0.7.15 -- apps/shared/chat`).
13
+ No source changes intended and no consumer action required.
14
+
15
+ ## 0.7.14
4
16
  ### Unreleased
5
17
  ### An answer never paints header-first (spend register item 32, cold-load CLS at 1280/1440/1920)
6
18
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useConversationAbilities } from "../../redux/execution-system/host-conversations/conversation-abilities.js";
4
- import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { Fragment, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
6
6
  import { loadConversation } from "../../redux/execution-system/thunks/load-conversation.thunk.js";
7
7
  import { StaleDataNotice } from "../../../host/ui-slots.js";
@@ -48,6 +48,8 @@ import { FollowUpSuggestions } from "./conversation-tools/FollowUpSuggestions.js
48
48
  import { groupMessageIds, groupsToRender } from "./conversation-tools/pinned-filter.js";
49
49
  import { loadFullConversationHistory } from "../../conversation-export/load-full-history.js";
50
50
  import { useMessageListInteractions } from "./conversation-tools/useMessageListInteractions.js";
51
+ import { useTranscriptLayout } from "../shared/transcript-layout.js";
52
+ import { timeGroupLabel, timeGroupStarts } from "./time-groups.js";
51
53
  import { Button } from "@ai-matrx/design-system/controls";
52
54
  function AgentConversationDisplay({
53
55
  conversationId,
@@ -132,6 +134,17 @@ function AgentConversationDisplay({
132
134
  pinnedOnly,
133
135
  pinned: pinnedIds
134
136
  });
137
+ const { timestamps } = useTranscriptLayout();
138
+ const groupTimes = useMemo(() => {
139
+ if (timestamps !== "grouped") return null;
140
+ const at = new Map(messages.map((m) => [m.id, Date.parse(m.createdAt)]));
141
+ const timed = visibleGroups.map((g) => {
142
+ const first = groupMessageIds(g).map((id) => at.get(id)).find((t) => t !== void 0 && Number.isFinite(t));
143
+ return { key: g.key, atMs: first ?? null };
144
+ });
145
+ const starts = timeGroupStarts(timed);
146
+ return new Map(timed.filter((t) => starts.has(t.key) && t.atMs !== null).map((t) => [t.key, t.atMs]));
147
+ }, [timestamps, messages, visibleGroups]);
135
148
  let latestAssistantKey;
136
149
  for (let i = displayGroups.length - 1; i >= 0; i--) {
137
150
  if (displayGroups[i].kind === "assistant") {
@@ -392,9 +405,19 @@ function AgentConversationDisplay({
392
405
  "aria-label": "Conversation",
393
406
  className: `${spacingClass} p-2 scrollbar-hide ${bottomPinned ? "min-h-full flex flex-col justify-end" : ""}`,
394
407
  children: [
395
- visibleGroups.map(
396
- (group, index) => wrapGroup(group, index, renderGroupBody(group))
397
- ),
408
+ visibleGroups.map((group, index) => {
409
+ const at = groupTimes?.get(group.key);
410
+ if (at === void 0) return wrapGroup(group, index, renderGroupBody(group));
411
+ const label = timeGroupLabel(at);
412
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
413
+ /* @__PURE__ */ jsxs("div", { "data-time-group": "", className: "pt-2 text-center text-[11px] leading-[14px] text-muted-foreground", role: "separator", "aria-label": `${label.day} ${label.time}`, children: [
414
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: label.day }),
415
+ " ",
416
+ label.time
417
+ ] }),
418
+ wrapGroup(group, index, renderGroupBody(group))
419
+ ] }, group.key);
420
+ }),
398
421
  pinnedOnly && visibleGroups.length === 0 && /* @__PURE__ */ jsxs("div", { className: "py-10 text-center text-sm text-muted-foreground", children: [
399
422
  "Nothing pinned in this conversation.",
400
423
  " ",
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Messages' time grouping (the transcript layout's `timestamps: "grouped"`): a centred line over the
3
+ * first message and over any message that follows an hour's quiet — "Today 9:41 AM",
4
+ * "Yesterday 4:02 PM", "Monday 9:41 AM" within the week, "Oct 6, 2026 at 9:41 AM" before it.
5
+ */
6
+ /** Quiet this long between two messages starts a new time group (Messages: about an hour). */
7
+ export declare const TIME_GROUP_GAP_MS: number;
8
+ /** The keys of the groups that open a time group: the first, and each after `gapMs` of quiet. */
9
+ export declare function timeGroupStarts(groups: ReadonlyArray<{
10
+ key: string;
11
+ atMs: number | null;
12
+ }>, gapMs?: number): Set<string>;
13
+ /** The line's two parts, Messages' way: the day (drawn bold) and the time. */
14
+ export declare function timeGroupLabel(atMs: number, nowMs?: number, locale?: string): {
15
+ day: string;
16
+ time: string;
17
+ };
@@ -0,0 +1,29 @@
1
+ const TIME_GROUP_GAP_MS = 60 * 6e4;
2
+ function timeGroupStarts(groups, gapMs = TIME_GROUP_GAP_MS) {
3
+ const starts = /* @__PURE__ */ new Set();
4
+ let last = null;
5
+ for (const g of groups) {
6
+ if (g.atMs === null) continue;
7
+ if (last === null || g.atMs - last >= gapMs) starts.add(g.key);
8
+ last = g.atMs;
9
+ }
10
+ return starts;
11
+ }
12
+ const startOfDay = (ms) => {
13
+ const d = new Date(ms);
14
+ d.setHours(0, 0, 0, 0);
15
+ return d.getTime();
16
+ };
17
+ function timeGroupLabel(atMs, nowMs = Date.now(), locale) {
18
+ const time = new Date(atMs).toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit" });
19
+ const days = Math.round((startOfDay(nowMs) - startOfDay(atMs)) / 864e5);
20
+ if (days <= 0) return { day: "Today", time };
21
+ if (days === 1) return { day: "Yesterday", time };
22
+ if (days < 7) return { day: new Date(atMs).toLocaleDateString(locale, { weekday: "long" }), time };
23
+ return { day: new Date(atMs).toLocaleDateString(locale, { month: "short", day: "numeric", year: "numeric" }), time: `at ${time}` };
24
+ }
25
+ export {
26
+ TIME_GROUP_GAP_MS,
27
+ timeGroupLabel,
28
+ timeGroupStarts
29
+ };
@@ -22,6 +22,12 @@ export interface TranscriptLayout {
22
22
  align?: "center" | "start";
23
23
  userMessage?: "card" | "compact" | "bubble";
24
24
  answerActions?: "shown" | "latest" | "hover";
25
+ /**
26
+ * When each message was sent: `hover` (the web chat: the time at the end of an answer's action
27
+ * row, shown on hover) or `grouped` (Messages, Xcode's coding assistant: a centred "Today 9:41 AM"
28
+ * line over the first message and over any that follow an hour's quiet).
29
+ */
30
+ timestamps?: "hover" | "grouped";
25
31
  }
26
32
  export declare const DEFAULT_TRANSCRIPT_LAYOUT: Required<TranscriptLayout>;
27
33
  export declare function resolveTranscriptLayout(layout?: TranscriptLayout): Required<TranscriptLayout>;
@@ -7,7 +7,8 @@ const DEFAULT_TRANSCRIPT_LAYOUT = {
7
7
  width: "reading",
8
8
  align: "center",
9
9
  userMessage: "compact",
10
- answerActions: "hover"
10
+ answerActions: "hover",
11
+ timestamps: "hover"
11
12
  };
12
13
  const TranscriptLayoutContext = createContext(DEFAULT_TRANSCRIPT_LAYOUT);
13
14
  function resolveTranscriptLayout(layout) {
@@ -19,10 +20,10 @@ function transcriptColumnClass(layout) {
19
20
  return ASSISTANT_MESSAGE_COLUMN_CLASS;
20
21
  }
21
22
  function TranscriptLayoutProvider(props) {
22
- const { width, align, userMessage, answerActions } = resolveTranscriptLayout(props.layout);
23
+ const { width, align, userMessage, answerActions, timestamps } = resolveTranscriptLayout(props.layout);
23
24
  const value = useMemo(
24
- () => ({ width, align, userMessage, answerActions }),
25
- [width, align, userMessage, answerActions]
25
+ () => ({ width, align, userMessage, answerActions, timestamps }),
26
+ [width, align, userMessage, answerActions, timestamps]
26
27
  );
27
28
  return createElement(TranscriptLayoutContext.Provider, { value }, props.children);
28
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.7.15",
3
+ "version": "0.7.16",
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",
@@ -4684,14 +4684,12 @@
4684
4684
  "@ai-matrx/alchemy": "latest",
4685
4685
  "@ai-matrx/associations": "latest",
4686
4686
  "@ai-matrx/browser-audio": "latest",
4687
+ "@ai-matrx/agents": "latest",
4687
4688
  "@ai-matrx/canvas": "latest",
4688
4689
  "@ai-matrx/content-ir": "latest",
4689
- "@ai-matrx/agents": "latest",
4690
- "@ai-matrx/content-ir-react": "latest",
4691
- "@ai-matrx/data": "latest",
4692
- "@ai-matrx/diff": "latest",
4693
4690
  "@ai-matrx/design-system": "latest",
4694
4691
  "@ai-matrx/icons": "latest",
4692
+ "@ai-matrx/data": "latest",
4695
4693
  "@ai-matrx/kit": "latest",
4696
4694
  "@ai-matrx/media": "latest",
4697
4695
  "@ai-matrx/print": "latest",
@@ -4699,7 +4697,9 @@
4699
4697
  "@ai-matrx/records": "latest",
4700
4698
  "@ai-matrx/records-ui": "latest",
4701
4699
  "@ai-matrx/rich-content": "latest",
4702
- "@ai-matrx/rich-editor": "latest"
4700
+ "@ai-matrx/rich-editor": "latest",
4701
+ "@ai-matrx/diff": "latest",
4702
+ "@ai-matrx/content-ir-react": "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": "fe30f9706e88c2c101f1c3ab4beec866c90c2ec1"
4821
+ "gitHead": "6158809f49ab5fe092f5dc6c56c60898eb0f92a6"
4822
4822
  }