179 lines
4.7 KiB
Markdown
179 lines
4.7 KiB
Markdown
|
|
# AGENTS.md - Interview Bot
|
||
|
|
|
||
|
|
> AI-powered interviewer bot with video call interface and calendar scheduling on Cloudflare Workers.
|
||
|
|
|
||
|
|
## Quick Reference
|
||
|
|
|
||
|
|
| Command | Description |
|
||
|
|
|---------|-------------|
|
||
|
|
| `npm run dev` | Start local dev server (port 8787) |
|
||
|
|
| `npm run deploy` | Deploy to Cloudflare Workers |
|
||
|
|
| `npm run tail` | Stream production logs |
|
||
|
|
|
||
|
|
## Project Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
src/index.ts # Entire app: API routes, AI chat, HTML/CSS/JS frontend
|
||
|
|
wrangler.toml # Cloudflare Workers config (KV bindings, AI binding)
|
||
|
|
package.json # Dependencies and npm scripts
|
||
|
|
```
|
||
|
|
|
||
|
|
**Single-file architecture**: All backend routes + frontend UI lives in `src/index.ts`.
|
||
|
|
|
||
|
|
## Build & Development
|
||
|
|
|
||
|
|
```bash
|
||
|
|
npm install && npm run dev # Local dev at http://localhost:8787
|
||
|
|
npm run deploy # Deploy to production
|
||
|
|
```
|
||
|
|
|
||
|
|
### KV Setup (First-time only)
|
||
|
|
```bash
|
||
|
|
npx wrangler kv namespace create INTERVIEWS
|
||
|
|
npx wrangler kv namespace create INTERVIEWS --preview
|
||
|
|
# Update wrangler.toml with returned IDs
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
**No test framework.** Manual testing only:
|
||
|
|
- `GET /api/interviews` - List interviews
|
||
|
|
- `POST /api/interviews` - Create interview
|
||
|
|
- `PUT /api/interviews/:id` - Update interview
|
||
|
|
- `DELETE /api/interviews/:id` - Delete interview
|
||
|
|
- `POST /api/chat` - Stream AI chat
|
||
|
|
|
||
|
|
## Code Style
|
||
|
|
|
||
|
|
### TypeScript Patterns
|
||
|
|
|
||
|
|
- **Runtime**: Cloudflare Workers (no Node.js APIs)
|
||
|
|
- **No tsconfig.json**: Wrangler handles TypeScript compilation
|
||
|
|
- **Explicit types**: Always type function params and return values
|
||
|
|
- **Interfaces**: Define at file top, before constants
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Interfaces at top
|
||
|
|
interface Interview {
|
||
|
|
id: string;
|
||
|
|
candidateName: string;
|
||
|
|
status: 'scheduled' | 'in-progress' | 'completed' | 'cancelled';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Explicit return types
|
||
|
|
async function getInterviews(env: Env, corsHeaders: Record<string, string>): Promise<Response> {
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
|
||
|
|
// Type assertions for JSON
|
||
|
|
const body = await request.json() as Partial<Interview>;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Naming Conventions
|
||
|
|
|
||
|
|
| Element | Convention | Example |
|
||
|
|
|---------|------------|---------|
|
||
|
|
| Interfaces | PascalCase | `Interview`, `ChatMessage`, `Env` |
|
||
|
|
| Functions | camelCase | `handleChat`, `getInterviews` |
|
||
|
|
| Constants | SCREAMING_SNAKE | `SYSTEM_PROMPT` |
|
||
|
|
| Variables | camelCase | `corsHeaders`, `currentView` |
|
||
|
|
|
||
|
|
### Error Handling
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
try {
|
||
|
|
// operation
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error:', error);
|
||
|
|
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
|
||
|
|
status: 500,
|
||
|
|
headers: { 'Content-Type': 'application/json', ...corsHeaders },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### API Response Pattern
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Always include CORS headers
|
||
|
|
const corsHeaders = {
|
||
|
|
'Access-Control-Allow-Origin': '*',
|
||
|
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||
|
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||
|
|
};
|
||
|
|
|
||
|
|
// JSON response
|
||
|
|
return new Response(JSON.stringify(data), {
|
||
|
|
status: 200,
|
||
|
|
headers: { 'Content-Type': 'application/json', ...corsHeaders },
|
||
|
|
});
|
||
|
|
|
||
|
|
// SSE streaming response
|
||
|
|
return new Response(stream as ReadableStream, {
|
||
|
|
headers: { 'Content-Type': 'text/event-stream', ...corsHeaders },
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### Routing (No framework)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
if (path === '/api/interviews' && request.method === 'GET') {
|
||
|
|
return await getInterviews(env, corsHeaders);
|
||
|
|
}
|
||
|
|
if (path.startsWith('/api/interviews/') && request.method === 'DELETE') {
|
||
|
|
const id = path.split('/')[3];
|
||
|
|
return await deleteInterview(id, env, corsHeaders);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Frontend (Inline in getHTML())
|
||
|
|
|
||
|
|
- CSS: Custom properties in `:root`, inline `<style>` block
|
||
|
|
- JS: Inline `<script>` at end of body, vanilla DOM manipulation
|
||
|
|
- State: Module-level variables (`let interviews = []`)
|
||
|
|
|
||
|
|
## Environment Bindings
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export interface Env {
|
||
|
|
AI: Ai; // Workers AI
|
||
|
|
INTERVIEWS: KVNamespace; // KV storage
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Key Data Structures
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface Interview {
|
||
|
|
id: string;
|
||
|
|
candidateName: string;
|
||
|
|
email: string;
|
||
|
|
scheduledAt: string; // ISO 8601
|
||
|
|
duration: number; // minutes
|
||
|
|
status: 'scheduled' | 'in-progress' | 'completed' | 'cancelled';
|
||
|
|
notes: string[];
|
||
|
|
transcript: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ChatMessage {
|
||
|
|
role: 'system' | 'user' | 'assistant';
|
||
|
|
content: string;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Common Tasks
|
||
|
|
|
||
|
|
**Add API endpoint**: Add handler function + path match in `fetch` handler + include `corsHeaders`
|
||
|
|
|
||
|
|
**Modify AI behavior**: Edit `SYSTEM_PROMPT` constant or adjust `max_tokens` in `handleChat()`
|
||
|
|
|
||
|
|
**Update UI**: Modify HTML/CSS/JS in `getHTML()` function
|
||
|
|
|
||
|
|
## Gotchas
|
||
|
|
|
||
|
|
1. **No Node.js APIs** - Edge runtime only
|
||
|
|
2. **Single file** - Don't split into modules
|
||
|
|
3. **KV IDs** - Must update `wrangler.toml` after namespace creation
|
||
|
|
4. **Template literals** - Frontend uses escaped backticks (\`\`) for nested templates
|
||
|
|
5. **AI model** - Default `llama-3.1-8b-instruct`; use `llama-3.3-70b-instruct-fp8-fast` for quality
|