Skip to content
Merged
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
187 changes: 187 additions & 0 deletions apps/website/src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* Default OpenGraph + Twitter share card for the marketing site.
*
* Renders a 1200×630 PNG at request time via Next.js ImageResponse.
* Per-route overrides can be added by dropping an `opengraph-image.tsx`
* file in any route folder.
*/
import { ImageResponse } from 'next/og';

export const runtime = 'edge';
export const alt = 'Angular Agent Framework — Signal-native streaming for Angular + LangGraph';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

async function loadFont(family: string, weight: number): Promise<ArrayBuffer | null> {
try {
// Modern UA → woff2. Older UA → ttf. We ask for both and pick the first that resolves.
// ImageResponse wants the raw font binary; woff2 is fine (Satori decompresses internally).
const css = await fetch(
`https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`,
{ headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' } },
).then((res) => res.text());
// Grab any url(...) src — the first one is the woff2 the modern UA gets.
const match = css.match(/src:\s*url\((https?:\/\/[^)]+)\)/);
if (!match) return null;
const fontRes = await fetch(match[1]);
if (!fontRes.ok) return null;
return await fontRes.arrayBuffer();
} catch {
return null;
}
}

export default async function OpenGraphImage() {
const [garamondBold, interRegular, interBold, monoBold] = await Promise.all([
loadFont('EB+Garamond', 700),
loadFont('Inter', 400),
loadFont('Inter', 600),
loadFont('JetBrains+Mono', 700),
]);
const fonts = [
garamondBold && { name: 'EB Garamond', data: garamondBold, weight: 700 as const, style: 'normal' as const },
interRegular && { name: 'Inter', data: interRegular, weight: 400 as const, style: 'normal' as const },
interBold && { name: 'Inter', data: interBold, weight: 600 as const, style: 'normal' as const },
monoBold && { name: 'JetBrains Mono', data: monoBold, weight: 700 as const, style: 'normal' as const },
].filter((f): f is NonNullable<typeof f> => f !== null);

return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
background: 'linear-gradient(135deg, #fafbfc 0%, #eaf3ff 100%)',
display: 'flex',
flexDirection: 'column',
padding: '72px 80px',
color: '#1a1a2e',
fontFamily: 'Inter, sans-serif',
}}
>
{/* Eyebrow */}
<div
style={{
fontFamily: 'JetBrains Mono, monospace',
fontSize: 18,
letterSpacing: '0.12em',
color: '#004090',
fontWeight: 700,
textTransform: 'uppercase',
marginBottom: 28,
}}
>
Angular Agent Framework · MIT
</div>

{/* Headline — EB Garamond serif matches marketing-site h1 */}
<div
style={{
fontFamily: 'EB Garamond, Georgia, serif',
fontSize: 76,
lineHeight: 1.05,
fontWeight: 700,
letterSpacing: '-0.02em',
color: '#1a1a2e',
marginBottom: 24,
maxWidth: 980,
}}
>
Ship agentic Angular apps without rewriting your frontend.
</div>

{/* Subhead */}
<div
style={{
fontSize: 26,
lineHeight: 1.45,
color: '#555770',
maxWidth: 920,
marginBottom: 'auto',
}}
>
Signal-native streaming for LangGraph and AG-UI. Headless primitives plus
opinionated compositions for Angular 20+ teams shipping to production.
</div>

{/* Footer row — pill trust signals + wordmark */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 36,
}}
>
<div style={{ display: 'flex', gap: 12 }}>
<PillBadge tone="accent">MIT</PillBadge>
<PillBadge tone="neutral">LangGraph + AG-UI</PillBadge>
<PillBadge tone="angular">Angular 20+</PillBadge>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 10,
fontFamily: 'EB Garamond, Georgia, serif',
fontSize: 22,
fontWeight: 700,
color: '#1a1a2e',
}}
>
<span style={{ fontSize: 28 }}>🛩️</span>
<span>cacheplane.ai</span>
</div>
</div>
</div>
),
{
...size,
fonts,
},
);
}

interface PillBadgeProps {
tone: 'accent' | 'neutral' | 'angular';
children: React.ReactNode;
}

function PillBadge({ tone, children }: PillBadgeProps) {
const styles = {
accent: {
bg: 'rgba(0, 64, 144, 0.08)',
border: 'rgba(0, 64, 144, 0.18)',
color: '#004090',
},
neutral: {
bg: '#ffffff',
border: '#e6e8ee',
color: '#555770',
},
angular: {
bg: 'rgba(221, 0, 49, 0.06)',
border: 'rgba(221, 0, 49, 0.18)',
color: '#DD0031',
},
}[tone];

return (
<div
style={{
display: 'flex',
alignItems: 'center',
padding: '8px 18px',
borderRadius: 999,
background: styles.bg,
border: `1px solid ${styles.border}`,
color: styles.color,
fontFamily: 'JetBrains Mono, monospace',
fontSize: 15,
fontWeight: 600,
}}
>
{children}
</div>
);
}
6 changes: 4 additions & 2 deletions libs/chat/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# chat
# @ngaf/chat

This library was generated with [Nx](https://nx.dev).
Drop-in agent chat UI for Angular 20+. Headless primitives that read from a runtime-neutral `Agent` contract, plus opinionated compositions (`<chat>`, `<chat-debug>`, GenUI surfaces) you can ship in days.

Part of the [Angular Agent Framework](https://github.com/cacheplane/angular-agent-framework). MIT licensed.

## Runtime adapters

Expand Down
31 changes: 29 additions & 2 deletions libs/render/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
# render
# @ngaf/render

This library was generated with [Nx](https://nx.dev).
Generative UI for Angular. Agents emit structured JSON specs; this library renders them into Angular components you already own. Supports the Vercel `json-render` and Google A2UI v1 protocols out of the box.

Part of the [Angular Agent Framework](https://github.com/cacheplane/angular-agent-framework). MIT licensed.

## Install

```bash
npm install @ngaf/render
```

## What it does

- **Spec-driven rendering** — agents return JSON; you map each node type to one of your Angular components via a registry
- **Two protocols supported** — Vercel `json-render` and Google A2UI v1
- **Per-component fallback API** — when a spec node has no registered component, you control what renders (and surface it to your observability layer)
- **Readiness gate** — holds renders until the surface is real, so users never see mystery partial UI
- **Streaming partial renders** — works with `@ngaf/partial-json` to render progressive JSON as it streams

## Documentation

- [Quickstart](https://cacheplane.ai/docs/render/getting-started/quickstart)
- [Component registry](https://cacheplane.ai/docs/render/guides/registry)
- [Fallback patterns](https://cacheplane.ai/docs/render/guides/fallback)
- [A2UI v1 protocol](https://cacheplane.ai/docs/render/a2ui/overview)

## License

MIT — free for any use. See [LICENSE](../../LICENSE).
Loading