134 lines
5.6 KiB
JavaScript
134 lines
5.6 KiB
JavaScript
import { renderSlot } from "../../lib/slots.mjs";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
import { ChevronRight } from "lucide-react";
|
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
import { Streamdown } from "streamdown";
|
|
|
|
//#region src/components/chat/CopilotChatReasoningMessage.tsx
|
|
/**
|
|
* Formats an elapsed duration (in seconds) to a human-readable string.
|
|
*/
|
|
function formatDuration(seconds) {
|
|
if (seconds < 1) return "a few seconds";
|
|
if (seconds < 60) return `${Math.round(seconds)} seconds`;
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.round(seconds % 60);
|
|
if (secs === 0) return `${mins} minute${mins > 1 ? "s" : ""}`;
|
|
return `${mins}m ${secs}s`;
|
|
}
|
|
function CopilotChatReasoningMessage({ message, messages, isRunning, header, contentView, toggle, children, className, ...props }) {
|
|
const isLatest = messages?.[messages.length - 1]?.id === message.id;
|
|
const isStreaming = !!(isRunning && isLatest);
|
|
const hasContent = !!(message.content && message.content.length > 0);
|
|
const startTimeRef = useRef(null);
|
|
const [elapsed, setElapsed] = useState(0);
|
|
useEffect(() => {
|
|
if (isStreaming && startTimeRef.current === null) startTimeRef.current = Date.now();
|
|
if (!isStreaming && startTimeRef.current !== null) {
|
|
setElapsed((Date.now() - startTimeRef.current) / 1e3);
|
|
return;
|
|
}
|
|
if (!isStreaming) return;
|
|
const timer = setInterval(() => {
|
|
if (startTimeRef.current !== null) setElapsed((Date.now() - startTimeRef.current) / 1e3);
|
|
}, 1e3);
|
|
return () => clearInterval(timer);
|
|
}, [isStreaming]);
|
|
const [isOpen, setIsOpen] = useState(isStreaming);
|
|
useEffect(() => {
|
|
if (isStreaming) setIsOpen(true);
|
|
else setIsOpen(false);
|
|
}, [isStreaming]);
|
|
const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration(elapsed)}`;
|
|
const boundHeader = renderSlot(header, CopilotChatReasoningMessage.Header, {
|
|
isOpen,
|
|
label,
|
|
hasContent,
|
|
isStreaming,
|
|
onClick: hasContent ? () => setIsOpen((prev) => !prev) : void 0
|
|
});
|
|
const boundContent = renderSlot(contentView, CopilotChatReasoningMessage.Content, {
|
|
isStreaming,
|
|
hasContent,
|
|
children: message.content
|
|
});
|
|
const boundToggle = renderSlot(toggle, CopilotChatReasoningMessage.Toggle, {
|
|
isOpen,
|
|
children: boundContent
|
|
});
|
|
if (children) return /* @__PURE__ */ jsx("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
header: boundHeader,
|
|
contentView: boundContent,
|
|
toggle: boundToggle,
|
|
message,
|
|
messages,
|
|
isRunning
|
|
})
|
|
});
|
|
return /* @__PURE__ */ jsxs("div", {
|
|
className: twMerge("cpk:my-1", className),
|
|
"data-message-id": message.id,
|
|
...props,
|
|
children: [boundHeader, boundToggle]
|
|
});
|
|
}
|
|
(function(_CopilotChatReasoningMessage) {
|
|
_CopilotChatReasoningMessage.Header = ({ isOpen, label = "Thoughts", hasContent, isStreaming, className, children: headerChildren, ...headerProps }) => {
|
|
const isExpandable = !!hasContent;
|
|
return /* @__PURE__ */ jsxs("button", {
|
|
type: "button",
|
|
className: twMerge("cpk:inline-flex cpk:items-center cpk:gap-1 cpk:py-1 cpk:text-sm cpk:text-muted-foreground cpk:transition-colors cpk:select-none", isExpandable ? "cpk:hover:text-foreground cpk:cursor-pointer" : "cpk:cursor-default", className),
|
|
"aria-expanded": isExpandable ? isOpen : void 0,
|
|
...headerProps,
|
|
children: [
|
|
/* @__PURE__ */ jsx("span", {
|
|
className: "cpk:font-medium",
|
|
children: label
|
|
}),
|
|
isStreaming && !hasContent && /* @__PURE__ */ jsx("span", {
|
|
className: "cpk:inline-flex cpk:items-center cpk:ml-1",
|
|
children: /* @__PURE__ */ jsx("span", { className: "cpk:w-1.5 cpk:h-1.5 cpk:rounded-full cpk:bg-muted-foreground cpk:animate-pulse" })
|
|
}),
|
|
headerChildren,
|
|
isExpandable && /* @__PURE__ */ jsx(ChevronRight, { className: twMerge("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", isOpen && "cpk:rotate-90") })
|
|
]
|
|
});
|
|
};
|
|
_CopilotChatReasoningMessage.Content = ({ isStreaming, hasContent, className, children: contentChildren, ...contentProps }) => {
|
|
if (!hasContent && !isStreaming) return null;
|
|
return /* @__PURE__ */ jsx("div", {
|
|
className: twMerge("cpk:pb-2 cpk:pt-1", className),
|
|
...contentProps,
|
|
children: /* @__PURE__ */ jsxs("div", {
|
|
className: "cpk:text-sm cpk:text-muted-foreground",
|
|
children: [/* @__PURE__ */ jsx(Streamdown, { children: typeof contentChildren === "string" ? contentChildren : "" }), isStreaming && hasContent && /* @__PURE__ */ jsx("span", {
|
|
className: "cpk:inline-flex cpk:items-center cpk:ml-1 cpk:align-middle",
|
|
children: /* @__PURE__ */ jsx("span", { className: "cpk:w-2 cpk:h-2 cpk:rounded-full cpk:bg-muted-foreground cpk:animate-pulse-cursor" })
|
|
})]
|
|
})
|
|
});
|
|
};
|
|
_CopilotChatReasoningMessage.Toggle = ({ isOpen, className, children: toggleChildren, ...toggleProps }) => {
|
|
return /* @__PURE__ */ jsx("div", {
|
|
className: twMerge("cpk:grid cpk:transition-[grid-template-rows] cpk:duration-200 cpk:ease-in-out", className),
|
|
style: { gridTemplateRows: isOpen ? "1fr" : "0fr" },
|
|
...toggleProps,
|
|
children: /* @__PURE__ */ jsx("div", {
|
|
className: "cpk:overflow-hidden",
|
|
children: toggleChildren
|
|
})
|
|
});
|
|
};
|
|
})(CopilotChatReasoningMessage || (CopilotChatReasoningMessage = {}));
|
|
CopilotChatReasoningMessage.Header.displayName = "CopilotChatReasoningMessage.Header";
|
|
CopilotChatReasoningMessage.Content.displayName = "CopilotChatReasoningMessage.Content";
|
|
CopilotChatReasoningMessage.Toggle.displayName = "CopilotChatReasoningMessage.Toggle";
|
|
var CopilotChatReasoningMessage_default = CopilotChatReasoningMessage;
|
|
|
|
//#endregion
|
|
export { CopilotChatReasoningMessage_default as default };
|
|
//# sourceMappingURL=CopilotChatReasoningMessage.mjs.map
|