supermemory/apps/docs/snippets/slack-message.mdx
Prasanna721 434c86e2c3 fix docs image pipeline (#1411)
Keep docs asset URLs repo-root-relative and pass images as literal MDX children so Mintlify can compile them through OptimizedImage under the /docs mount.

Mintlify's official guidance says: “Image paths are root-relative from your docs repository.” It also says relative paths such as `./screenshot.png` are unsupported. See [Image embeds](https://www.mintlify.com/docs/create/image-embeds).

The existing `/images/...` paths were correct. The failure came from passing them through custom-component string props, which kept Mintlify from seeing those images during its MDX transform.

- fix hero, building-block, and Slack avatar images
- preserve Slack avatar clipping
- normalize Hermes and Company Brain icon sizing

Tested with Mintlify validation, broken-link checks, and browser checks across every changed route.
2026-08-04 04:55:50 +00:00

331 lines
14 KiB
Text

export const Mention = ({ children, self = false }) => (
<span
className="rounded px-1 py-px font-medium"
style={{
backgroundColor: self ? "rgba(18,100,163,0.16)" : "rgba(18,100,163,0.1)",
color: "#1264A3",
}}
>
@{children}
</span>
);
export const ChannelRef = ({ children }) => (
<span className="font-medium" style={{ color: "#1264A3" }}>
#{children}
</span>
);
export const SlackButton = ({ children, variant = "default" }) => (
<span
className="mr-2 mt-2 inline-flex items-center justify-center rounded-md px-3 py-1.5 text-sm font-semibold"
style={
variant === "primary"
? { backgroundColor: "#2f855a", color: "#ffffff" }
: { border: "1px solid rgba(150,150,150,0.4)" }
}
>
{children}
</span>
);
export const SlackUnfurl = ({ color = "#8a94a6", footer, children }) => (
<div className="mt-1 mb-0.5 pl-3" style={{ borderLeft: `3px solid ${color}` }}>
<div className="text-[15px] leading-normal text-gray-700 dark:text-zinc-300 [&_p]:m-0">
{children}
</div>
{footer ? (
<div className="mt-1 text-xs text-gray-400 dark:text-zinc-500">{footer}</div>
) : null}
</div>
);
export const SlackThread = ({
type = "channel",
channel = "general",
private: isPrivate = false,
members,
dmWith,
hasAvatar = false,
maxHeight = "70vh",
children,
}) => {
const childItems = Array.isArray(children) ? children : [children];
const dmAvatar = hasAvatar ? childItems[0] : null;
const threadMessages = hasAvatar ? childItems.slice(1) : children;
const composerLabel =
type === "dm"
? `Message ${dmWith?.name || "someone"}`
: type === "thread"
? "Reply..."
: `Message ${isPrivate ? "" : "#"}${channel.replace(/^#/, "")}`;
return (
<div
className="not-prose my-6 overflow-hidden rounded-lg border border-black/10 bg-white shadow-[0_1px_3px_rgba(0,0,0,0.08)] dark:border-white/10 dark:bg-[#1A1D21]"
style={{
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
}}
>
{type === "thread" ? (
<div className="flex items-center justify-between border-b border-black/10 px-4 py-3 dark:border-white/10">
<span className="text-[15px] font-bold text-gray-900 dark:text-zinc-50">Thread</span>
<div className="flex items-center gap-3 text-gray-400 dark:text-zinc-500">
<svg viewBox="0 0 18 18" className="h-4 w-4" fill="currentColor" aria-hidden="true">
<path d="M9.5 2.75L11.412 7.587L16.25 9.5L11.412 11.413L9.5 16.25L7.587 11.413L2.75 9.5L7.587 7.587L9.5 2.75Z" />
</svg>
<svg viewBox="0 0 18 18" className="h-4 w-4" fill="currentColor" aria-hidden="true">
<circle cx="4" cy="9" r="1.3" />
<circle cx="9" cy="9" r="1.3" />
<circle cx="14" cy="9" r="1.3" />
</svg>
<svg viewBox="0 0 18 18" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
<path d="M4 4l10 10M14 4L4 14" strokeLinecap="round" />
</svg>
</div>
</div>
) : type === "dm" ? (
<div className="flex items-center gap-2 border-b border-black/10 px-4 py-3 dark:border-white/10">
<div className="relative h-6 w-6 flex-none">
<span className="block h-full w-full overflow-hidden rounded-md [&_img]:h-full [&_img]:w-full [&_img]:object-cover">
{dmAvatar}
</span>
<span className="absolute -bottom-0.5 -right-0.5 h-2.5 w-2.5 rounded-full border-2 border-white bg-[#2BAC76] dark:border-[#1A1D21]" />
</div>
<span className="truncate text-[15px] font-bold text-gray-900 dark:text-zinc-50">
{dmWith?.name || "Direct message"}
</span>
</div>
) : (
<div className="flex items-center justify-between border-b border-black/10 px-4 py-3 dark:border-white/10">
<div className="flex min-w-0 items-center gap-1.5">
{isPrivate ? (
<svg viewBox="0 0 18 18" className="h-3.5 w-3.5 flex-none text-gray-400 dark:text-zinc-500" fill="currentColor" aria-hidden="true">
<path d="M9 1.75a3.25 3.25 0 00-3.25 3.25v1.5h-.5A1.25 1.25 0 004 7.75v6A1.25 1.25 0 005.25 15h7.5A1.25 1.25 0 0014 13.75v-6a1.25 1.25 0 00-1.25-1.25h-.5V5A3.25 3.25 0 009 1.75zm-1.75 4.75V5a1.75 1.75 0 113.5 0v1.5h-3.5z" />
</svg>
) : (
<svg viewBox="0 0 18 18" className="h-4 w-4 flex-none text-gray-400 dark:text-zinc-500" fill="currentColor" aria-hidden="true">
<path d="M7.2 1.5a.6.6 0 10-1.2 0l-.4 3H2.7a.6.6 0 000 1.2h2.7l-.5 3.6H2.1a.6.6 0 000 1.2h2.6l-.4 3a.6.6 0 101.2 0l.4-3h3.6l-.4 3a.6.6 0 101.2 0l.4-3h2.9a.6.6 0 000-1.2h-2.7l.5-3.6h3a.6.6 0 000-1.2h-2.8l.4-3a.6.6 0 10-1.2 0l-.4 3H6.8l.4-3zM6.5 6.7l3.6.0-.5 3.6H6l.5-3.6z" />
</svg>
)}
<span className="truncate text-[15px] font-bold text-gray-900 dark:text-zinc-50">
{channel.replace(/^#/, "")}
</span>
</div>
{members ? (
<div className="flex flex-none items-center gap-1 text-xs font-medium text-gray-500 dark:text-zinc-400">
<svg viewBox="0 0 18 18" className="h-3.5 w-3.5" fill="currentColor" aria-hidden="true">
<path d="M9 9.5a3.25 3.25 0 100-6.5 3.25 3.25 0 000 6.5zM3 15c0-2.5 2.5-4 6-4s6 1.5 6 4v.5H3V15z" />
</svg>
{members}
</div>
) : null}
</div>
)}
<div className="relative">
<div
ref={(el) => {
if (!el || !maxHeight) return;
const wrapper = el.parentElement;
const aboveHint = wrapper?.querySelector('[data-hint="above"]');
const belowHint = wrapper?.querySelector('[data-hint="below"]');
const update = () => {
const hasAbove = el.scrollTop > 4;
const hasBelow = el.scrollHeight - el.scrollTop - el.clientHeight > 4;
if (aboveHint) aboveHint.style.opacity = hasAbove ? "1" : "0";
if (belowHint) belowHint.style.opacity = hasBelow ? "1" : "0";
};
update();
el.addEventListener("scroll", update);
requestAnimationFrame(update);
}}
className="overflow-y-auto bg-white dark:bg-[#1A1D21]"
style={maxHeight ? { maxHeight } : undefined}
>
{threadMessages}
</div>
{maxHeight ? (
<div
data-hint="above"
className="pointer-events-none absolute inset-x-0 top-0 flex h-12 items-start justify-center pt-1.5 opacity-0 transition-opacity duration-150"
>
<div
className="absolute inset-0 dark:hidden"
style={{ background: "linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0))" }}
/>
<div
className="absolute inset-0 hidden dark:block"
style={{ background: "linear-gradient(to bottom, rgba(26,29,33,1), rgba(26,29,33,0))" }}
/>
<span className="relative rounded-full border border-black/10 bg-white px-2.5 py-1 text-xs font-medium text-gray-500 shadow-sm dark:border-white/10 dark:bg-[#222529] dark:text-zinc-400">
↑ More messages above
</span>
</div>
) : null}
{maxHeight ? (
<div
data-hint="below"
className="pointer-events-none absolute inset-x-0 bottom-0 flex h-12 items-end justify-center pb-1.5 opacity-0 transition-opacity duration-150"
>
<div
className="absolute inset-0 dark:hidden"
style={{ background: "linear-gradient(to top, rgba(255,255,255,1), rgba(255,255,255,0))" }}
/>
<div
className="absolute inset-0 hidden dark:block"
style={{ background: "linear-gradient(to top, rgba(26,29,33,1), rgba(26,29,33,0))" }}
/>
<span className="relative rounded-full border border-black/10 bg-white px-2.5 py-1 text-xs font-medium text-gray-500 shadow-sm dark:border-white/10 dark:bg-[#222529] dark:text-zinc-400">
More messages below ↓
</span>
</div>
) : null}
</div>
<div className="border-t border-black/10 p-3 dark:border-white/10">
<div className="rounded-lg border border-black/15 px-3 py-2 text-sm text-gray-400 dark:border-white/15 dark:text-zinc-500">
{composerLabel}
</div>
</div>
</div>
);
};
export const SlackReplyDivider = ({ count = 1 }) => (
<div className="flex items-center gap-3 px-4 py-2">
<span className="flex-none text-xs font-semibold text-gray-500 dark:text-zinc-400">
{count} {count === 1 ? "reply" : "replies"}
</span>
<span className="h-px flex-1 bg-black/10 dark:bg-white/10" />
</div>
);
export const SlackMessage = ({
name,
self = false,
bot = false,
time,
color = "#611f69",
hasAvatar = false,
badges,
reactions,
highlighted = false,
subtitle,
children,
}) => {
const SELF_NAME = "Dhravya Shah";
const childItems = Array.isArray(children) ? children : [children];
const resolvedAvatar = hasAvatar ? childItems[0] : null;
const messageBody = hasAvatar ? childItems.slice(1) : children;
const resolvedName = bot ? "supermemory" : self ? SELF_NAME : name || SELF_NAME;
const resolvedBadges = badges || (bot ? ["AGENT"] : null);
return (
<div
className={
"group relative flex gap-2.5 px-4 py-2 hover:bg-black/[0.02] dark:hover:bg-white/[0.02] " +
(highlighted
? "mx-2 my-1 rounded-md border border-[#1D9BD1]/60 bg-[#1D9BD1]/[0.04] px-2 py-2"
: "")
}
>
<div className="absolute -top-4 right-3 hidden items-center gap-0.5 rounded-md border border-black/10 bg-white p-0.5 shadow-sm group-hover:flex dark:border-white/10 dark:bg-[#222529]">
{["✅", "👀", "🙌", "😊", "↩️", "⋯"].map((icon, i) => (
<span
key={i}
className="flex h-6 w-6 items-center justify-center rounded text-xs hover:bg-black/5 dark:hover:bg-white/10"
>
{icon}
</span>
))}
</div>
{resolvedAvatar ? (
<span className="mt-0.5 h-9 w-9 flex-none overflow-hidden rounded-md [&_img]:h-full [&_img]:w-full [&_img]:object-cover">
{resolvedAvatar}
</span>
) : (
<div
className="mt-0.5 flex h-9 w-9 flex-none items-center justify-center rounded-md text-sm font-bold text-white"
style={{ backgroundColor: color }}
>
{(resolvedName || "?").slice(0, 1).toUpperCase()}
</div>
)}
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-baseline gap-x-1.5 gap-y-0.5">
<span className="text-[15px] font-bold text-gray-900 dark:text-zinc-50">
{resolvedName}
</span>
{subtitle ? (
<span className="text-xs font-normal text-gray-500 dark:text-zinc-400">
({subtitle})
</span>
) : null}
{resolvedBadges
? resolvedBadges.map((b) => (
<span
key={b}
className="rounded-[3px] border border-black/10 px-1 py-px text-[10px] font-medium leading-tight text-gray-500 dark:border-white/15 dark:text-zinc-400"
>
{b}
</span>
))
: null}
{time ? (
<span className="text-xs font-normal text-gray-400 dark:text-zinc-500">{time}</span>
) : null}
</div>
<div className="text-[15px] leading-normal text-gray-800 dark:text-zinc-200 [&_p]:m-0 [&_strong]:font-bold [&_strong]:text-gray-900 dark:[&_strong]:text-zinc-50 [&_code]:rounded [&_code]:bg-black/5 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[0.9em] dark:[&_code]:bg-white/10">
{messageBody}
</div>
{reactions ? (
<div className="mt-1.5 flex flex-wrap gap-1">
{reactions.map((r, i) => (
<span
key={i}
className="flex items-center gap-1 rounded-full border border-[#1D9BD1]/40 bg-[#1D9BD1]/10 px-2 py-0.5 text-xs text-[#1264A3] dark:border-[#1D9BD1]/40 dark:bg-[#1D9BD1]/15 dark:text-[#7DD3FC]"
>
<span>{r.emoji}</span>
<span className="font-semibold">{r.count}</span>
</span>
))}
</div>
) : null}
</div>
</div>
);
};
export const FileAttachment = ({ name, size }) => (
<div className="inline-flex max-w-[260px] items-center gap-2 rounded-lg border border-black/10 bg-white px-3 py-2 dark:border-white/10 dark:bg-[#222529]">
<svg
viewBox="0 0 18 18"
className="h-6 w-6 flex-none text-gray-400 dark:text-zinc-500"
fill="none"
stroke="currentColor"
strokeWidth="1.3"
aria-hidden="true"
>
<path
d="M4.5 2h6l3 3v9.5a1 1 0 01-1 1h-8a1 1 0 01-1-1V3a1 1 0 011-1z"
strokeLinejoin="round"
/>
<path d="M10.5 2v3h3" strokeLinejoin="round" />
</svg>
<div className="min-w-0">
<div className="truncate text-sm font-medium text-gray-900 dark:text-zinc-50">{name}</div>
{size ? (
<div className="truncate text-xs text-gray-500 dark:text-zinc-400">{size}</div>
) : null}
</div>
</div>
);
export const AgentLink = ({ href, children }) => (
<a href={href} className="font-medium no-underline hover:underline" style={{ color: "#1264A3" }}>
{children}
</a>
);