From ac05f81abd9a2167b68e594b4a8325bf692e02c3 Mon Sep 17 00:00:00 2001 From: ssdeanx Date: Sun, 1 Mar 2026 09:45:17 -0500 Subject: [PATCH] feat: introduce initial landing page UI, comprehensive Tailwind v4 styling with custom properties, Convex schema, and an AI schema display component. --- .agentstack/tasks/HARNESS_SPEC_TASK.md | 440 ------------ .../tasks/TASK-001-chat-streaming-fixes.md | 173 ----- .github/instructions/convex.instructions.md | 663 ++++++++++++++++++ .gitignore | 3 + app/components/landing-features.tsx | 70 +- app/components/landing-hero.tsx | 252 ++++--- app/components/landing-stats.tsx | 48 +- app/globals.css | 68 +- convex/schema.ts | 18 +- dot/tests/test-results/test-results.json | 30 - package-lock.json | 8 +- package.json | 2 +- src/components/ai-elements/schema-display.tsx | 8 +- 13 files changed, 993 insertions(+), 790 deletions(-) delete mode 100644 .agentstack/tasks/HARNESS_SPEC_TASK.md delete mode 100644 .agentstack/tasks/TASK-001-chat-streaming-fixes.md create mode 100644 .github/instructions/convex.instructions.md delete mode 100644 dot/tests/test-results/test-results.json diff --git a/.agentstack/tasks/HARNESS_SPEC_TASK.md b/.agentstack/tasks/HARNESS_SPEC_TASK.md deleted file mode 100644 index 303c249c..00000000 --- a/.agentstack/tasks/HARNESS_SPEC_TASK.md +++ /dev/null @@ -1,440 +0,0 @@ -# HARNESS_SPEC_TASK: Mastra Harness Integration for AgentSandbox - -**Status**: PENDING -**Priority**: HIGH -**Created**: 2026-02-23 - -## Problem Statement - -The `agent-sandbox.tsx` component currently uses a temporary `data-sandbox` event type to receive sandbox data. This is NOT the correct approach. The goal is to properly integrate the existing `mainHarness` with the chat UI components using Harness's official event subscription pattern. - -## Reference Documentation - -- https://mastra.ai/reference/harness/harness-class -- https://mastra.ai/reference/workspace/sandbox -- https://mastra.ai/reference/workspace/filesystem -- https://mastra.ai/reference/workspace/local-sandbox -- https://mastra.ai/reference/workspace/local-filesystem -- https://mastra.ai/reference/workspace/workspace-class - -## Requirements - -### Functional Requirements - -| ID | Requirement | Priority | -| ---- | -------------------------------------------------------------------------------------------------------- | -------- | -| FR-1 | Integrate existing `mainHarness` with `agent-sandbox.tsx` component | HIGH | -| FR-2 | Use Harness `subscribe()` pattern for event streaming to React | HIGH | -| FR-3 | Map Harness events to `AgentSandboxData` interface props | HIGH | -| FR-4 | Support all 9 sandbox tabs: Files, Terminal, Tests, Schema, Stack Trace, Packages, Env Vars, JSX Preview | MEDIUM | -| FR-5 | Handle workspace operations: read/write/edit files, execute commands, list files | HIGH | -| FR-6 | Stream terminal output in real-time via `onStdout`/`onStderr` callbacks | MEDIUM | -| FR-7 | Support subagent invocations (explore, quick-fix) with event propagation | MEDIUM | -| FR-8 | Handle tool approval workflows when tools require user confirmation | MEDIUM | - -### Non-Functional Requirements - -| ID | Requirement | Priority | -| ----- | --------------------------------------------------------------- | -------- | -| NFR-1 | React hook pattern for Harness subscription with proper cleanup | HIGH | -| NFR-2 | Type-safe event mapping with TypeScript | HIGH | -| NFR-3 | Real-time updates without polling (event-driven) | HIGH | -| NFR-4 | Proper memory management (unsubscribe on unmount) | HIGH | -| NFR-5 | Error boundary integration for Harness failures | MEDIUM | - -### Integration Requirements - -| ID | Requirement | Priority | -| ---- | --------------------------------------------------------------------------------------- | -------- | -| IR-1 | Work with existing `mainWorkspace` (LocalFilesystem + LocalSandbox) | HIGH | -| IR-2 | Compatible with `chat-context.tsx` message/streaming system | HIGH | -| IR-3 | Support all 8 Harness modes: plan, code, review, test, refactor, research, edit, report | MEDIUM | -| IR-4 | Integrate with existing subagents: explore, quick-fix | MEDIUM | - ---- - -## Architecture - -### Existing mainHarness Configuration - -```typescript -// src/mastra/harness.ts -export const mainHarness = new Harness({ - id: 'agentstack-harness', - resourceId: 'agentstack', - stateSchema: harnessStateSchema, - modes: [ - { - id: 'plan', - name: 'Planner', - agent: codeArchitectAgent, - default: true, - }, - { id: 'code', name: 'Builder', agent: codeArchitectAgent }, - { id: 'review', name: 'Reviewer', agent: codeReviewerAgent }, - { id: 'test', name: 'Tester', agent: testEngineerAgent }, - { id: 'refactor', name: 'Refactorer', agent: refactoringAgent }, - { id: 'research', name: 'Researcher', agent: researchAgent }, - { id: 'edit', name: 'Editor', agent: editorAgent }, - { id: 'report', name: 'Reporter', agent: reportAgent }, - ], - workspace: mainWorkspace, // LocalFilesystem + LocalSandbox - subagents: [ - { id: 'explore', name: 'Explorer', agent: exploreAgent }, - { id: 'quick-fix', name: 'Quick Fix', agent: quickFixAgent }, - ], -}) -``` - -### Harness Event Types (from official docs) - -```typescript -// Workspace Events -type WorkspaceEvent = - | 'workspace_status_changed' - | 'workspace_ready' - | 'workspace_error' - -// Tool Events -type ToolEvent = - | 'tool_start' - | 'tool_approval_required' - | 'tool_update' - | 'tool_end' - | 'tool_input_start' - | 'tool_input_delta' - | 'tool_input_end' - -// Subagent Events -type SubagentEvent = - | 'subagent_start' - | 'subagent_text_delta' - | 'subagent_tool_start' - | 'subagent_tool_end' - | 'subagent_end' - -// Task Events -type TaskEvent = 'task_updated' - -// Message Events -type MessageEvent = 'message_start' | 'message_update' | 'message_end' - -// Agent Events -type AgentEvent = 'agent_start' | 'agent_end' -``` - -### AgentSandboxData Interface (Target) - -```typescript -// app/chat/components/agent-sandbox.tsx lines 190-215 -interface AgentSandboxData { - title?: string - description?: string - files?: Array - activeFile?: string - terminalOutput?: string - terminalIsStreaming?: boolean - testSuites?: SandboxTestSuite[] - testSummary?: { - passed: number - failed: number - pending: number - total: number - } - schema?: SandboxSchema - stackTrace?: string - errorType?: string - errorMessage?: string - preview?: string - package?: PackageData - packages?: PackageData[] - environmentVariables?: EnvironmentVar[] - jsxPreview?: JSXPreviewData -} -``` - -### Event-to-Data Mapping - -| Harness Event | AgentSandboxData Field(s) | Notes | -| ---------------------------- | ----------------------------------------- | ----------------------- | -| `workspace_status_changed` | `files`, `activeFile` | File tree updates | -| `tool_start` (workspace\_\*) | `files`, `terminalOutput` | File/command operations | -| `tool_update` | `terminalOutput`, `terminalIsStreaming` | Real-time stdout/stderr | -| `tool_end` | Clear streaming flags | Operation complete | -| `tool_approval_required` | Show confirmation dialog | Requires user action | -| `subagent_start` | `title`, `description` | Subagent context | -| `subagent_text_delta` | `terminalOutput` (if terminal mode) | Streaming output | -| `message_end` | `testSuites`, `testSummary`, `schema` | Final results | -| `workspace_error` | `stackTrace`, `errorType`, `errorMessage` | Error handling | - ---- - -## Design - -### React Hook Pattern: `useHarnessSubscription` - -```typescript -// lib/hooks/use-harness-subscription.ts -import { useEffect, useRef, useState } from 'react' -import { mainHarness } from '@/src/mastra/harness' -import type { AgentSandboxData } from '@/app/chat/components/agent-sandbox' - -interface HarnessEvent { - type: string - payload: unknown - timestamp: Date -} - -export function useHarnessSubscription(resourceId: string, agentId?: string) { - const [sandboxData, setSandboxData] = useState({}) - const [isConnected, setIsConnected] = useState(false) - const [error, setError] = useState(null) - const unsubscribeRef = useRef<(() => void) | null>(null) - - useEffect(() => { - const handleEvent = (event: HarnessEvent) => { - // Map event to AgentSandboxData updates - const updates = mapEventToSandboxData(event) - setSandboxData((prev) => ({ ...prev, ...updates })) - } - - // Subscribe to Harness events - const unsubscribe = mainHarness.subscribe(handleEvent) - unsubscribeRef.current = unsubscribe - setIsConnected(true) - - return () => { - if (unsubscribeRef.current) { - unsubscribeRef.current() - unsubscribeRef.current = null - } - setIsConnected(false) - } - }, [resourceId, agentId]) - - return { sandboxData, isConnected, error } -} - -function mapEventToSandboxData(event: HarnessEvent): Partial { - switch (event.type) { - case 'tool_update': - return { - terminalOutput: (event.payload as any).output, - terminalIsStreaming: true, - } - case 'tool_end': - return { - terminalIsStreaming: false, - } - case 'workspace_status_changed': - return { - files: (event.payload as any).files, - activeFile: (event.payload as any).activeFile, - } - // ... additional mappings - default: - return {} - } -} -``` - -### Component Integration - -```typescript -// app/chat/components/agent-sandbox.tsx -import { useHarnessSubscription } from '@/lib/hooks/use-harness-subscription' - -export function AgentSandbox({ - message, - resourceId, - agentId, -}: AgentSandboxProps) { - const { sandboxData, isConnected, error } = useHarnessSubscription( - resourceId, - agentId - ) - - // Use sandboxData for rendering tabs - const files = sandboxData.files ?? [] - const terminalOutput = sandboxData.terminalOutput ?? '' - const testSuites = sandboxData.testSuites ?? [] - - // ... rest of component -} -``` - -### Workspace Tools Integration - -The Harness workspace provides these tools that emit events: - -| Tool | Events Emitted | UI Update | -| ---------------------------------- | --------------------------------------- | ---------------------- | -| `mastra_workspace_read_file` | `tool_start`, `tool_end` | Show file content | -| `mastra_workspace_write_file` | `tool_start`, `tool_end`, `tool_update` | Update file tree | -| `mastra_workspace_edit_file` | `tool_start`, `tool_end` | Show diff preview | -| `mastra_workspace_list_files` | `tool_start`, `tool_end` | Update file tree | -| `mastra_workspace_execute_command` | `tool_start`, `tool_update`, `tool_end` | Stream terminal output | -| `mastra_workspace_delete` | `tool_start`, `tool_end` | Update file tree | -| `mastra_workspace_mkdir` | `tool_start`, `tool_end` | Update file tree | -| `mastra_workspace_search` | `tool_start`, `tool_end` | Show search results | - -### Terminal Streaming Pattern - -```typescript -// When execute_command is called via Harness -const result = await harness.executeCommand({ - command: 'npm', - args: ['test'], - onStdout: (data) => { - // Emits tool_update event with stdout - harness.emit({ - type: 'tool_update', - payload: { output: data, stream: 'stdout' }, - }) - }, - onStderr: (data) => { - // Emits tool_update event with stderr - harness.emit({ - type: 'tool_update', - payload: { output: data, stream: 'stderr' }, - }) - }, -}) -``` - ---- - -## Implementation Tasks - -### Phase 1: Core Infrastructure (HIGH Priority) - -| Task ID | Description | Files | Effort | -| ------- | ---------------------------------------------- | ---------------------------------------- | ------ | -| T-001 | Create `useHarnessSubscription` hook | `lib/hooks/use-harness-subscription.ts` | 4h | -| T-002 | Implement `mapEventToSandboxData` event mapper | `lib/harness/event-mapper.ts` | 3h | -| T-003 | Add TypeScript types for Harness events | `lib/types/harness-events.ts` | 2h | -| T-004 | Create Harness context provider | `app/chat/providers/harness-context.tsx` | 3h | - -### Phase 2: Component Integration (HIGH Priority) - -| Task ID | Description | Files | Effort | -| ------- | -------------------------------------------------- | --------------------------------------- | ------ | -| T-005 | Integrate hook with `agent-sandbox.tsx` | `app/chat/components/agent-sandbox.tsx` | 4h | -| T-006 | Update `chat-context.tsx` to include Harness state | `app/chat/providers/chat-context.tsx` | 3h | -| T-007 | Add Harness connection status to UI | `app/chat/components/chat-header.tsx` | 2h | -| T-008 | Implement tool approval dialog | `app/chat/components/tool-approval.tsx` | 3h | - -### Phase 3: Event Streaming (MEDIUM Priority) - -| Task ID | Description | Files | Effort | -| ------- | ------------------------------------------------ | -------------------------------- | ------ | -| T-009 | Wire terminal streaming via `tool_update` events | `lib/harness/terminal-stream.ts` | 3h | -| T-010 | Implement file tree sync on workspace events | `lib/harness/file-sync.ts` | 3h | -| T-011 | Add test result streaming | `lib/harness/test-stream.ts` | 2h | -| T-012 | Handle subagent event propagation | `lib/harness/subagent-events.ts` | 2h | - -### Phase 4: Error Handling & Polish (MEDIUM Priority) - -| Task ID | Description | Files | Effort | -| ------- | ---------------------------------------------- | ---------------------------------------- | ------ | -| T-013 | Add error boundary for Harness failures | `app/chat/components/harness-error.tsx` | 2h | -| T-014 | Implement reconnection logic | `lib/hooks/use-harness-subscription.ts` | 2h | -| T-015 | Add unit tests for event mapper | `lib/harness/tests/event-mapper.test.ts` | 2h | -| T-016 | Add integration tests for Harness subscription | `lib/hooks/tests/use-harness.test.ts` | 3h | - ---- - -## File Structure - -``` -lib/ -├── hooks/ -│ ├── use-harness-subscription.ts # NEW: Main hook -│ └── tests/ -│ └── use-harness.test.ts # NEW: Hook tests -├── harness/ -│ ├── event-mapper.ts # NEW: Event→Data mapping -│ ├── terminal-stream.ts # NEW: Terminal streaming -│ ├── file-sync.ts # NEW: File tree sync -│ ├── test-stream.ts # NEW: Test result streaming -│ ├── subagent-events.ts # NEW: Subagent handling -│ └── tests/ -│ └── event-mapper.test.ts # NEW: Mapper tests -├── types/ -│ └── harness-events.ts # NEW: Event type definitions - -app/chat/ -├── providers/ -│ └── harness-context.tsx # NEW: Harness context -├── components/ -│ ├── agent-sandbox.tsx # MODIFY: Integrate hook -│ ├── tool-approval.tsx # NEW: Approval dialog -│ └── harness-error.tsx # NEW: Error boundary -``` - ---- - -## Testing Strategy - -### Unit Tests - -1. **Event Mapper Tests**: Verify `mapEventToSandboxData` correctly transforms each event type -2. **Hook Tests**: Test subscription lifecycle, cleanup, error handling -3. **Type Tests**: Ensure TypeScript types match Harness API - -### Integration Tests - -1. **Harness Subscription**: Verify events flow through to state updates -2. **Component Rendering**: Test `AgentSandbox` receives and displays data correctly -3. **Error Scenarios**: Test error boundaries and reconnection - -### Manual Testing - -1. Start Harness with each mode (plan, code, review, etc.) -2. Verify file operations update UI in real-time -3. Verify terminal streaming works correctly -4. Verify test results appear after test execution -5. Verify error states are handled gracefully - ---- - -## Dependencies - -| Package | Version | Purpose | -| -------------- | ------- | -------------------------- | -| `@mastra/core` | ^0.x | Harness class, event types | -| React | ^19.x | Hooks, context | -| @ai-sdk/react | ^1.x | Streaming integration | - ---- - -## Risks & Mitigations - -| Risk | Mitigation | -| ---------------------------------------- | -------------------------------------------------- | -| Harness API changes | Pin Mastra version, add API compatibility tests | -| Event flooding causes performance issues | Debounce events, batch updates | -| Memory leaks from subscriptions | Strict cleanup in useEffect return | -| Type mismatches between events and data | Comprehensive type definitions, runtime validation | - ---- - -## Acceptance Criteria - -- [ ] `useHarnessSubscription` hook created with proper cleanup -- [ ] All Harness events correctly mapped to `AgentSandboxData` fields -- [ ] Terminal output streams in real-time -- [ ] File tree updates on workspace operations -- [ ] Test results display correctly after test runs -- [ ] Error states handled with user-friendly messages -- [ ] All unit and integration tests pass -- [ ] No memory leaks on component unmount - ---- - -## Notes - -- **CRITICAL**: Do NOT use `data-sandbox` event type - this was temporary -- Leverage existing `mainHarness` configuration in `src/mastra/harness.ts` -- The workspace tools (`mastra_workspace_*`) emit the events we need -- Reference official Mastra docs for event signatures and payloads diff --git a/.agentstack/tasks/TASK-001-chat-streaming-fixes.md b/.agentstack/tasks/TASK-001-chat-streaming-fixes.md deleted file mode 100644 index a6358ebf..00000000 --- a/.agentstack/tasks/TASK-001-chat-streaming-fixes.md +++ /dev/null @@ -1,173 +0,0 @@ -# TASK-001: Chat Streaming Fixes - -**Status**: IN PROGRESS -**Priority**: CRITICAL -**Created**: 2026-02-16 - -## Problem Statement - -1. **Blank screen during reasoning**: Agent thinks but shows nothing until text arrives -2. **Token counter broken**: Already exists in UI but shows 0 - backend sends `usage.reasoningTokens=262` but UI doesn't parse it -3. **Reasoning not displayed**: Backend sends `providerOptions.google.thoughtSignature` but UI doesn't render it - -## Root Causes - -### 1. Token Counter Not Working - -- Backend sends in `step-finish` or `finish` parts -- UI doesn't extract from these part types -- File: `chat-context.tsx` - need to parse `step-finish` parts for usage - -### 2. Reasoning Not Displayed - -- Backend: `providerOptions.google.thoughtSignature` (base64 encoded) -- UI checks: `ReasoningUIPart.type === 'reasoning'` -- Mismatch: Google sends reasoning via providerMetadata, not as ReasoningUIPart -- File: `chat-context.tsx` lines 262-277 `streamingReasoning` useMemo - -### 3. Blank During Reasoning - -- `streamingContent` is empty during reasoning-only phase -- Loading indicator shows "Thinking..." but no reasoning content -- Need: Show `AgentReasoning` component when `streamingReasoning` has content but `streamingContent` is empty - -## Backend Evidence (from logs) - -```json -{ - "usage": { - "inputTokens": 12305, - "outputTokens": 908, - "totalTokens": 13213, - "reasoningTokens": 262 - }, - "providerOptions": { - "google": { - "thoughtSignature": "Es4ICssIAb4+9vsk+5nPbMKwgqEFaoK3BzttVElSqYCQnFewEG2J1TYiIlhkGNADgLFjRosd483aJTto3PkfhjFngu+DAXzMUoTX+pn02lLwVIHgiy+G6FDwwVaEu/3rIuQPYvS29yA+uUMomHBeWCS6UOHAUbMx4Jz/tLneDbyN35JkNA1PIOK7d5uGubBkZ3cRtLJtv1bYGUJvuBYcrylDCAp129IFdl3I0jR+WpNF+3Khk3tEFujyf4xYoC+2otS61gmyV3gDv23Fa9bpt3eRN6c/cWtN0Y5f928igx+wWVnFRwO+dbtLmovbsyLRoVr2C+4O8a4rKsZN00PaLy+ibuB4/st9pXGAoC70EFsHPcb1KhLg8igmHpXjGafhwKQ9dSBbfx09F0Yh5mP1FiKbL/AjpLuYB9wgSQZRr9FZI/FHgUme1CigXA+jx6AqoYzKAn7xaDnljR29n179UxP6YWQamdPwZZY37FO1wwlfU7hxgxvFQP/JP1u2ERl5M5ExIeOkIFIivRReYa/kNKnre4Kofy/0l9TplSGgpjKx6pHbvysYCxAWqFzriM2LoffCGMSdxxDbIdQE1cb15NRxau+MdWUOslzVmp0Uzs47r8p+rSxYYJxIB3TNiWc2sgguWwM9HwbTLFcGnt/WU7OWF5sl/woeu6J9QdUw4VbwlYNSutsK/fW8n52lI6VDC91DM+JyizBNGTgyYUClJZz/FqkVZiwpbrT0KE2Y5bXNpim2wLWT3we6Bjkf8DcEmVOCiFfou9APDgqqlHV791X7f3UtzzsIAqtUzzJS1rVMBYkTzwdm2aCgOpakzKinMAimi7pXUWweZelcAeSV0mkc/sNfLO5nAW9rZOwZFgMtXXSBi9OrWt+oBWgmo1lLA6EJpyxM0rYXwMP+LdaIvvBlOlUu3kXEU6MTzCPMDnfSzTBmWlwXEig3yOib7CkOBydVsbzFSYtYbNOwTucUZLnXaP01RcHnUrW6Rqn707tsULiD2nkxIX0jvZlSneOq7ia/MQnbsekigJDWyUeIzlUivjqsJ8T9G9a0VI0gRtwHp0/jyxUhZHb6RSfrJLQgRir+qpjnXcgEeTvtBOuIB8ONtUYut214sH05myFny0xpQwZLaiz4uDsdEBVh40oUNH72ZG4an00KgGZv772IJHOeF1buH55gKUnI+ukbkaMKe6Vc8t2+mSHQ4nGED9KV84eGYYMztjI2LtKOWLdifD/l5VYrO1GQwRRnkeAq1ns774nkYFvxMqxM/+NgCW6XOVew63f4B6XJlaIygdV5rMUVP4ZLVACqHYoRIpcWZVNNV2nv00ouCrzLABRFIIUwvIWyr5VTtNdXTGqny5e/1klcUpY0zZ8vP/QpSdzAeykdZw69wi0G0uBL5NjBnmAVSyLlO2TGoCOuIJa1MBrk8XZ37McNpzeDXYiYK+KsdfeRBQCLDkLnehFMX4QT+tIxg==" - } - } -} -``` - -## Key Files to Modify - -| File | Lines | What to Fix | -| ------------------- | --------- | ------------------------------------------------------ | -| `chat-context.tsx` | 262-277 | `streamingReasoning` - add thoughtSignature extraction | -| `chat-context.tsx` | ??? | Add usage extraction from `step-finish` parts | -| `chat-messages.tsx` | 1697-1735 | Show `AgentReasoning` during reasoning-only streaming | - -## AI SDK v6 Types Needed - -```typescript -import { - // Type guards - isReasoningUIPart, - isTextUIPart, - isStepStartUIPart, - isDataUIPart, - - // Part types - ReasoningUIPart, - TextUIPart, - StepStartUIPart, - - // Usage types - FinishReason, - StepResult, -} from 'ai' -``` - -## Implementation Plan - -### Step 1: Fix streamingReasoning Extraction - -```typescript -const streamingReasoning = useMemo(() => { - const lastMessage = messages[messages.length - 1] - if (lastMessage?.role === 'assistant') { - // 1. Check for ReasoningUIPart - const reasoningPart = lastMessage.parts?.find( - (p): p is ReasoningUIPart => p.type === 'reasoning' - ) - if (reasoningPart?.text) return reasoningPart.text - - // 2. Check providerMetadata.google.thoughtSignature - for (const part of lastMessage.parts ?? []) { - const pm = (part as any).providerMetadata - if (pm?.google?.thoughtSignature) { - // thoughtSignature is base64 encoded - try { - return atob(pm.google.thoughtSignature) - } catch { - return pm.google.thoughtSignature - } - } - } - - // 3. Fallback to extractThoughtSummaryFromParts - return extractThoughtSummaryFromParts(lastMessage.parts) - } - return '' -}, [messages]) -``` - -### Step 2: Fix Usage Extraction from step-finish - -```typescript -const usage: TokenUsage | null = useMemo(() => { - for (const message of messages) { - if (message.role === 'assistant') { - for (const part of message.parts ?? []) { - if (part.type === 'step-finish' || part.type === 'finish') { - const usageData = (part as any).usage - if (usageData) { - return { - inputTokens: - usageData.promptTokens ?? - usageData.inputTokens ?? - 0, - outputTokens: - usageData.completionTokens ?? - usageData.outputTokens ?? - 0, - totalTokens: usageData.totalTokens ?? 0, - inputTokenDetails: { - cacheReadTokens: - usageData.inputTokenDetails?.cacheRead ?? 0, - cacheWriteTokens: - usageData.inputTokenDetails?.cacheWrite ?? - 0, - noCacheTokens: - usageData.inputTokenDetails?.noCache ?? 0, - }, - outputTokenDetails: { - textTokens: - usageData.outputTokenDetails?.text ?? 0, - reasoningTokens: - usageData.outputTokenDetails?.reasoning ?? - 0, - }, - } - } - } - } - } - } - return null -}, [messages]) -``` - -### Step 3: Fix Blank During Reasoning - -In chat-messages.tsx, add condition to show AgentReasoning when: - -- `status === 'streaming'` -- `streamingReasoning` has content -- `streamingContent` is empty - -## Notes - -- Don't add token display - it EXISTS, just FIX the extraction -- Don't modify selectAgent - just needs to match agentId string -- Focus on extraction logic in chat-context.tsx diff --git a/.github/instructions/convex.instructions.md b/.github/instructions/convex.instructions.md new file mode 100644 index 00000000..a042030a --- /dev/null +++ b/.github/instructions/convex.instructions.md @@ -0,0 +1,663 @@ +--- +applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.jsx" +--- + +# Convex guidelines +## Function guidelines +### New function syntax +- ALWAYS use the new function syntax for Convex functions. For example: +```typescript +import { query } from "./_generated/server"; +import { v } from "convex/values"; +export const f = query({ + args: {}, + handler: async (ctx, args) => { + // Function body + }, +}); +``` + +### Http endpoint syntax +- HTTP endpoints are defined in `convex/http.ts` and require an `httpAction` decorator. For example: +```typescript +import { httpRouter } from "convex/server"; +import { httpAction } from "./_generated/server"; +const http = httpRouter(); +http.route({ + path: "/echo", + method: "POST", + handler: httpAction(async (ctx, req) => { + const body = await req.bytes(); + return new Response(body, { status: 200 }); + }), +}); +``` +- HTTP endpoints are always registered at the exact path you specify in the `path` field. For example, if you specify `/api/someRoute`, the endpoint will be registered at `/api/someRoute`. + +### Validators +- Below is an example of an array validator: +```typescript +import { mutation } from "./_generated/server"; +import { v } from "convex/values"; + +export default mutation({ +args: { + simpleArray: v.array(v.union(v.string(), v.number())), +}, +handler: async (ctx, args) => { + //... +}, +}); +``` +- Below is an example of a schema with validators that codify a discriminated union type: +```typescript +import { defineSchema, defineTable } from "convex/server"; +import { v } from "convex/values"; + +export default defineSchema({ + results: defineTable( + v.union( + v.object({ + kind: v.literal("error"), + errorMessage: v.string(), + }), + v.object({ + kind: v.literal("success"), + value: v.number(), + }), + ), + ) +}); +``` +- Here are the valid Convex types along with their respective validators: +Convex Type | TS/JS type | Example Usage | Validator for argument validation and schemas | Notes | +| ----------- | ------------| -----------------------| -----------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Id | string | `doc._id` | `v.id(tableName)` | | +| Null | null | `null` | `v.null()` | JavaScript's `undefined` is not a valid Convex value. Functions the return `undefined` or do not return will return `null` when called from a client. Use `null` instead. | +| Int64 | bigint | `3n` | `v.int64()` | Int64s only support BigInts between -2^63 and 2^63-1. Convex supports `bigint`s in most modern browsers. | +| Float64 | number | `3.1` | `v.number()` | Convex supports all IEEE-754 double-precision floating point numbers (such as NaNs). Inf and NaN are JSON serialized as strings. | +| Boolean | boolean | `true` | `v.boolean()` | +| String | string | `"abc"` | `v.string()` | Strings are stored as UTF-8 and must be valid Unicode sequences. Strings must be smaller than the 1MB total size limit when encoded as UTF-8. | +| Bytes | ArrayBuffer | `new ArrayBuffer(8)` | `v.bytes()` | Convex supports first class bytestrings, passed in as `ArrayBuffer`s. Bytestrings must be smaller than the 1MB total size limit for Convex types. | +| Array | Array | `[1, 3.2, "abc"]` | `v.array(values)` | Arrays can have at most 8192 values. | +| Object | Object | `{a: "abc"}` | `v.object({property: value})` | Convex only supports "plain old JavaScript objects" (objects that do not have a custom prototype). Objects can have at most 1024 entries. Field names must be nonempty and not start with "$" or "_". | +| Record | Record | `{"a": "1", "b": "2"}` | `v.record(keys, values)` | Records are objects at runtime, but can have dynamic keys. Keys must be only ASCII characters, nonempty, and not start with "$" or "_". | + +### Function registration +- Use `internalQuery`, `internalMutation`, and `internalAction` to register internal functions. These functions are private and aren't part of an app's API. They can only be called by other Convex functions. These functions are always imported from `./_generated/server`. +- Use `query`, `mutation`, and `action` to register public functions. These functions are part of the public API and are exposed to the public Internet. Do NOT use `query`, `mutation`, or `action` to register sensitive internal functions that should be kept private. +- You CANNOT register a function through the `api` or `internal` objects. +- ALWAYS include argument validators for all Convex functions. This includes all of `query`, `internalQuery`, `mutation`, `internalMutation`, `action`, and `internalAction`. +- If the JavaScript implementation of a Convex function doesn't have a return value, it implicitly returns `null`. + +### Function calling +- Use `ctx.runQuery` to call a query from a query, mutation, or action. +- Use `ctx.runMutation` to call a mutation from a mutation or action. +- Use `ctx.runAction` to call an action from an action. +- ONLY call an action from another action if you need to cross runtimes (e.g. from V8 to Node). Otherwise, pull out the shared code into a helper async function and call that directly instead. +- Try to use as few calls from actions to queries and mutations as possible. Queries and mutations are transactions, so splitting logic up into multiple calls introduces the risk of race conditions. +- All of these calls take in a `FunctionReference`. Do NOT try to pass the callee function directly into one of these calls. +- When using `ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction` to call a function in the same file, specify a type annotation on the return value to work around TypeScript circularity limitations. For example, +``` +export const f = query({ + args: { name: v.string() }, + handler: async (ctx, args) => { + return "Hello " + args.name; + }, +}); + +export const g = query({ + args: {}, + handler: async (ctx, args) => { + const result: string = await ctx.runQuery(api.example.f, { name: "Bob" }); + return null; + }, +}); +``` + +### Function references +- Function references are pointers to registered Convex functions. +- Use the `api` object defined by the framework in `convex/_generated/api.ts` to call public functions registered with `query`, `mutation`, or `action`. +- Use the `internal` object defined by the framework in `convex/_generated/api.ts` to call internal (or private) functions registered with `internalQuery`, `internalMutation`, or `internalAction`. +- Convex uses file-based routing, so a public function defined in `convex/example.ts` named `f` has a function reference of `api.example.f`. +- A private function defined in `convex/example.ts` named `g` has a function reference of `internal.example.g`. +- Functions can also registered within directories nested within the `convex/` folder. For example, a public function `h` defined in `convex/messages/access.ts` has a function reference of `api.messages.access.h`. + +### Api design +- Convex uses file-based routing, so thoughtfully organize files with public query, mutation, or action functions within the `convex/` directory. +- Use `query`, `mutation`, and `action` to define public functions. +- Use `internalQuery`, `internalMutation`, and `internalAction` to define private, internal functions. + +### Pagination +- Paginated queries are queries that return a list of results in incremental pages. +- You can define pagination using the following syntax: + +```ts +import { v } from "convex/values"; +import { query, mutation } from "./_generated/server"; +import { paginationOptsValidator } from "convex/server"; +export const listWithExtraArg = query({ + args: { paginationOpts: paginationOptsValidator, author: v.string() }, + handler: async (ctx, args) => { + return await ctx.db + .query("messages") + .withIndex("by_author", (q) => q.eq("author", args.author)) + .order("desc") + .paginate(args.paginationOpts); + }, +}); +``` +Note: `paginationOpts` is an object with the following properties: +- `numItems`: the maximum number of documents to return (the validator is `v.number()`) +- `cursor`: the cursor to use to fetch the next page of documents (the validator is `v.union(v.string(), v.null())`) +- A query that ends in `.paginate()` returns an object that has the following properties: +- page (contains an array of documents that you fetches) +- isDone (a boolean that represents whether or not this is the last page of documents) +- continueCursor (a string that represents the cursor to use to fetch the next page of documents) + + +## Validator guidelines +- `v.bigint()` is deprecated for representing signed 64-bit integers. Use `v.int64()` instead. +- Use `v.record()` for defining a record type. `v.map()` and `v.set()` are not supported. + +## Schema guidelines +- Always define your schema in `convex/schema.ts`. +- Always import the schema definition functions from `convex/server`. +- System fields are automatically added to all documents and are prefixed with an underscore. The two system fields that are automatically added to all documents are `_creationTime` which has the validator `v.number()` and `_id` which has the validator `v.id(tableName)`. +- Always include all index fields in the index name. For example, if an index is defined as `["field1", "field2"]`, the index name should be "by_field1_and_field2". +- Index fields must be queried in the same order they are defined. If you want to be able to query by "field1" then "field2" and by "field2" then "field1", you must create separate indexes. + +## Typescript guidelines +- You can use the helper typescript type `Id` imported from './_generated/dataModel' to get the type of the id for a given table. For example if there is a table called 'users' you can use `Id<'users'>` to get the type of the id for that table. +- If you need to define a `Record` make sure that you correctly provide the type of the key and value in the type. For example a validator `v.record(v.id('users'), v.string())` would have the type `Record, string>`. Below is an example of using `Record` with an `Id` type in a query: +```ts +import { query } from "./_generated/server"; +import { Doc, Id } from "./_generated/dataModel"; + +export const exampleQuery = query({ + args: { userIds: v.array(v.id("users")) }, + handler: async (ctx, args) => { + const idToUsername: Record, string> = {}; + for (const userId of args.userIds) { + const user = await ctx.db.get("users", userId); + if (user) { + idToUsername[user._id] = user.username; + } + } + + return idToUsername; + }, +}); +``` +- Be strict with types, particularly around id's of documents. For example, if a function takes in an id for a document in the 'users' table, take in `Id<'users'>` rather than `string`. +- Always use `as const` for string literals in discriminated union types. +- When using the `Array` type, make sure to always define your arrays as `const array: Array = [...];` +- When using the `Record` type, make sure to always define your records as `const record: Record = {...};` + +## Full text search guidelines +- A query for "10 messages in channel '#general' that best match the query 'hello hi' in their body" would look like: + +const messages = await ctx.db + .query("messages") + .withSearchIndex("search_body", (q) => + q.search("body", "hello hi").eq("channel", "#general"), + ) + .take(10); + +## Query guidelines +- Do NOT use `filter` in queries. Instead, define an index in the schema and use `withIndex` instead. +- Convex queries do NOT support `.delete()`. Instead, `.collect()` the results, iterate over them, and call `ctx.db.delete(row._id)` on each result. +- Use `.unique()` to get a single document from a query. This method will throw an error if there are multiple documents that match the query. +- When using async iteration, don't use `.collect()` or `.take(n)` on the result of a query. Instead, use the `for await (const row of query)` syntax. +### Ordering +- By default Convex always returns documents in ascending `_creationTime` order. +- You can use `.order('asc')` or `.order('desc')` to pick whether a query is in ascending or descending order. If the order isn't specified, it defaults to ascending. +- Document queries that use indexes will be ordered based on the columns in the index and can avoid slow table scans. + + +## Mutation guidelines +- Use `ctx.db.replace` to fully replace an existing document. This method will throw an error if the document does not exist. Syntax: `await ctx.db.replace('tasks', taskId, { name: 'Buy milk', completed: false })` +- Use `ctx.db.patch` to shallow merge updates into an existing document. This method will throw an error if the document does not exist. Syntax: `await ctx.db.patch('tasks', taskId, { completed: true })` + +## Action guidelines +- Always add `"use node";` to the top of files containing actions that use Node.js built-in modules. +- Never add `"use node";` to a file that also exports queries or mutations. Only actions can run in the Node.js runtime; queries and mutations must stay in the default Convex runtime. If you need Node.js built-ins alongside queries or mutations, put the action in a separate file. +- `fetch()` is available in the default Convex runtime. You do NOT need `"use node";` just to use `fetch()`. +- Never use `ctx.db` inside of an action. Actions don't have access to the database. +- Below is an example of the syntax for an action: +```ts +import { action } from "./_generated/server"; + +export const exampleAction = action({ + args: {}, + handler: async (ctx, args) => { + console.log("This action does not return anything"); + return null; + }, +}); +``` + +## Scheduling guidelines +### Cron guidelines +- Only use the `crons.interval` or `crons.cron` methods to schedule cron jobs. Do NOT use the `crons.hourly`, `crons.daily`, or `crons.weekly` helpers. +- Both cron methods take in a FunctionReference. Do NOT try to pass the function directly into one of these methods. +- Define crons by declaring the top-level `crons` object, calling some methods on it, and then exporting it as default. For example, +```ts +import { cronJobs } from "convex/server"; +import { internal } from "./_generated/api"; +import { internalAction } from "./_generated/server"; + +const empty = internalAction({ + args: {}, + handler: async (ctx, args) => { + console.log("empty"); + }, +}); + +const crons = cronJobs(); + +// Run `internal.crons.empty` every two hours. +crons.interval("delete inactive users", { hours: 2 }, internal.crons.empty, {}); + +export default crons; +``` +- You can register Convex functions within `crons.ts` just like any other file. +- If a cron calls an internal function, always import the `internal` object from '_generated/api', even if the internal function is registered in the same file. + + +## File storage guidelines +- Convex includes file storage for large files like images, videos, and PDFs. +- The `ctx.storage.getUrl()` method returns a signed URL for a given file. It returns `null` if the file doesn't exist. +- Do NOT use the deprecated `ctx.storage.getMetadata` call for loading a file's metadata. + +Instead, query the `_storage` system table. For example, you can use `ctx.db.system.get` to get an `Id<"_storage">`. +``` +import { query } from "./_generated/server"; +import { Id } from "./_generated/dataModel"; + +type FileMetadata = { + _id: Id<"_storage">; + _creationTime: number; + contentType?: string; + sha256: string; + size: number; +} + +export const exampleQuery = query({ + args: { fileId: v.id("_storage") }, + handler: async (ctx, args) => { + const metadata: FileMetadata | null = await ctx.db.system.get("_storage", args.fileId); + console.log(metadata); + return null; + }, +}); +``` +- Convex storage stores items as `Blob` objects. You must convert all items to/from a `Blob` when using Convex storage. + + +# Examples: +## Example: chat-app + +### Task +``` +Create a real-time chat application backend with AI responses. The app should: +- Allow creating users with names +- Support multiple chat channels +- Enable users to send messages to channels +- Automatically generate AI responses to user messages +- Show recent message history + +The backend should provide APIs for: +1. User management (creation) +2. Channel management (creation) +3. Message operations (sending, listing) +4. AI response generation using OpenAI's GPT-4 + +Messages should be stored with their channel, author, and content. The system should maintain message order +and limit history display to the 10 most recent messages per channel. + +``` + +### Analysis +1. Task Requirements Summary: +- Build a real-time chat backend with AI integration +- Support user creation +- Enable channel-based conversations +- Store and retrieve messages with proper ordering +- Generate AI responses automatically + +2. Main Components Needed: +- Database tables: users, channels, messages +- Public APIs for user/channel management +- Message handling functions +- Internal AI response generation system +- Context loading for AI responses + +3. Public API and Internal Functions Design: +Public Mutations: +- createUser: + - file path: convex/index.ts + - arguments: {name: v.string()} + - returns: v.object({userId: v.id("users")}) + - purpose: Create a new user with a given name +- createChannel: + - file path: convex/index.ts + - arguments: {name: v.string()} + - returns: v.object({channelId: v.id("channels")}) + - purpose: Create a new channel with a given name +- sendMessage: + - file path: convex/index.ts + - arguments: {channelId: v.id("channels"), authorId: v.id("users"), content: v.string()} + - returns: v.null() + - purpose: Send a message to a channel and schedule a response from the AI + +Public Queries: +- listMessages: + - file path: convex/index.ts + - arguments: {channelId: v.id("channels")} + - returns: v.array(v.object({ + _id: v.id("messages"), + _creationTime: v.number(), + channelId: v.id("channels"), + authorId: v.optional(v.id("users")), + content: v.string(), + })) + - purpose: List the 10 most recent messages from a channel in descending creation order + +Internal Functions: +- generateResponse: + - file path: convex/index.ts + - arguments: {channelId: v.id("channels")} + - returns: v.null() + - purpose: Generate a response from the AI for a given channel +- loadContext: + - file path: convex/index.ts + - arguments: {channelId: v.id("channels")} + - returns: v.array(v.object({ + _id: v.id("messages"), + _creationTime: v.number(), + channelId: v.id("channels"), + authorId: v.optional(v.id("users")), + content: v.string(), + })) +- writeAgentResponse: + - file path: convex/index.ts + - arguments: {channelId: v.id("channels"), content: v.string()} + - returns: v.null() + - purpose: Write an AI response to a given channel + +4. Schema Design: +- users + - validator: { name: v.string() } + - indexes: +- channels + - validator: { name: v.string() } + - indexes: +- messages + - validator: { channelId: v.id("channels"), authorId: v.optional(v.id("users")), content: v.string() } + - indexes + - by_channel: ["channelId"] + +5. Background Processing: +- AI response generation runs asynchronously after each user message +- Uses OpenAI's GPT-4 to generate contextual responses +- Maintains conversation context using recent message history + + +### Implementation + +#### package.json +```typescript +{ + "name": "chat-app", + "description": "This example shows how to build a chat app without authentication.", + "version": "1.0.0", + "dependencies": { + "convex": "^1.31.2", + "openai": "^6.0.0" + }, + "devDependencies": { + "typescript": "^5.7.3" + } +} +``` + +#### tsconfig.json +```typescript +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "exclude": ["convex"], + "include": ["**/src/**/*.tsx", "**/src/**/*.ts", "vite.config.ts"] +} +``` + +#### convex/index.ts +```typescript +import { + query, + mutation, + internalQuery, + internalMutation, + internalAction, +} from "./_generated/server"; +import { v } from "convex/values"; +import OpenAI from "openai"; +import { internal } from "./_generated/api"; + +/** + * Create a user with a given name. + */ +export const createUser = mutation({ + args: { + name: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.db.insert("users", { name: args.name }); + }, +}); + +/** + * Create a channel with a given name. + */ +export const createChannel = mutation({ + args: { + name: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.db.insert("channels", { name: args.name }); + }, +}); + +/** + * List the 10 most recent messages from a channel in descending creation order. + */ +export const listMessages = query({ + args: { + channelId: v.id("channels"), + }, + handler: async (ctx, args) => { + const messages = await ctx.db + .query("messages") + .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) + .order("desc") + .take(10); + return messages; + }, +}); + +/** + * Send a message to a channel and schedule a response from the AI. + */ +export const sendMessage = mutation({ + args: { + channelId: v.id("channels"), + authorId: v.id("users"), + content: v.string(), + }, + handler: async (ctx, args) => { + const channel = await ctx.db.get(args.channelId); + if (!channel) { + throw new Error("Channel not found"); + } + const user = await ctx.db.get(args.authorId); + if (!user) { + throw new Error("User not found"); + } + await ctx.db.insert("messages", { + channelId: args.channelId, + authorId: args.authorId, + content: args.content, + }); + await ctx.scheduler.runAfter(0, internal.index.generateResponse, { + channelId: args.channelId, + }); + return null; + }, +}); + +const openai = new OpenAI(); + +export const generateResponse = internalAction({ + args: { + channelId: v.id("channels"), + }, + handler: async (ctx, args) => { + const context = await ctx.runQuery(internal.index.loadContext, { + channelId: args.channelId, + }); + const response = await openai.chat.completions.create({ + model: "gpt-4o", + messages: context, + }); + const content = response.choices[0].message.content; + if (!content) { + throw new Error("No content in response"); + } + await ctx.runMutation(internal.index.writeAgentResponse, { + channelId: args.channelId, + content, + }); + return null; + }, +}); + +export const loadContext = internalQuery({ + args: { + channelId: v.id("channels"), + }, + handler: async (ctx, args) => { + const channel = await ctx.db.get(args.channelId); + if (!channel) { + throw new Error("Channel not found"); + } + const messages = await ctx.db + .query("messages") + .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) + .order("desc") + .take(10); + + const result = []; + for (const message of messages) { + if (message.authorId) { + const user = await ctx.db.get(message.authorId); + if (!user) { + throw new Error("User not found"); + } + result.push({ + role: "user" as const, + content: `${user.name}: ${message.content}`, + }); + } else { + result.push({ role: "assistant" as const, content: message.content }); + } + } + return result; + }, +}); + +export const writeAgentResponse = internalMutation({ + args: { + channelId: v.id("channels"), + content: v.string(), + }, + handler: async (ctx, args) => { + await ctx.db.insert("messages", { + channelId: args.channelId, + content: args.content, + }); + return null; + }, +}); +``` + +#### convex/schema.ts +```typescript +import { defineSchema, defineTable } from "convex/server"; +import { v } from "convex/values"; + +export default defineSchema({ + channels: defineTable({ + name: v.string(), + }), + + users: defineTable({ + name: v.string(), + }), + + messages: defineTable({ + channelId: v.id("channels"), + authorId: v.optional(v.id("users")), + content: v.string(), + }).index("by_channel", ["channelId"]), +}); +``` + +#### convex/tsconfig.json +```typescript +{ + /* This TypeScript project config describes the environment that + * Convex functions run in and is used to typecheck them. + * You can modify it, but some settings required to use Convex. + */ + "compilerOptions": { + /* These settings are not required by Convex and can be modified. */ + "allowJs": true, + "strict": true, + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + + /* These compiler options are required by Convex */ + "target": "ESNext", + "lib": ["ES2021", "dom"], + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "isolatedModules": true, + "noEmit": true + }, + "include": ["./**/*"], + "exclude": ["./_generated"] +} +``` + +#### src/App.tsx +```typescript +export default function App() { + return
Hello World
; +} +``` diff --git a/.gitignore b/.gitignore index b6985306..b365eb9e 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,6 @@ thoughts/ledgers/CONTINUITY_ses_37f1.md thoughts/ledgers/CONTINUITY_ses_3771.md thoughts/ledgers/CONTINUITY_ses_3772.md .sisyphus/plans/update-readme-accurate-stats.md +src/mastra/tracing.ts +.github/-/observability-setup.md +.github/-/testing-strategies.md diff --git a/app/components/landing-features.tsx b/app/components/landing-features.tsx index 9704ee6b..e2d62b6a 100644 --- a/app/components/landing-features.tsx +++ b/app/components/landing-features.tsx @@ -1,5 +1,6 @@ 'use client' +import { motion } from 'framer-motion' import { BotIcon, DatabaseIcon, @@ -12,6 +13,7 @@ import { } from 'lucide-react' import { Badge } from '@/ui/badge' import { BentoGrid, BentoGridItem } from '@/ui/effects/bento-grid' +import { cn } from '@/lib/utils' import { SectionLayout, useSectionReveal, @@ -101,13 +103,19 @@ export function LandingFeatures() { Features
-
+ -
+

Everything You Need @@ -119,25 +127,39 @@ export function LandingFeatures() { {/* Main feature grid (Bento) */} - - {FEATURES.map((feature, i) => ( - - ))} - + + + {FEATURES.map((feature, i) => ( + + ))} + + {/* Additional features */}
- {ADDITIONAL_FEATURES.map((feature) => ( -
( +
{feature.description}

-
+
))}
{/* Stats bar */} -
+
{[ { value: '22+', label: 'Specialized Agents' }, @@ -168,13 +196,13 @@ export function LandingFeatures() {
{item.value}
-
+
{item.label}
))}
-
+
) diff --git a/app/components/landing-hero.tsx b/app/components/landing-hero.tsx index bd4043c4..928fb3e3 100644 --- a/app/components/landing-hero.tsx +++ b/app/components/landing-hero.tsx @@ -2,122 +2,214 @@ import { useGSAP } from '@gsap/react' import { gsap } from 'gsap' -import { useRef } from 'react' +import { useRef, useEffect } from 'react' +import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion' import { ensureGsapRegistered } from '@/app/components/gsap/registry' import { AnimatedOrbitalLogo } from '@/app/components/gsap/animated-orbital-logo' -import { AnimatedQuantumLattice } from '@/app/components/gsap/svg-suite' +import { AnimatedQuantumLattice, AnimatedNeuralMesh } from '@/app/components/gsap/svg-suite' import { NetworkBackground } from './network-background' export function LandingHero() { const sectionRef = useRef(null) - const contentRef = useRef(null) + const containerRef = useRef(null) + + // Mouse Parallax values + const mouseX = useMotionValue(0) + const mouseY = useMotionValue(0) + + // Smooth springs for fluid motion + const springConfig = { damping: 25, stiffness: 150 } + const rotateX = useSpring(useTransform(mouseY, [-0.5, 0.5], [5, -5]), springConfig) + const rotateY = useSpring(useTransform(mouseX, [-0.5, 0.5], [-5, 5]), springConfig) + + // Deeper layers move more + const layer1RotateX = useSpring(useTransform(mouseY, [-0.5, 0.5], [10, -10]), springConfig) + const layer1RotateY = useSpring(useTransform(mouseX, [-0.5, 0.5], [-10, 10]), springConfig) + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!containerRef.current) { + return + } + const rect = containerRef.current.getBoundingClientRect() + const x = (e.clientX - rect.left) / rect.width - 0.5 + const y = (e.clientY - rect.top) / rect.height - 0.5 + mouseX.set(x) + mouseY.set(y) + } + + window.addEventListener('mousemove', handleMouseMove) + return () => window.removeEventListener('mousemove', handleMouseMove) + }, [mouseX, mouseY]) useGSAP( () => { ensureGsapRegistered() - if (!sectionRef.current || !contentRef.current) { - return - } - - const prefersReducedMotion = window.matchMedia( - '(prefers-reduced-motion: reduce)' - ).matches - - if (prefersReducedMotion) { - gsap.set('.hero-element', { opacity: 1, y: 0 }) - gsap.set(contentRef.current, { y: 0, scale: 1 }) + if (!sectionRef.current) { return } const tl = gsap.timeline() + // Initial "Big Bang" entry tl.fromTo( - '.hero-element', - { opacity: 0, y: 20 }, + '.hero-3d-node', + { opacity: 0, scale: 0.5, z: -500, rotateX: 45, rotateY: -45 }, { opacity: 1, - y: 0, - duration: 0.6, - ease: 'power2.out', + scale: 1, + z: 0, + rotateX: 0, + rotateY: 0, + duration: 1.2, + ease: 'expo.out', stagger: 0.1, } ) - gsap.to(contentRef.current, { - y: -50, - scale: 1.05, - ease: 'none', - scrollTrigger: { - trigger: sectionRef.current, - start: 'top bottom', - end: 'bottom top', - scrub: 1, + tl.fromTo( + '.hero-text-shimmer', + { opacity: 0, y: 40, filter: 'blur(10px)' }, + { + opacity: 1, + y: 0, + filter: 'blur(0px)', + duration: 0.8, + ease: 'power3.out', + stagger: 0.1, }, - }) + '-=0.8' + ) }, - { - scope: sectionRef, - } + { scope: sectionRef } ) return (
- {/* Interactive System Visualization */} - - - {/* Vignette to focus attention */} -
- -
-
- -
+ {/* 3D Stage Background */} +
+
-
- {/* Main Content */} -
-
- - - - SYSTEM OPERATIONAL - + {/* Decorative Ambient Glows */} +
+
+ +
+ + {/* Background Layer: Floating Grid element */} + +
+ + + {/* Floating 3D Cards */} +
+ +
+ +
+
+ + +
+ +
+
-

- Agent Orchestration -
- - Interactive Swarm - -

- -

- Visualizing real-time agent interactions. Hover over the - nodes to interact with the neural network. -

- -
-
- - Live Simulation - -
-
-
+ {/* Main Content Layer */} + +
+ +
+ + + + + + Nexus Core Online + +
+
+ +

+ AGENT +
+ + DYNAMICS + +

+ +

+ Architecting high-fidelity autonomous swarms. + Experience the next generation of + multi-agent orchestration + with real-time spatial visualization. +

+ +
+ + + +
+
+
+ + {/* Custom Floating Animation for 3D elements */} + + + {/* Stage Floor Shadow */} +
) } diff --git a/app/components/landing-stats.tsx b/app/components/landing-stats.tsx index 68b80bd8..506dcfe7 100644 --- a/app/components/landing-stats.tsx +++ b/app/components/landing-stats.tsx @@ -1,7 +1,7 @@ 'use client' import { useState, useEffect, useRef } from 'react' -import { useInView } from 'framer-motion' +import { motion, useInView } from 'framer-motion' import { BotIcon, WrenchIcon, @@ -139,13 +139,19 @@ export function LandingStats() { Platform Metrics
-
+ -
+

Measurable Performance at Scale @@ -157,11 +163,18 @@ export function LandingStats() { {/* Primary stats */} -
+
{STATS.map((stat) => ( -
-
+
{stat.description}
-
+ + {/* Decorative Glow */} +
+ ))}
{/* Secondary stats bar */} -
+
{SECONDARY_STATS.map((stat) => (
-
+
-
+
))}
-
+

Join thousands of developers building the future of AI diff --git a/app/globals.css b/app/globals.css index 2af0b7b1..71360c9b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -575,31 +575,80 @@ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); } - /* Mesh Gradient Background */ + /* Mesh Gradient Background - Enhanced with floating animation */ .mesh-gradient { background-color: var(--background); background-image: radial-gradient( at 0% 0%, - oklch(0.7 0.2 260 / 15%) 0px, - transparent 50% + oklch(0.65 0.25 265 / 15%) 0px, + transparent 55% ), radial-gradient( at 100% 0%, - oklch(0.6 0.2 300 / 15%) 0px, + oklch(0.6 0.22 310 / 12%) 0px, transparent 50% ), radial-gradient( at 100% 100%, - oklch(0.8 0.2 100 / 10%) 0px, - transparent 50% + oklch(0.75 0.18 110 / 8%) 0px, + transparent 45% ), radial-gradient( at 0% 100%, - oklch(0.5 0.2 200 / 15%) 0px, - transparent 50% + oklch(0.55 0.2 210 / 15%) 0px, + transparent 55% ); background-attachment: fixed; + background-size: 200% 200%; + animation: mesh-drift 20s ease infinite alternate; + } + + @keyframes mesh-drift { + 0% { + background-position: 0% 0%; + } + 100% { + background-position: 100% 100%; + } + } + + /* 3D Stage & Layers */ + .stage-3d { + perspective: 1200px; + transform-style: preserve-3d; + } + + .layer-3d { + transform-style: preserve-3d; + backface-visibility: hidden; + } + + .layer-back { + transform: translateZ(-100px) scale(1.1); + } + .layer-mid { + transform: translateZ(0); + } + .layer-front { + transform: translateZ(100px) scale(0.9); + } + + /* Ultra-Refined Glassmorphism */ + .glass-ultra { + background: color-mix(in oklch, var(--surface-glass), transparent 20%); + backdrop-filter: blur(20px) saturate(180%); + border: 1px solid oklch(1 0 0 / 15%); + box-shadow: + 0 8px 32px 0 oklch(0 0 0 / 15%), + inset 0 0 1px 1px oklch(1 0 0 / 10%); + } + + .glass-morphism { + background: oklch(1 0 0 / 5%); + backdrop-filter: blur(8px); + border: 1px solid oklch(1 0 0 / 10%); + box-shadow: 0 4px 24px 0 oklch(0 0 0 / 8%); } /* @@ -921,7 +970,8 @@ ), color-mix(in oklch, var(--card), transparent 14%); box-shadow: - 0 10px 30px -16px color-mix(in oklch, var(--primary), transparent 68%), + 0 10px 30px -16px + color-mix(in oklch, var(--primary), transparent 68%), inset 0 1px 0 color-mix(in oklch, white, transparent 90%); } diff --git a/convex/schema.ts b/convex/schema.ts index c9de4e19..54cdcb62 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,5 +1,4 @@ -import { defineSchema, defineTable } from 'convex/server'; -import { v } from 'convex/values'; +import { defineSchema } from 'convex/server'; import { mastraThreadsTable, mastraMessagesTable, @@ -8,21 +7,9 @@ import { mastraVectorIndexesTable, mastraVectorsTable, mastraDocumentsTable, + mastraWorkflowSnapshotsTable, } from '@mastra/convex/schema'; -// Explicitly define mastra_workflow_snapshots to ensure the `id` field exists -// (some downstream Convex schema variants omit `id` which causes an invalid -// index declaration during Convex schema push). -const mastraWorkflowSnapshotsTable = defineTable({ - id: v.string(), - workflow_name: v.string(), - run_id: v.string(), - resourceId: v.optional(v.string()), - snapshot: v.any(), - createdAt: v.string(), - updatedAt: v.string(), -}).index('by_record_id', ['id']).index('by_workflow_run', ['workflow_name', 'run_id']).index('by_workflow', ['workflow_name']).index('by_resource', ['resourceId']).index('by_created', ['createdAt']); - export default defineSchema({ mastra_threads: mastraThreadsTable, mastra_messages: mastraMessagesTable, @@ -32,4 +19,5 @@ export default defineSchema({ mastra_vector_indexes: mastraVectorIndexesTable, mastra_vectors: mastraVectorsTable, mastra_documents: mastraDocumentsTable, + mastra_workflows: mastraWorkflowSnapshotsTable }); diff --git a/dot/tests/test-results/test-results.json b/dot/tests/test-results/test-results.json deleted file mode 100644 index 50aa167e..00000000 --- a/dot/tests/test-results/test-results.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "numTotalTestSuites": 0, - "numPassedTestSuites": 0, - "numFailedTestSuites": 0, - "numPendingTestSuites": 0, - "numTotalTests": 0, - "numPassedTests": 0, - "numFailedTests": 0, - "numPendingTests": 0, - "numTodoTests": 0, - "snapshot": { - "added": 0, - "failure": false, - "filesAdded": 0, - "filesRemoved": 0, - "filesRemovedList": [], - "filesUnmatched": 0, - "filesUpdated": 0, - "matched": 0, - "total": 0, - "unchecked": 0, - "uncheckedKeysByFile": [], - "unmatched": 0, - "updated": 0, - "didUpdate": false - }, - "startTime": 1768217443502, - "success": false, - "testResults": [] -} diff --git a/package-lock.json b/package-lock.json index 8be3da9f..168b364d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,7 +151,7 @@ "react-chartjs-2": "^5.3.1", "react-dom": "^19.2.4", "react-jsx-parser": "^2.4.1", - "react-resizable-panels": "^4.6.5", + "react-resizable-panels": "^4.7.0", "recharts": "^2.15.4", "rehype": "^13.0.2", "rehype-class-names": "^2.0.0", @@ -31322,9 +31322,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.6.5.tgz", - "integrity": "sha512-pmQP6qv9KmsesNMvWVNvVfVJAwYSOWWbAOAtrPR8Cre20+j1NWIlyft0btjtDQE+OepXmI6g3VPrCXQY0oD7+Q==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.7.0.tgz", + "integrity": "sha512-QJTeh8KJR6lKzDwVez+WGPPUPuarIjR3ptg23thJ0G5dAMYJH4iSN1AnNY0DuSP+PgH8s9eYfwrR7AlmX6TvYA==", "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0", diff --git a/package.json b/package.json index e2573912..27b790b4 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,7 @@ "react-chartjs-2": "^5.3.1", "react-dom": "^19.2.4", "react-jsx-parser": "^2.4.1", - "react-resizable-panels": "^4.6.5", + "react-resizable-panels": "^4.7.0", "recharts": "^2.15.4", "rehype": "^13.0.2", "rehype-class-names": "^2.0.0", diff --git a/src/components/ai-elements/schema-display.tsx b/src/components/ai-elements/schema-display.tsx index 6e069e94..10e66edb 100644 --- a/src/components/ai-elements/schema-display.tsx +++ b/src/components/ai-elements/schema-display.tsx @@ -94,7 +94,7 @@ export const SchemaDisplay = ({

- {description && } + {(Boolean(description)) && } {parameters && parameters.length > 0 && ( @@ -282,7 +282,7 @@ export const SchemaDisplayParameter = ({ )}
- {description && ( + {(Boolean(description)) && (

{description}

)}
@@ -397,7 +397,7 @@ export const SchemaDisplayProperty = ({ )} - {description && ( + {(Boolean(description)) && (

)}

- {description && ( + {(Boolean(description)) && (

{description}

)}