feat: add AI Agent chat integration with Anthropic Claude API

Add AI writing assistant (Agent) powered by Anthropic Claude API. Agent can be enabled on Write page (replaces textarea) and optionally in RightSidebar on posts/pages via frontmatter.

Features:
- AIChatView component with per-page chat history
- Page content context support for AI responses
- Markdown rendering for AI responses
- User-friendly error handling for missing API keys
- System prompt configurable via Convex environment variables
- Anonymous session authentication using localStorage

Environment variables required:
- ANTHROPIC_API_KEY (required)
- CLAUDE_PROMPT_STYLE, CLAUDE_PROMPT_COMMUNITY, CLAUDE_PROMPT_RULES (optional split prompts)
- CLAUDE_SYSTEM_PROMPT (optional single prompt fallback)

Configuration:
- siteConfig.aiChat.enabledOnWritePage: Enable Agent toggle on /write page
- siteConfig.aiChat.enabledOnContent: Allow Agent on posts/pages via frontmatter
- Frontmatter aiChat: true (requires rightSidebar: true)

Updated files:
- src/components/AIChatView.tsx: AI chat interface component
- src/components/RightSidebar.tsx: Conditional Agent rendering
- src/pages/Write.tsx: Agent mode toggle (title changes to Agent)
- convex/aiChats.ts: Chat history queries and mutations
- convex/aiChatActions.ts: Claude API integration with error handling
- convex/schema.ts: aiChats table with indexes
- src/config/siteConfig.ts: AIChatConfig interface
- Documentation updated across all files

Documentation:
- files.md: Updated component descriptions
- changelog.md: Added v1.33.0 entry
- TASK.md: Marked AI chat tasks as completed
- README.md: Added AI Agent Chat section
- content/pages/docs.md: Added AI Agent chat documentation
- content/blog/setup-guide.md: Added AI Agent chat setup instructions
- public/raw/changelog.md: Added v1.33.0 entry
This commit is contained in:
Wayne Sutton
2025-12-26 12:31:33 -08:00
parent 50890e9153
commit bfe88d0217
34 changed files with 3867 additions and 245 deletions

View File

@@ -0,0 +1,269 @@
---
name: AI Chat Write Agent
overview: Implement an AI-powered chat write agent that integrates with the Write page (replacing textarea when active) and optionally appears in the RightSidebar on posts/pages. Uses Anthropic Claude API with anonymous sessions, per-page chat history stored in Convex, and optional page content loading.
todos:
- id: schema
content: Add aiChats table to convex/schema.ts with indexes
status: completed
- id: backend-mutations
content: Create convex/aiChats.ts with queries and mutations
status: completed
dependencies:
- schema
- id: backend-action
content: Create convex/aiChatActions.ts with Claude API integration
status: completed
dependencies:
- backend-mutations
- id: siteconfig
content: Add AIChatConfig interface and defaults to siteConfig.ts
status: completed
- id: chat-component
content: Create AIChatView.tsx component with full chat UI
status: completed
dependencies:
- backend-mutations
- backend-action
- id: chat-styles
content: Add AI chat CSS styles to global.css (theme-aware)
status: completed
dependencies:
- chat-component
- id: right-sidebar
content: Update RightSidebar.tsx to conditionally render AIChatView
status: completed
dependencies:
- chat-component
- siteconfig
- id: write-page
content: Update Write.tsx with AI chat mode toggle
status: completed
dependencies:
- chat-component
- siteconfig
- id: post-page
content: Update Post.tsx to pass content context to RightSidebar
status: completed
dependencies:
- right-sidebar
- id: frontmatter
content: Add aiChat field to sync-posts.ts and Write.tsx field definitions
status: completed
- id: dependencies
content: Add @anthropic-ai/sdk to package.json
status: completed
---
# AI Chat Write Agent Implementation
## Architecture Overview
```mermaid
flowchart TB
subgraph frontend [Frontend Components]
AIChatView[AIChatView Component]
WritePage[Write.tsx]
RightSidebar[RightSidebar.tsx]
PostPage[Post.tsx]
end
subgraph convex [Convex Backend]
aiChats[aiChats.ts - Queries/Mutations]
aiChatActions[aiChatActions.ts - Claude API Action]
schema[schema.ts - aiChats table]
end
subgraph external [External Services]
Claude[Anthropic Claude API]
end
WritePage -->|mode toggle| AIChatView
RightSidebar -->|when aiChat enabled| AIChatView
PostPage -->|passes content context| RightSidebar
AIChatView -->|mutations| aiChats
AIChatView -->|action| aiChatActions
aiChatActions -->|API call| Claude
aiChats -->|read/write| schema
```
## Key Design Decisions
- **Anonymous Sessions**: Uses localStorage `sessionId` (UUID) until auth is added
- **Chat Scope**: Per-page using slug as context identifier (e.g., "write-page", "about", "my-post-slug")
- **Page Context**: Optional button to load current page's markdown into chat context
- **Mode Toggle**: Write page switches between textarea and chat interface
- **Configuration**: Separate siteConfig toggles for Write page and RightSidebar
---
## 1. Database Schema Updates
Update [`convex/schema.ts`](convex/schema.ts) to add the `aiChats` table:
```typescript
aiChats: defineTable({
sessionId: v.string(),
contextId: v.string(), // slug or "write-page"
messages: v.array(
v.object({
role: v.union(v.literal("user"), v.literal("assistant")),
content: v.string(),
timestamp: v.number(),
}),
),
pageContext: v.optional(v.string()), // loaded page content
lastMessageAt: v.optional(v.number()),
})
.index("by_session_and_context", ["sessionId", "contextId"])
.index("by_session", ["sessionId"])
```
---
## 2. Convex Backend
### Create [`convex/aiChats.ts`](convex/aiChats.ts)
Queries and mutations for chat management:
- `getAIChatByContext` - Fetch chat for sessionId + contextId
- `getOrCreateAIChat` - Create chat if none exists
- `addUserMessage` - Add user message
- `addAssistantMessage` - Internal mutation for AI response
- `clearChat` - Clear messages
- `setPageContext` - Store loaded page content
### Create [`convex/aiChatActions.ts`](convex/aiChatActions.ts)
Node.js action for Claude API:
- `generateResponse` - Calls Claude API with conversation history
- Uses `ANTHROPIC_API_KEY` environment variable
- Loads system prompt from `CLAUDE_SYSTEM_PROMPT` or split `CLAUDE_PROMPT_*` variables
- Model: `claude-sonnet-4-20250514` with 2048 max tokens
- Includes last 20 messages as context
---
## 3. Frontend Components
### Create [`src/components/AIChatView.tsx`](src/components/AIChatView.tsx)
Main chat component with:
- Message list with markdown rendering (react-markdown)
- Auto-expanding textarea input
- Send button and keyboard shortcuts (Enter to send, Shift+Enter newline)
- Loading state with stop generation button
- Clear chat command ("clear")
- Copy message button on AI responses
- "Load Page Content" button (when page content available)
- Theme-aware styling matching existing UI
### Update [`src/components/RightSidebar.tsx`](src/components/RightSidebar.tsx)
Transform from empty placeholder to conditional chat container:
- Accept `aiChatEnabled`, `pageContent`, and `slug` props
- Render `AIChatView` when enabled
- Pass page context for "Load Content" feature
---
## 4. Page Updates
### Update [`src/pages/Write.tsx`](src/pages/Write.tsx)
Add AI chat mode toggle:
- New state: `isAIChatMode` (boolean)
- Add "AI Chat" button in Actions section (using `ChatCircle` from Phosphor Icons)
- When active: replace `<textarea>` with `<AIChatView contextId="write-page" />`
- Show "Back to Editor" button to switch back
- Respect `siteConfig.aiChat.enabledOnWritePage` setting
### Update [`src/pages/Post.tsx`](src/pages/Post.tsx)
Pass content to RightSidebar:
- Check `siteConfig.aiChat.enabledOnContent` AND frontmatter `aiChat: true`
- Pass `pageContent={content}` and `slug` to RightSidebar
- Add new frontmatter field `aiChat` to field definitions
---
## 5. Configuration
### Update [`src/config/siteConfig.ts`](src/config/siteConfig.ts)
Add AI chat configuration interface and defaults:
```typescript
export interface AIChatConfig {
enabledOnWritePage: boolean; // Show AI chat on /write
enabledOnContent: boolean; // Allow AI chat on posts/pages via frontmatter
}
// In SiteConfig interface:
aiChat: AIChatConfig;
// Default values:
aiChat: {
enabledOnWritePage: true,
enabledOnContent: true,
},
```
### Update Frontmatter Fields
Add `aiChat` field to sync scripts and field definitions:
- [`scripts/sync-posts.ts`](scripts/sync-posts.ts) - Add to PostFrontmatter and PageFrontmatter
- [`src/pages/Write.tsx`](src/pages/Write.tsx) - Add to POST_FIELDS and PAGE_FIELDS
---
## 6. Styling
### Update [`src/styles/global.css`](src/styles/global.css)
Add AI chat styles (approximately 300-400 lines):
- `.ai-chat-view` - Main container
- `.ai-chat-messages` - Scrollable message list
- `.ai-chat-message`, `.ai-chat-message-user`, `.ai-chat-message-assistant`
- `.ai-chat-input-container`, `.ai-chat-input`, `.ai-chat-send-button`
- `.ai-chat-loading`, `.ai-chat-stop-button`
- `.ai-chat-copy-button`, `.ai-chat-clear-button`
- `.ai-chat-load-context-button`
- Theme variants for dark, light, tan, cloud
- Mobile responsive styles
---
## 7. Dependencies
### Update [`package.json`](package.json)
Add required packages:
```json
"@anthropic-ai/sdk": "^0.71.2"
```
Note: `react-markdown` and `remark-gfm` already exist in the project.---
## 8. Environment Variables
Add to Convex deployment:
- `ANTHROPIC_API_KEY` (required) - Claude API key
- `CLAUDE_SYSTEM_PROMPT` (optional) - Custom system prompt for writing assistant
---
## File Summary

View File

@@ -0,0 +1,301 @@
---
name: Custom Homepage Configuration
overview: Add configuration to set any page or blog post as the homepage, with all Post component features (sidebar, copy dropdown, etc.) but without the featured section. Original homepage remains accessible at /home.
todos: []
---
# Cus
tom Homepage ConfigurationAllow configuring any page or blog post to serve as the homepage while preserving all Post component features (sidebar, copy dropdown, author info, etc.) and using the page/post's metadata for SEO.
## Architecture Overview
```mermaid
flowchart TD
A[User visits /] --> B{homepage.type?}
B -->|default| C[Home Component]
B -->|page| D[Post Component with page slug]
B -->|post| E[Post Component with post slug]
D --> F[Render page content]
E --> G[Render post content]
F --> H[No featured section]
G --> H
H --> I[All Post features: sidebar, copy dropdown, etc.]
J[User visits /home] --> C
```
## Implementation Steps
### 1. Update `src/config/siteConfig.ts`
Add homepage configuration interface and default:
```typescript
// Add to SiteConfig interface
export interface HomepageConfig {
type: "default" | "page" | "post";
slug?: string; // Required if type is "page" or "post"
originalHomeRoute?: string; // Route to access original homepage (default: "/home")
}
export interface SiteConfig {
// ... existing fields ...
homepage: HomepageConfig;
}
// Add to siteConfig object
export const siteConfig: SiteConfig = {
// ... existing config ...
homepage: {
type: "default", // Options: "default", "page", "post"
slug: undefined, // e.g., "about" or "welcome-post"
originalHomeRoute: "/home", // Route to access original homepage
},
};
```
### 2. Update `src/App.tsx`
Modify routing to conditionally render homepage:
```typescript
function App() {
usePageTracking();
const location = useLocation();
if (location.pathname === "/write") {
return <Write />;
}
// Determine if we should use a custom homepage
const useCustomHomepage =
siteConfig.homepage.type !== "default" &&
siteConfig.homepage.slug;
return (
<SidebarProvider>
<Layout>
<Routes>
{/* Homepage route - either default Home or custom page/post */}
<Route
path="/"
element={
useCustomHomepage ? (
<Post
slug={siteConfig.homepage.slug!}
isHomepage={true}
homepageType={siteConfig.homepage.type}
/>
) : (
<Home />
)
}
/>
{/* Original homepage route (when custom homepage is set) */}
{useCustomHomepage && (
<Route
path={siteConfig.homepage.originalHomeRoute || "/home"}
element={<Home />}
/>
)}
{/* ... rest of routes ... */}
</Routes>
</Layout>
</SidebarProvider>
);
}
```
### 3. Update `src/pages/Post.tsx`
Add props to support homepage mode and hide back button:
```typescript
interface PostProps {
slug?: string; // Optional slug prop when used as homepage
isHomepage?: boolean; // Flag to indicate this is the homepage
homepageType?: "page" | "post"; // Type of homepage content
}
export default function Post({
slug: propSlug,
isHomepage = false,
homepageType
}: PostProps = {}) {
const { slug: routeSlug } = useParams<{ slug: string }>();
const slug = propSlug || routeSlug;
// ... existing queries ...
// Conditionally hide back button when used as homepage
// In the render section:
{!isHomepage && (
<button onClick={() => navigate("/")} className="back-button">
<ArrowLeft size={16} />
<span>Back</span>
</button>
)}
// ... rest of component unchanged ...
}
```
### 4. Update `scripts/configure-fork.ts`
Add homepage configuration support:
```typescript
interface ForkConfig {
// ... existing fields ...
homepage?: {
type: "default" | "page" | "post";
slug?: string;
originalHomeRoute?: string;
};
}
// Add update function
function updateSiteConfig(config: ForkConfig): void {
// ... existing updates ...
if (config.homepage) {
const homepageConfig = JSON.stringify(config.homepage, null, 2)
.replace(/"/g, '"')
.replace(/\n/g, '\n ');
updateFile(
"src/config/siteConfig.ts",
[
{
search: /homepage:\s*\{[^}]*\},/s,
replace: `homepage: ${homepageConfig},`,
},
]
);
}
}
```
### 5. Update `FORK_CONFIG.md`
Add homepage configuration section:
````markdown
## Homepage Configuration
You can set any page or blog post to serve as your homepage.
### In fork-config.json
```json
{
"homepage": {
"type": "page",
"slug": "about",
"originalHomeRoute": "/home"
}
}
````
Options:
- `type`: `"default"` (standard homepage), `"page"` (use a static page), or `"post"` (use a blog post)
- `slug`: The slug of the page or post to use (required if type is "page" or "post")
- `originalHomeRoute`: Route to access the original homepage (default: "/home")
### Manual Configuration
In `src/config/siteConfig.ts`:
```typescript
homepage: {
type: "page", // or "post" or "default"
slug: "about", // slug of page/post to use
originalHomeRoute: "/home", // route to original homepage
},
```
### Notes
- Custom homepage uses the page/post's full content and features (sidebar, copy dropdown, etc.)
- Featured section is NOT shown on custom homepage
- SEO metadata comes from the page/post's frontmatter
- Original homepage remains accessible at `/home` when custom homepage is set
````javascript
### 6. Update `fork-config.json.example`
Add homepage configuration example:
```json
{
"homepage": {
"type": "default",
"slug": null,
"originalHomeRoute": "/home"
}
}
````
## Files to Modify
1. `src/config/siteConfig.ts` - Add HomepageConfig interface and default config
2. `src/App.tsx` - Conditional homepage routing
3. `src/pages/Post.tsx` - Add props for homepage mode, hide back button
4. `scripts/configure-fork.ts` - Add homepage config parsing and file updates
5. `FORK_CONFIG.md` - Document homepage configuration
6. `fork-config.json.example` - Add homepage config example
## Behavior Details
### Custom Homepage (page/post)
- Renders using Post component with all features
- Shows page/post content with markdown rendering
- Includes sidebar if `layout: "sidebar"` in frontmatter
- Includes copy dropdown, author info, tags (for posts)
- Does NOT show back button
- Does NOT show featured section
- Uses page/post metadata for SEO (title, description, OG image)
### Original Homepage
- Remains accessible at `/home` (or configured route)
- Shows all standard features (featured section, posts list, etc.)
- Unchanged functionality
### Default Behavior
- When `homepage.type === "default"`, standard Home component renders at `/`
- No `/home` route is created
## Testing Checklist
- [ ] Default homepage works at `/`
- [ ] Custom page homepage renders at `/` with all Post features
- [ ] Custom post homepage renders at `/` with all Post features
- [ ] Back button hidden on custom homepage
- [ ] Featured section not shown on custom homepage
- [ ] Original homepage accessible at `/home` when custom homepage set
- [ ] SEO metadata uses page/post frontmatter
- [ ] Sidebar works on custom homepage if layout is "sidebar"