mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
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:
269
.cursor/plans/ai_chat_write_agent_9b3e5be2.plan.md
Normal file
269
.cursor/plans/ai_chat_write_agent_9b3e5be2.plan.md
Normal 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
|
||||
301
.cursor/plans/custom_homepage_configuration_83419476.plan.md
Normal file
301
.cursor/plans/custom_homepage_configuration_83419476.plan.md
Normal 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"
|
||||
Reference in New Issue
Block a user