@assistant-ui/react
Version:
Open-source TypeScript/React library for building production-grade AI chat experiences
337 lines (336 loc) • 10.4 kB
JavaScript
"use client";
import { PartByIndexProvider } from "../../context/providers/PartByIndexProvider.js";
import { TextMessagePartProvider } from "../../context/providers/TextMessagePartProvider.js";
import { MessagePartPrimitiveText } from "../messagePart/MessagePartText.js";
import { MessagePartPrimitiveImage } from "../messagePart/MessagePartImage.js";
import { MessagePartPrimitiveInProgress } from "../messagePart/MessagePartInProgress.js";
import { getMessagePartKeys } from "@assistant-ui/core/internal";
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
import { MessagePartComponent } from "@assistant-ui/core/react";
import { memo } from "@assistant-ui/tap/react-shim";
import { useAuiState } from "@assistant-ui/store";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
// src/primitives/message/MessagePartsGrouped.tsx
/**
* Groups message parts by their parent ID.
* Parts without a parent ID appear in their chronological position as individual groups.
* Parts with the same parent ID are grouped together at the position of their first occurrence.
*/
const groupMessagePartsByParentId = (parts) => {
const groupMap = /* @__PURE__ */ new Map();
for (let i = 0; i < parts.length; i++) {
const groupId = parts[i]?.parentId ?? i;
const indices = groupMap.get(groupId) ?? [];
indices.push(i);
groupMap.set(groupId, indices);
}
const groups = [];
for (const [groupId, indices] of groupMap) {
const groupKey = typeof groupId === "string" ? groupId : void 0;
groups.push({
groupKey,
indices
});
}
return groups;
};
const useMessagePartsGrouped = (groupingFunction) => {
const $ = c(9);
const parts = useAuiState(_temp);
let t0;
bb0: {
if (parts.length === 0) {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [];
$[0] = t1;
} else t1 = $[0];
t0 = t1;
break bb0;
}
let t1;
if ($[1] !== groupingFunction || $[2] !== parts) {
t1 = groupingFunction(parts);
$[1] = groupingFunction;
$[2] = parts;
$[3] = t1;
} else t1 = $[3];
t0 = t1;
}
const groups = t0;
let t1;
if ($[4] !== parts) {
t1 = getMessagePartKeys(parts);
$[4] = parts;
$[5] = t1;
} else t1 = $[5];
let t2;
if ($[6] !== groups || $[7] !== t1) {
t2 = {
groups,
partKeys: t1
};
$[6] = groups;
$[7] = t1;
$[8] = t2;
} else t2 = $[8];
return t2;
};
const defaultComponents = {
Text: () => /* @__PURE__ */ jsxs("p", {
style: { whiteSpace: "pre-line" },
children: [/* @__PURE__ */ jsx(MessagePartPrimitiveText, {}), /* @__PURE__ */ jsx(MessagePartPrimitiveInProgress, { children: /* @__PURE__ */ jsx("span", {
style: { fontFamily: "revert" },
children: " ●"
}) })]
}),
Reasoning: () => null,
Source: () => null,
Image: () => /* @__PURE__ */ jsx(MessagePartPrimitiveImage, {}),
File: () => null,
Unstable_Audio: () => null,
Group: ({ children }) => children
};
const MessagePart = memo((t0) => {
const $ = c(5);
const { partIndex, components } = t0;
let t1;
if ($[0] !== components) {
t1 = /* @__PURE__ */ jsx(MessagePartComponent, { components });
$[0] = components;
$[1] = t1;
} else t1 = $[1];
let t2;
if ($[2] !== partIndex || $[3] !== t1) {
t2 = /* @__PURE__ */ jsx(PartByIndexProvider, {
index: partIndex,
children: t1
});
$[2] = partIndex;
$[3] = t1;
$[4] = t2;
} else t2 = $[4];
return t2;
}, (prev, next) => prev.partIndex === next.partIndex && prev.components?.Text === next.components?.Text && prev.components?.Reasoning === next.components?.Reasoning && prev.components?.Source === next.components?.Source && prev.components?.Image === next.components?.Image && prev.components?.File === next.components?.File && prev.components?.Unstable_Audio === next.components?.Unstable_Audio && prev.components?.tools === next.components?.tools && prev.components?.data === next.components?.data);
const EmptyPartFallback = (t0) => {
const $ = c(6);
const { status, component: Component } = t0;
const t1 = status.type === "running";
let t2;
if ($[0] !== Component || $[1] !== status) {
t2 = /* @__PURE__ */ jsx(Component, {
type: "text",
text: "",
status
});
$[0] = Component;
$[1] = status;
$[2] = t2;
} else t2 = $[2];
let t3;
if ($[3] !== t1 || $[4] !== t2) {
t3 = /* @__PURE__ */ jsx(TextMessagePartProvider, {
text: "",
isRunning: t1,
children: t2
});
$[3] = t1;
$[4] = t2;
$[5] = t3;
} else t3 = $[5];
return t3;
};
const COMPLETE_STATUS = Object.freeze({ type: "complete" });
const EmptyPartsImpl = (t0) => {
const $ = c(6);
const { components } = t0;
const status = useAuiState(_temp2);
if (components?.Empty) {
let t1;
if ($[0] !== components.Empty || $[1] !== status) {
t1 = /* @__PURE__ */ jsx(components.Empty, { status });
$[0] = components.Empty;
$[1] = status;
$[2] = t1;
} else t1 = $[2];
return t1;
}
const t1 = components?.Text ?? defaultComponents.Text;
let t2;
if ($[3] !== status || $[4] !== t1) {
t2 = /* @__PURE__ */ jsx(EmptyPartFallback, {
status,
component: t1
});
$[3] = status;
$[4] = t1;
$[5] = t2;
} else t2 = $[5];
return t2;
};
const EmptyParts = memo(EmptyPartsImpl, (prev, next) => prev.components?.Empty === next.components?.Empty && prev.components?.Text === next.components?.Text);
/**
* Renders the parts of a message grouped by a custom grouping function.
*
* This component allows you to group message parts based on any criteria you define.
* The grouping function receives all message parts and returns an array of groups,
* where each group has a key and an array of part indices.
*
* @deprecated Prefer `<MessagePrimitive.GroupedParts>` for adjacent
* grouping — it dispatches all rendering through one `switch (part.type)`
* and supports nested group paths. Keep this primitive only for
* non-adjacent clustering (e.g., gathering parts with the same parent-id
* across the message).
*
* @example
* ```tsx
* // Group by parent ID (default behavior)
* <MessagePrimitive.Unstable_PartsGrouped
* components={{
* Text: ({ text }) => <p className="message-text">{text}</p>,
* Image: ({ image }) => <img src={image} alt="Message image" />,
* Group: ({ groupKey, indices, children }) => {
* if (!groupKey) return <>{children}</>;
* return (
* <div className="parent-group border rounded p-4">
* <h4>Parent ID: {groupKey}</h4>
* {children}
* </div>
* );
* }
* }}
* />
* ```
*/
const MessagePrimitiveUnstable_PartsGrouped = (t0) => {
const $ = c(21);
const { groupingFunction, components } = t0;
const contentLength = useAuiState(_temp3);
const { groups: messageGroups, partKeys } = useMessagePartsGrouped(groupingFunction);
let t1;
bb0: {
if (contentLength === 0) {
let t2;
if ($[0] !== components) {
t2 = /* @__PURE__ */ jsx(EmptyParts, { components });
$[0] = components;
$[1] = t2;
} else t2 = $[1];
t1 = t2;
break bb0;
}
const t2 = components?.Text ?? defaultComponents.Text;
const t3 = components?.Reasoning ?? defaultComponents.Reasoning;
const t4 = components?.Image ?? defaultComponents.Image;
const t5 = components?.Source ?? defaultComponents.Source;
const t6 = components?.File ?? defaultComponents.File;
const t7 = components?.Unstable_Audio ?? defaultComponents.Unstable_Audio;
let t8;
if ($[2] !== components || $[3] !== t2 || $[4] !== t3 || $[5] !== t4 || $[6] !== t5 || $[7] !== t6 || $[8] !== t7) {
t8 = {
...components,
Text: t2,
Reasoning: t3,
Image: t4,
Source: t5,
File: t6,
Unstable_Audio: t7
};
$[2] = components;
$[3] = t2;
$[4] = t3;
$[5] = t4;
$[6] = t5;
$[7] = t6;
$[8] = t7;
$[9] = t8;
} else t8 = $[9];
const leafComponents = t8;
let t9;
if ($[10] !== components?.Group || $[11] !== leafComponents || $[12] !== messageGroups || $[13] !== partKeys) {
let t10;
if ($[15] !== components?.Group || $[16] !== leafComponents || $[17] !== partKeys) {
t10 = (group, groupIndex) => {
const GroupComponent = components?.Group ?? defaultComponents.Group;
const identity = partKeys[group.indices[0]];
return /* @__PURE__ */ jsx(GroupComponent, {
groupKey: group.groupKey,
indices: group.indices,
children: group.indices.map((partIndex) => /* @__PURE__ */ jsx(MessagePart, {
partIndex,
components: leafComponents
}, partKeys[partIndex]))
}, JSON.stringify([
"group",
group.groupKey ?? null,
identity?.includes(":") ? `id:${identity}` : groupIndex
]));
};
$[15] = components?.Group;
$[16] = leafComponents;
$[17] = partKeys;
$[18] = t10;
} else t10 = $[18];
t9 = messageGroups.map(t10);
$[10] = components?.Group;
$[11] = leafComponents;
$[12] = messageGroups;
$[13] = partKeys;
$[14] = t9;
} else t9 = $[14];
t1 = t9;
}
const partsElements = t1;
let t2;
if ($[19] !== partsElements) {
t2 = /* @__PURE__ */ jsx(Fragment, { children: partsElements });
$[19] = partsElements;
$[20] = t2;
} else t2 = $[20];
return t2;
};
MessagePrimitiveUnstable_PartsGrouped.displayName = "MessagePrimitive.Unstable_PartsGrouped";
/**
* Renders the parts of a message grouped by their parent ID.
* This is a convenience wrapper around Unstable_PartsGrouped with parent ID grouping.
*
* @deprecated Use MessagePrimitive.Unstable_PartsGrouped instead for more flexibility
*/
const MessagePrimitiveUnstable_PartsGroupedByParentId = (t0) => {
const $ = c(6);
let components;
let props;
if ($[0] !== t0) {
({components, ...props} = t0);
$[0] = t0;
$[1] = components;
$[2] = props;
} else {
components = $[1];
props = $[2];
}
let t1;
if ($[3] !== components || $[4] !== props) {
t1 = /* @__PURE__ */ jsx(MessagePrimitiveUnstable_PartsGrouped, {
...props,
components,
groupingFunction: groupMessagePartsByParentId
});
$[3] = components;
$[4] = props;
$[5] = t1;
} else t1 = $[5];
return t1;
};
MessagePrimitiveUnstable_PartsGroupedByParentId.displayName = "MessagePrimitive.Unstable_PartsGroupedByParentId";
function _temp(s) {
return s.message.parts;
}
function _temp2(s) {
return s.message.status ?? COMPLETE_STATUS;
}
function _temp3(s) {
return s.message.parts.length;
}
export { MessagePrimitiveUnstable_PartsGrouped, MessagePrimitiveUnstable_PartsGroupedByParentId };
//# sourceMappingURL=MessagePartsGrouped.js.map