Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 16 additions & 26 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@source "../node_modules/streamdown/dist/*.js";
/* @source "../node_modules/streamdown/dist/*.js"; */

@custom-variant dark (&:is(.dark *));

Expand Down Expand Up @@ -277,17 +277,17 @@
}

[data-streamdown="code-block"] {
@apply my-3 overflow-hidden rounded-lg border border-primary-border bg-primary-bg [corner-shape:squircle];
@apply my-3 p-0 gap-0 flex flex-col rounded-lg border-0 ring ring-primary-border shadow-xs;
}
[data-streamdown="code-block-header"] {
@apply flex items-center justify-between border-b border-primary-border bg-primary-bg p-3 text-xs text-ink-primary;
@apply flex items-center justify-between [&>span]:font-sans h-auto px-3 py-4 -my-2 bg-tertiary-bg border-0 text-xs text-ink-primary;
}
[data-streamdown="code-block-body"] {
@apply overflow-x-auto p-3 bg-primary-bg text-sm leading-relaxed;
@apply overflow-x-auto border-0 p-3 bg-primary-bg ring ring-tertiary-border text-sm leading-relaxed;
}

[data-streamdown="inline-code"] {
@apply rounded-sm bg-primary-bg px-1 py-0.5 text-sm text-ink-primary;
@apply rounded-sm bg-primary-bg border border-primary-border px-1 py-0.5 text-sm text-ink-secondary;
}

[data-streamdown="link"] {
Expand Down Expand Up @@ -327,39 +327,29 @@
}

[data-streamdown="table-wrapper"] {
@apply my-3 overflow-x-auto;
@apply my-3 border-0 p-0 gap-0 flex flex-col ring ring-secondary-border overflow-x-auto [&_div]:border-0;
}
[data-streamdown="table"] {
@apply w-full text-sm;
@apply w-full text-sm border-0 p-0 gap-0 bg-tertiary-bg;
}
[data-streamdown="table-header"] {
@apply border-b border-primary-border bg-primary-bg;
@apply [&_tr]:border-0 [&_tr]:bg-tertiary-bg [&_th]:px-4 [&_th]:py-2 [&>tr]:-my-2;
}

[data-streamdown="table-body"] {
@apply rounded-xl overflow-hidden;
clip-path: inset(0 round 12px);
}
[data-streamdown="table-header-cell"] {
@apply px-3 py-1.5 text-left font-medium text-ink-primary;
@apply px-3 py-1.5 text-left font-medium text-ink-primary overflow-hidden;
}
[data-streamdown="table-row"] {
@apply border-b border-primary-border;
@apply border-b border-primary-border overflow-hidden;
}
[data-streamdown="table-cell"] {
@apply px-3 py-1.5 text-ink-primary;
@apply px-3 py-1.5 text-ink-primary bg-primary-bg overflow-hidden;
}

[data-streamdown="image"] {
@apply max-w-full rounded-md;
}

/* ─── Shiki dual-theme code blocks ───
CodeBlock highlights with { light: github-light, dark: github-dark } and
defaultColor:false, so shiki emits per-token CSS variables instead of a fixed
color. Activate light by default and swap to dark under the .dark class. Only
the text color switches — the <pre> keeps its own bg-secondary surface. */
.shiki,
.shiki span {
color: var(--shiki-light);
}

.dark .shiki,
.dark .shiki span {
color: var(--shiki-dark);
}
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata } from "next";
import { IBM_Plex_Mono, Inter } from "next/font/google";
import { Providers } from "@/components/providers";
import "streamdown/styles.css";
// import "streamdown/styles.css";
import "./globals.css";
const inter = Inter({
variable: "--font-inter",
Expand Down
33 changes: 26 additions & 7 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions components/ai/composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const ComposerContainer = ({ className, ...props }: ComposerContainerProps) => (
// Positioned so it paints above the context window peeking out from
// behind its top edge. Edge is shadow-drawn (shadow-border), not a
// border, matching the playground cards.
"relative bg-primary-bg rounded-4xl border border-primary-border shadow-xs [corner-shape:squircle] cursor-text transition-colors",
"relative bg-primary-bg rounded-2xl border border-primary-border shadow-xs cursor-text transition-colors",
className,
)}
{...props}
Expand Down Expand Up @@ -188,6 +188,7 @@ type ComposerTextareaProps = {

const ComposerTextarea = ({ className, disabled = false, ...props }: ComposerTextareaProps) => (
<ComposerPrimitive.Textarea
data-slot="composer-textarea"
disabled={disabled}
// The wire format carries the icon as an opaque string; narrow it to this
// app's concrete keys here — unknown values fall back to no icon.
Expand All @@ -203,7 +204,8 @@ const ComposerTextarea = ({ className, disabled = false, ...props }: ComposerTex
);
}}
className={cn(
"max-h-32 min-h-8 overflow-y-auto py-2 px-3 text-md",
"max-h-32 min-h-8 overflow-y-auto py-2 text-md",
"**:data-composer-editor:px-3 **:data-composer-placeholder:px-3",
"mask-[linear-gradient(to_bottom,transparent,black_16px,black_calc(100%-16px),transparent)]",
// The editor element — ProseMirror-owned DOM, out of JSX reach.
"**:data-composer-editor:w-full **:data-composer-editor:max-w-none **:data-composer-editor:font-book **:data-composer-editor:leading-[1.7] [&_[data-composer-editor]:focus]:outline-none",
Expand Down Expand Up @@ -366,7 +368,7 @@ const ComposerPanel = ({ className, ...props }: ComposerPanelProps) => (
// coordinates); portaled to the body, so it never reserves layout in the composer.
// Match the composer Container's width via the positioner's --anchor-width var.
"absolute z-50 w-(--anchor-width) overflow-hidden",
"rounded-4xl border border-primary-border bg-primary-bg [corner-shape:squircle]",
"rounded-2xl border border-primary-border bg-primary-bg",
"transition-[opacity,transform] duration-150 ease-out",
"data-starting-style:opacity-0 data-ending-style:opacity-0",
// Slide from the anchored edge: default (above) drops in from below; flipped
Expand Down Expand Up @@ -396,7 +398,7 @@ const ComposerPopover = ({ className, ...props }: ComposerPopoverProps) => {
// off the anchor token with a stronger shadow. `absolute` is the base positioning
// context (the positioner then writes left/top in page coordinates); z-50 keeps
// the portaled popover above the thread.
"absolute z-50 w-72 overflow-hidden border border-primary-border bg-primary-bg rounded-4xl shadow-lg [corner-shape:squircle]",
"absolute z-50 w-72 overflow-hidden border border-primary-border bg-primary-bg rounded-2xl shadow-lg",
"transition-[opacity,transform,filter] duration-150 ease-out",
"data-closed:opacity-0 data-closed:blur-[3px]",
// Slide from the anchored edge — opens upward by default, downward when flipped.
Expand Down
2 changes: 0 additions & 2 deletions components/docs/intentface.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@source "../node_modules/streamdown/dist/*.js";

@custom-variant dark (&:is(.dark *));

:root {
Expand Down
5 changes: 4 additions & 1 deletion components/ui/markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { code } from "@streamdown/code";
import type { ComponentProps } from "react";
import type { Components } from "streamdown";
import { Streamdown } from "streamdown";
Expand Down Expand Up @@ -63,12 +64,14 @@ const Markdown = ({
...props
}: ComponentProps<typeof Streamdown>) => (
<Streamdown
plugins={{ code }}
controls={false}
components={{ ...markdownComponents, ...components }}
className={cn(
"text-md [&_p]:whitespace-pre-wrap [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
"px-px text-md [&_p]:whitespace-pre-wrap [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
className,
)}
shikiTheme={["github-light", "vesper"]}
{...props}
/>
);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@ai-sdk/react": "^3.0.220",
"@base-ui/react": "^1.6.0",
"@intentface/chat": "workspace:*",
"@streamdown/code": "^1.1.1",
"@tiptap/core": "^3.27.1",
"@tiptap/extension-document": "^3.27.1",
"@tiptap/extension-paragraph": "^3.27.1",
Expand Down
7 changes: 7 additions & 0 deletions packages/chat/src/chip-markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export const encodeChipMarkdown = (chip: ChipData): string => {
};

export const parseChipSegments = (text: string): ChipSegment[] => {
// Fast path — every chip token contains this literal, and most message text
// carries no chips; an includes() probe is far cheaper than a regex scan of
// the full text (which the streaming row would otherwise repeat per token).
if (!text.includes("](chip:")) {
return text.length > 0 ? [{ type: "text", text }] : [];
}

const segments: ChipSegment[] = [];
let lastIndex = 0;
for (const match of text.matchAll(CHIP_REF_PATTERN)) {
Expand Down
33 changes: 20 additions & 13 deletions packages/chat/src/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,27 @@ const MessageText = ({
renderChip,
...elementProps
}: MessageTextProps) => {
const segments = parseChipSegments(children);

const segmentNodes = segments.map((segment, index) =>
segment.type === "text" ? (
<Fragment key={index}>{renderText ? renderText(segment.text, index) : segment.text}</Fragment>
) : (
<Fragment key={index}>
{renderChip ? (
renderChip(segment, index)
// Settled rows re-render for reasons other than their text (data-last flips,
// parent context) — memoizing on the text keeps them from re-parsing and
// rebuilding the segment tree each time.
const segmentNodes = useMemo(
() =>
parseChipSegments(children).map((segment, index) =>
segment.type === "text" ? (
<Fragment key={index}>
{renderText ? renderText(segment.text, index) : segment.text}
</Fragment>
) : (
<span data-message-chip="">{segment.label}</span>
)}
</Fragment>
),
<Fragment key={index}>
{renderChip ? (
renderChip(segment, index)
) : (
<span data-message-chip="">{segment.label}</span>
)}
</Fragment>
),
),
[children, renderText, renderChip],
);

return useRenderElement(
Expand Down