diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 000000000..344aa68fb --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "website-dev", + "runtimeExecutable": "/bin/bash", + "runtimeArgs": ["-c", "export PATH=/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH && npx nx serve website"], + "port": 3000 + } + ] +} diff --git a/apps/website/content/docs-v2/api/stream-resource.mdx b/apps/website/content/docs-v2/api/stream-resource.mdx new file mode 100644 index 000000000..62df1cef7 --- /dev/null +++ b/apps/website/content/docs-v2/api/stream-resource.mdx @@ -0,0 +1,38 @@ +# streamResource() + +Creates a streaming resource connected to a LangGraph agent. Must be called within an Angular injection context. + +## Signature + +```typescript +function streamResource(options: StreamResourceOptions): StreamResource +``` + +## Options + +| Parameter | Type | Description | +|-----------|------|-------------| +| `assistantId` | `string` | Agent or graph identifier | +| `apiUrl` | `string` | LangGraph Platform base URL | +| `threadId` | `Signal \| string \| null` | Thread to connect to | +| `onThreadId` | `(id: string) => void` | Called when a new thread is created | +| `onInterrupt` | `(data: unknown) => void` | Called when the agent pauses for input | +| `transport` | `StreamTransport` | Custom transport (default: FetchStreamTransport) | + +## Return type + +streamResource() returns an object with these Signal properties: + +| Property | Type | Description | +|----------|------|-------------| +| `messages()` | `Signal` | Current message list | +| `status()` | `Signal<'idle' \| 'streaming' \| 'error'>` | Stream status | +| `error()` | `Signal` | Last error, if any | +| `threadId()` | `Signal` | Current thread ID | + +## Methods + +| Method | Description | +|--------|-------------| +| `submit(input)` | Send a message or resume from interrupt | +| `history()` | Get execution history for time travel | diff --git a/apps/website/content/docs-v2/getting-started/introduction.mdx b/apps/website/content/docs-v2/getting-started/introduction.mdx new file mode 100644 index 000000000..53fc6dcef --- /dev/null +++ b/apps/website/content/docs-v2/getting-started/introduction.mdx @@ -0,0 +1,28 @@ +# Introduction + +StreamResource brings full parity with React's `useStream()` hook to Angular 20+. It's the enterprise streaming resource for LangChain and Angular — built natively with Angular Signals, not wrapped or adapted. + + +StreamResource serves two audiences: Angular developers building AI-powered apps, and AI/agent developers who need an Angular frontend. + + +## What you'll build + +With streamResource(), you can build Angular applications that connect to LangGraph agents with: + +- **Token-by-token streaming** via SSE +- **Thread persistence** across sessions +- **Human-in-the-loop** interrupts and approvals +- **Time travel** debugging +- **Deterministic testing** with MockStreamTransport + +## Next steps + + + + Build your first streaming chat in 5 minutes + + + Detailed setup and configuration guide + + diff --git a/apps/website/content/docs-v2/guides/streaming.mdx b/apps/website/content/docs-v2/guides/streaming.mdx new file mode 100644 index 000000000..55d2fde6b --- /dev/null +++ b/apps/website/content/docs-v2/guides/streaming.mdx @@ -0,0 +1,53 @@ +# Streaming + +StreamResource provides token-by-token streaming from LangGraph agents via Server-Sent Events (SSE). Every update lands directly in Angular Signals. + + +Make sure you've completed the Installation guide first. + + +## Basic streaming + + + + +```typescript +// chat.component.ts +const chat = streamResource<{ messages: BaseMessage[] }>({ + assistantId: 'chat_agent', +}); + +// Status updates as streaming progresses +const isStreaming = computed(() => chat.status() === 'streaming'); +``` + + + + +```html + +@for (msg of chat.messages(); track $index) { +

+ {{ msg.content }} +

+} +``` + +
+
+ +## Stream status + +The `status()` signal reports the current state: + + + +No active stream. Ready to send a message. + + +Tokens are arriving. Messages update in real-time. + + +Something went wrong. Check the error() signal for details. + + diff --git a/apps/website/e2e/website.spec.ts b/apps/website/e2e/website.spec.ts index 82d636e8c..fc191a4b9 100644 --- a/apps/website/e2e/website.spec.ts +++ b/apps/website/e2e/website.spec.ts @@ -7,14 +7,13 @@ test('landing page renders hero headline', async ({ page }) => { expect(headline).toContain('Angular'); }); -test('landing page renders architecture diagram', async ({ page }) => { +test('landing page renders architecture section', async ({ page }) => { await page.goto('/'); - await expect(page.locator('svg[aria-label*="Architecture"]')).toBeVisible(); + await expect(page.getByText('Architecture').first()).toBeVisible(); }); test('landing page renders 6 feature cards', async ({ page }) => { await page.goto('/'); - // Feature section contains h2 "Features" and 6 cards const featureSection = page.locator('section').filter({ hasText: 'Features' }); await expect(featureSection).toBeVisible(); }); @@ -30,19 +29,18 @@ test('pricing page shows 4 plan cards', async ({ page }) => { test('pricing page lead form validates required fields', async ({ page }) => { await page.goto('/pricing'); await page.click('button[type="submit"]'); - // HTML5 validation prevents submit — form should still be visible await expect(page.locator('form')).toBeVisible(); }); test('docs page renders sidebar and content', async ({ page }) => { - await page.goto('/docs/deep-agents/getting-started/overview/overview/python'); + await page.goto('/docs/getting-started/introduction'); await expect(page.locator('aside')).toBeVisible(); await expect(page.locator('article')).toBeVisible(); }); -test('api reference page renders', async ({ page }) => { - await page.goto('/api-reference'); - await expect(page.getByText('streamResource()', { exact: true }).first()).toBeVisible(); +test('api reference renders in docs', async ({ page }) => { + await page.goto('/docs/api/stream-resource'); + await expect(page.getByText('streamResource()').first()).toBeVisible(); }); test('nav has pricing link', async ({ page }) => { diff --git a/apps/website/lib/design-tokens.ts b/apps/website/lib/design-tokens.ts index d701c150c..695e26e40 100644 --- a/apps/website/lib/design-tokens.ts +++ b/apps/website/lib/design-tokens.ts @@ -3,23 +3,37 @@ */ export const tokens = { colors: { - bg: '#080B14', - accent: '#6C8EFF', - accentGlow: 'rgba(108, 142, 255, 0.35)', - accentBorder: 'rgba(108, 142, 255, 0.15)', - accentBorderHover: 'rgba(108, 142, 255, 0.40)', - accentSurface: 'rgba(108, 142, 255, 0.08)', - textPrimary: '#EEF1FF', - textSecondary: '#8B96C8', - textMuted: '#4A527A', - sidebarBg: '#0A0D18', + bg: '#f8f9fc', + accent: '#004090', + accentLight: '#64C3FD', + accentGlow: 'rgba(0, 64, 144, 0.2)', + accentBorder: 'rgba(0, 64, 144, 0.15)', + accentBorderHover: 'rgba(0, 64, 144, 0.3)', + accentSurface: 'rgba(0, 64, 144, 0.06)', + textPrimary: '#1a1a2e', + textSecondary: '#555770', + textMuted: '#8b8fa3', + sidebarBg: 'rgba(255, 255, 255, 0.45)', angularRed: '#DD0031', }, + glass: { + bg: 'rgba(255, 255, 255, 0.45)', + bgHover: 'rgba(255, 255, 255, 0.55)', + blur: '16px', + border: 'rgba(255, 255, 255, 0.6)', + shadow: '0 4px 24px rgba(0, 0, 0, 0.06)', + }, + gradient: { + warm: 'radial-gradient(circle, rgba(221, 0, 49, 0.18), transparent 70%)', + cool: 'radial-gradient(circle, rgba(0, 64, 144, 0.18), transparent 70%)', + coolLight: 'radial-gradient(circle, rgba(100, 195, 253, 0.15), transparent 70%)', + bgFlow: 'linear-gradient(135deg, #fef0f3 0%, #f4f0ff 45%, #eaf3ff 70%, #e6f4ff 100%)', + }, glow: { - hero: '0 0 40px rgba(108, 142, 255, 0.5)', - demo: '0 0 30px rgba(108, 142, 255, 0.25)', - card: '0 0 24px rgba(108, 142, 255, 0.3)', - border: '0 0 12px rgba(108, 142, 255, 0.2)', - button: '0 0 16px rgba(108, 142, 255, 0.4)', + hero: '0 0 60px rgba(0, 64, 144, 0.15)', + demo: '0 0 30px rgba(0, 64, 144, 0.1)', + card: '0 0 24px rgba(0, 64, 144, 0.1)', + border: '0 0 12px rgba(0, 64, 144, 0.08)', + button: '0 0 16px rgba(0, 64, 144, 0.15)', }, } as const; diff --git a/apps/website/src/app/api-reference/page.tsx b/apps/website/src/app/api-reference/page.tsx deleted file mode 100644 index 7e2ad5994..000000000 --- a/apps/website/src/app/api-reference/page.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { ApiRefTable, type ApiEntry } from '../../components/docs/ApiRefTable'; - -// Placeholder entries — replaced by generated api-docs.json in Task W9 -const ENTRIES: ApiEntry[] = [ - { - name: 'streamResource()', - type: 'function', - description: 'Creates a streaming resource connected to a LangGraph agent. Must be called within an Angular injection context.', - params: [ - { name: 'assistantId', type: 'string', desc: 'Agent or graph identifier.' }, - { name: 'apiUrl', type: 'string', desc: 'LangGraph Platform base URL.' }, - { name: 'threadId', type: 'Signal | string | null', desc: 'Thread to connect to. Pass a signal for reactive updates.' }, - { name: 'onThreadId', type: '(id: string) => void', desc: 'Called when a new thread is auto-created.' }, - ], - }, - { - name: 'provideStreamResource()', - type: 'function', - description: 'Angular provider factory. Registers global defaults for apiUrl and transport.', - params: [ - { name: 'config', type: 'StreamResourceConfig', desc: 'Global config merged with per-call options.' }, - ], - }, -]; - -export default function ApiReferencePage() { - return ( -
-

API Reference

-

- API Reference -

- -
- ); -} diff --git a/apps/website/src/app/docs/[[...slug]]/page.tsx b/apps/website/src/app/docs/[[...slug]]/page.tsx index 9bfda3ad1..eb0ecd886 100644 --- a/apps/website/src/app/docs/[[...slug]]/page.tsx +++ b/apps/website/src/app/docs/[[...slug]]/page.tsx @@ -1,28 +1,30 @@ import { notFound } from 'next/navigation'; -import { DocsSidebar } from '../../../components/docs/DocsSidebar'; -import { MdxRenderer } from '../../../components/docs/MdxRenderer'; -import { OpenInCockpit } from '../../../components/docs/open-in-cockpit'; -import { getDocBySlug, getAllDocSlugs, getPromptBySlug } from '../../../lib/docs'; +import { DocsSidebarNew } from '../../../components/docs/DocsSidebarNew'; +import { MdxRendererNew } from '../../../components/docs/MdxRenderer'; +import { DocsSearch } from '../../../components/docs/DocsSearch'; +import { getDocBySlug, getAllDocSlugs } from '../../../lib/docs-new'; export function generateStaticParams() { - return getAllDocSlugs().map((slug) => ({ slug })); + return getAllDocSlugs().map(({ section, slug }) => ({ slug: [section, slug] })); } export default async function DocsPage({ params }: { params: Promise<{ slug?: string[] }> }) { const { slug: rawSlug } = await params; - const slug = rawSlug ?? ['deep-agents', 'getting-started', 'overview', 'overview', 'python']; - const doc = getDocBySlug(slug); + const slugParts = rawSlug ?? ['getting-started', 'introduction']; + + const [section, slug] = slugParts.length >= 2 + ? [slugParts[0], slugParts[1]] + : ['getting-started', 'introduction']; + + const doc = getDocBySlug(section, slug); if (!doc) notFound(); - const prompt = getPromptBySlug(slug) ?? undefined; return ( -
- -
-
- -
- +
+ + +
+
); diff --git a/apps/website/src/app/global.css b/apps/website/src/app/global.css index bfdf8fb5e..9a7be9bec 100644 --- a/apps/website/src/app/global.css +++ b/apps/website/src/app/global.css @@ -1,17 +1,17 @@ @import "tailwindcss"; @theme { - /* Existing design tokens — preserved for backward compat */ - --color-bg: #080B14; - --color-accent: #6C8EFF; - --color-accent-glow: rgba(108, 142, 255, 0.35); - --color-accent-border: rgba(108, 142, 255, 0.15); - --color-accent-border-hover: rgba(108, 142, 255, 0.40); - --color-accent-surface: rgba(108, 142, 255, 0.08); - --color-text-primary: #EEF1FF; - --color-text-secondary: #8B96C8; - --color-text-muted: #4A527A; - --color-sidebar-bg: #0A0D18; + --color-bg: #f8f9fc; + --color-accent: #004090; + --color-accent-light: #64C3FD; + --color-accent-glow: rgba(0, 64, 144, 0.2); + --color-accent-border: rgba(0, 64, 144, 0.15); + --color-accent-border-hover: rgba(0, 64, 144, 0.3); + --color-accent-surface: rgba(0, 64, 144, 0.06); + --color-text-primary: #1a1a2e; + --color-text-secondary: #555770; + --color-text-muted: #8b8fa3; + --color-sidebar-bg: rgba(255, 255, 255, 0.45); --color-angular-red: #DD0031; --font-garamond: "EB Garamond", Georgia, serif; @@ -19,51 +19,27 @@ --font-mono: "JetBrains Mono", monospace; } -/* shadcn CSS variable tokens — new, non-conflicting names */ -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); -} - :root { - /* Existing legacy vars */ - --color-bg: #080B14; - --color-accent: #6C8EFF; - --color-accent-glow: rgba(108, 142, 255, 0.35); - --color-accent-border: rgba(108, 142, 255, 0.15); - --color-accent-border-hover: rgba(108, 142, 255, 0.40); - --color-accent-surface: rgba(108, 142, 255, 0.08); - --color-text-primary: #EEF1FF; - --color-text-secondary: #8B96C8; - --color-text-muted: #4A527A; - --color-sidebar-bg: #0A0D18; + --color-bg: #f8f9fc; + --color-accent: #004090; + --color-accent-light: #64C3FD; + --color-accent-glow: rgba(0, 64, 144, 0.2); + --color-accent-border: rgba(0, 64, 144, 0.15); + --color-accent-border-hover: rgba(0, 64, 144, 0.3); + --color-accent-surface: rgba(0, 64, 144, 0.06); + --color-text-primary: #1a1a2e; + --color-text-secondary: #555770; + --color-text-muted: #8b8fa3; + --color-sidebar-bg: rgba(255, 255, 255, 0.45); --color-angular-red: #DD0031; - /* shadcn semantic vars — maps to same palette */ - --background: #080B14; - --foreground: #EEF1FF; - --primary: #6C8EFF; - --primary-foreground: #ffffff; - --card: #0A0D18; - --card-foreground: #EEF1FF; - --muted: #0D1020; - --muted-foreground: #8B96C8; - --border: #1a2040; - --input: #1a2040; - --ring: #6C8EFF; - --destructive: #FF6B6B; - --destructive-foreground: #ffffff; + --glass-bg: rgba(255, 255, 255, 0.45); + --glass-bg-hover: rgba(255, 255, 255, 0.55); + --glass-blur: 16px; + --glass-border: rgba(255, 255, 255, 0.6); + --glass-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); + + --gradient-bg-flow: linear-gradient(135deg, #fef0f3 0%, #f4f0ff 45%, #eaf3ff 70%, #e6f4ff 100%); } * { @@ -78,16 +54,21 @@ body { -moz-osx-font-smoothing: grayscale; } -.glow-text { - text-shadow: 0 0 40px rgba(108, 142, 255, 0.5); +/* Scroll-triggered fade-in for server components */ +@keyframes fadeInUp { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } } -.glow-demo { - box-shadow: 0 0 30px rgba(108, 142, 255, 0.25); +.animate-on-scroll { + animation: fadeInUp 0.6s ease-out both; + animation-timeline: view(); + animation-range: entry 0% entry 30%; } -.glow-card { - box-shadow: 0 0 24px rgba(108, 142, 255, 0.3); +/* Smooth scroll */ +html { + scroll-behavior: smooth; } /* Shiki code blocks — tokyo-night theme */ diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index bc77a22a1..3c33b3f2a 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -1,31 +1,41 @@ -import Script from 'next/script'; import { HeroTwoCol } from '../components/landing/HeroTwoCol'; import { ArchDiagram } from '../components/landing/ArchDiagram'; +import { ValueProps } from '../components/landing/ValueProps'; +import { LangGraphShowcase } from '../components/landing/LangGraphShowcase'; +import { DeepAgentsShowcase } from '../components/landing/DeepAgentsShowcase'; import { FeatureStrip } from '../components/landing/FeatureStrip'; import { CodeBlock } from '../components/landing/CodeBlock'; +import { CockpitCTA } from '../components/landing/CockpitCTA'; +import { StatsStrip } from '../components/landing/StatsStrip'; +import { tokens } from '../../lib/design-tokens'; export default async function HomePage() { return ( - <> +
+ {/* Ambient gradient blobs distributed across the long page */} +
+
+
+
+
+ + {/* 1. Hook — headline, animation, CTA */} + {/* 2. Trust — quick credibility stats */} + + {/* 3. Value — why this product, with interactive code tabs */} + + {/* 4. Proof — 30-second code example (show, don't tell) */} + + {/* 5. Depth — capability showcases with expandable code */} + + + {/* 6. Architecture — technical credibility for evaluators */} + {/* 7. Features — compact summary grid */} - - - {/* Angular Elements live demo */} -
-

Live Demo

-
- {/* @ts-expect-error — custom element registered by Angular Elements bundle */} - -
-