feat: add better provider support, icons, regen, and a lot more
This commit is contained in:
@@ -1,19 +1,34 @@
|
||||
# AGENTS.md
|
||||
# Veridian AGENTS.md
|
||||
|
||||
This file contains guidelines and commands for agentic coding agents working in
|
||||
the Veridian repository.
|
||||
**Nuxt 4 AI chat platform with Triplit database + better-auth**
|
||||
|
||||
## Project Overview
|
||||
## Architecture Overview
|
||||
|
||||
Veridian is a Nuxt 4 application built with TypeScript, using Triplit with
|
||||
better-auth for authentication, and UnoCSS for styling. The app is an agnetic
|
||||
chat platform that is meant to provide a well rounded experience for interacting
|
||||
with LLMs as well as providing a way to manage agents and provide helpful tools
|
||||
like RAG (retrieval augmented generation) and web scraping/search.
|
||||
```
|
||||
app/ # Nuxt 4 application (SSR)
|
||||
├── components/ # Vue components (Sidenav, Message, Settings, etc.)
|
||||
├── composables/ # Shared state (useChat, useAuth, useModels, etc.)
|
||||
├── layouts/ # Auth and default layouts
|
||||
├── middleware/ # Global auth middleware
|
||||
├── pages/ # File-based routing (/, /auth/*, /agent/*)
|
||||
├── plugins/ # Auth plugins (client/server), remark markdown
|
||||
├── types/ # TypeScript interfaces
|
||||
└── utils/ # Crypto, model-mapping, search utilities
|
||||
|
||||
You should be connected to the Nuxt docs MCP server, if you need to reference
|
||||
the documentation or are unsure on how to implement something, consult the
|
||||
docs.
|
||||
server/ # Server-side API routes
|
||||
└── api/
|
||||
├── auth/ # Better-auth handler
|
||||
├── chat/ # Generation, cancel endpoints
|
||||
└── provider/ # Model fetching from providers
|
||||
|
||||
triplit/ # Database
|
||||
├── schema.ts # Full schema with auth + app collections
|
||||
└── auth-schema.ts # Auth collections (users, sessions, accounts)
|
||||
|
||||
lib/ # Shared utilities
|
||||
├── auth.ts # Better-auth server config
|
||||
└── auth-client.ts # Better-auth client
|
||||
```
|
||||
|
||||
## Core Commands
|
||||
|
||||
@@ -26,215 +41,172 @@ database commands.
|
||||
|
||||
- `bunx triplit schema push` - Push schema changes to database
|
||||
|
||||
## Tech Stack & Dependencies
|
||||
## Code Style
|
||||
|
||||
- **Framework**: Nuxt 4 (SSR enabled)
|
||||
- **Language**: TypeScript with strict configuration
|
||||
- **Database**: Triplit (next generation fullstack syncing database, sort of
|
||||
like convex)
|
||||
- **Auth**: better-auth with email/password and social providers
|
||||
- **Styling**: UnoCSS with presetMini
|
||||
- **Icons**: @nuxt/icon with Iconify, use Myna UI Icons
|
||||
- **Indentation**: 4 spaces (no tabs)
|
||||
- **Semicolons**: Always required
|
||||
- **Equality**: Strict equality only (`===`, `!==`)
|
||||
- **Components**: PascalCase, never self-closing tags
|
||||
- **Functions**: camelCase for functions, PascalCase for constructors/classes
|
||||
- **Constants**: SCREAMING_SNAKE_CASE for constants, camelCase for const values
|
||||
- **Files**: kebab-case for files, PascalCase for Vue components
|
||||
- **State**: `useState('prefix:name', () => default)` for reactive state
|
||||
|
||||
## Code Style Guidelines
|
||||
## Imports Order
|
||||
|
||||
**Follow the LLVM golden rule: If you are extending, enhancing, or bug fixing
|
||||
already implemented code, use the style that is already being used so that
|
||||
the source is uniform and easy to follow.**
|
||||
Group imports in this order (alphabetical within groups):
|
||||
1. Type imports (`types/`)
|
||||
2. Dependency imports (npm packages)
|
||||
3. Triplit imports (`#triplit/`, `@triplit/`)
|
||||
4. Vue/Nuxt imports (`vue`, `#app`, `~~/`, `~/`, `@/`)
|
||||
5. Local imports (`lib/`, `server/`)
|
||||
|
||||
- **Always end lines with a semicolon regardless of surrounding code**
|
||||
- Use 4 spaces for indentation
|
||||
- Use consistent naming conventions for variables, functions, and constants
|
||||
- Always use strict equality checks (`===` and `!==`) instead of loose equality
|
||||
checks (`==` and `!=`)
|
||||
- Prefer using explicity equality checks over truthy/falsy checks
|
||||
e.g., prefer `if (value !== null)` instead of `if (value)`
|
||||
- Always check existing code patterns before implementing new features
|
||||
- Follow existing component and composable structures
|
||||
- Use proper TypeScript types for all function parameters and returns
|
||||
- Implement proper cleanup in composables (e.g., useClickOutside)
|
||||
- Prefer reactive state management over direct DOM manipulation
|
||||
- Keep components focused and single-purpose
|
||||
- Use semantic HTML elements where appropriate
|
||||
|
||||
### General Structure
|
||||
|
||||
- Use Nuxt's app directory structure (`app/`, `server/`, `lib/`)
|
||||
- Auth configuration in `lib/auth.ts`
|
||||
- Server API routes go in `server/api/`
|
||||
- Composables go in `app/composables/`
|
||||
- Database schema in `db/schema.ts`
|
||||
- Database migrations in `db/migrations/`
|
||||
|
||||
### TypeScript Guidelines
|
||||
|
||||
- Use strict TypeScript with no implicit any
|
||||
- Export types and interfaces explicitly
|
||||
- Use `computed()` and `ref()` from Vue 3 reactivity system
|
||||
- Type API responses and database models
|
||||
|
||||
### Vue Components
|
||||
|
||||
- **Never use self-closing tags, excluding images, br, hr, and input**
|
||||
- Use `<script setup lang="ts">` syntax
|
||||
- Strongly prefer composition API over options API
|
||||
- Prefer composables for shared state management
|
||||
- Maintain consistent 4-space indentation
|
||||
- Use PascalCase for component file names
|
||||
|
||||
### CSS/Styling
|
||||
|
||||
- Use UnoCSS utility classes exclusively
|
||||
- Never use margin for spacing, always prefer flexbox or grid
|
||||
- Prefer inline utility classes over custom CSS
|
||||
- Use semantic color variables: `--color-base`, `--color-neutral`, `--color-accent`, `--color-subtle`
|
||||
If you need to add new color variables, they are located in `app/assets/css/base.css`,
|
||||
along with a general CSS reset.
|
||||
- Apply consistent spacing and layout patterns
|
||||
- Use responsive prefixes only when necessary
|
||||
|
||||
### Imports
|
||||
|
||||
- Order imports: Vue/Nuxt imports first, then local files, then dependencies
|
||||
- `~/` is an alias for `app/` and `~~/` is an alias for the root of the project
|
||||
- Avoid wildcard imports
|
||||
- Keep imports sorted alphabetically within groups
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- **Page**: camelCase
|
||||
- **Composables**: camelCase
|
||||
- **Components**: PascalCase when referenced in templates
|
||||
- **Functions/Variables**: camelCase
|
||||
- **Constants**: UPPER_SNAKE_CASE for environment variables only
|
||||
- **Database tables**: snake_case
|
||||
- **API routes**: kebab-case path segments
|
||||
|
||||
### Database Patterns
|
||||
|
||||
- Use Triplit for database operations
|
||||
- Export all schemas from `triplit/schema.ts`
|
||||
- Use environment variables for database credentials
|
||||
- Triplit schemas are defined in `triplit/schema.ts` and pushed using `bunx triplit schema push`
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Use proper TypeScript error types
|
||||
- Implement try-catch blocks for database operations
|
||||
- Provide user-friendly error messages in API responses
|
||||
- Log errors appropriately without exposing sensitive data
|
||||
|
||||
### Performance Guidelines
|
||||
|
||||
- Leverage Nuxt's auto-imports and code splitting
|
||||
- Use `useState()` for shared state across components
|
||||
- Implement proper loading states with async operations
|
||||
- Optimize database queries and use indexes where needed
|
||||
|
||||
## Authentication Implementation
|
||||
|
||||
The app uses better-auth with:
|
||||
|
||||
- Email/password authentication (configurable via env vars)
|
||||
- Triplit adapter for authentication
|
||||
- Session management through cookies
|
||||
- Auth plugins in `app/plugins/` for client/server initialization
|
||||
- Global auth middleware in `app/middleware/auth.global.ts`
|
||||
|
||||
All users must be signed in to view pages aside from authentication pages.
|
||||
(located in `app/pages/auth/`), you do not need to check if session or user
|
||||
objects are null or undefined, use the non-null assertion operator (`!`) where
|
||||
necessary. In API routes, you can use `protectRoute` to guarantee that the
|
||||
user must be authenticated.
|
||||
|
||||
## File Organization
|
||||
|
||||
```
|
||||
|-- app/ # Application code
|
||||
│ |-- components/ # Vue components
|
||||
│ |-- composables/ # Reuseable composition functions
|
||||
│ |-- layouts/ # Layout components
|
||||
│ |-- middleware/ # Route middleware
|
||||
│ |-- pages/ # File-based routing
|
||||
│ \-- plugins/ # Vue/Nuxt plugins
|
||||
|-- server/ # Server-side code
|
||||
│ \-- api/ # API routes
|
||||
|-- triplit/ # Database related files
|
||||
│ |-- schema.ts # Database schema
|
||||
│ |-- client.ts # Interacts with the database on the client
|
||||
│ \-- server.ts # Interacts with the database on the server
|
||||
\-- lib/ # Shared utilities
|
||||
```typescript
|
||||
// Example import order
|
||||
import type { Result, Ok, Err } from '~~/types/result';
|
||||
import type { Entity } from '@triplit/client';
|
||||
import type schema from '#triplit/schema';
|
||||
import type { Foo } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useFeature } from '~/composables/useFeature';
|
||||
import { decrypt } from '~/utils/crypto';
|
||||
import { authClient } from '~~/lib/auth-client';
|
||||
```
|
||||
|
||||
## Testing
|
||||
## Error Handling
|
||||
|
||||
Currently no test framework is configured, and tests are not currently a
|
||||
requirement.
|
||||
Avoid using `throw` statements in your code.
|
||||
|
||||
## Environment Variables
|
||||
Use the `Result<T, E>` type with `Ok()` and `Err()` factory functions:
|
||||
|
||||
Key environment variables:
|
||||
```typescript
|
||||
import { type Result, Ok, Err } from '~~/types/result';
|
||||
|
||||
- `TRIPLIT_SERVICE_TOKEN` - Triplit service token (admin token. **SECRET**)
|
||||
- `NUXT_TRIPLIT_ANON_TOKEN` - Triplit anonymous token (for anonymous access)
|
||||
- `BETTER_AUTH_SECRET` - Authentication secret (for better-auth)
|
||||
- `EXTERNAL_JWT_SECRET` - Will always be BETTER_AUTH_SECRET (used for
|
||||
verifying JWT tokens from better-auth)
|
||||
- `NUXT_PUBLIC_TRIPLIT_URL` - Triplit server URL
|
||||
const myFunction(): Result<ReturnType, ErrorType> {
|
||||
if (error) {
|
||||
return Err(ErrorType.SpecificError);
|
||||
}
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
## Common Patterns
|
||||
// Usage
|
||||
const result = myFunction();
|
||||
if (result.ok === false) {
|
||||
console.error(result.error);
|
||||
return;
|
||||
}
|
||||
processData(result.data);
|
||||
```
|
||||
|
||||
### Composables Pattern
|
||||
## Composables Pattern
|
||||
|
||||
All composables return reactive state + actions:
|
||||
|
||||
```typescript
|
||||
export const useFeature = () => {
|
||||
const state = useState<Type>('feature:state', () => defaultValue)
|
||||
const computed = computed(() => /* logic */)
|
||||
|
||||
const actions = {
|
||||
// methods
|
||||
}
|
||||
|
||||
return { state, computed, ...actions }
|
||||
}
|
||||
const state = useState('feature:state', () => defaultValue);
|
||||
const computedValue = computed(() => /* logic */);
|
||||
const actions = {
|
||||
async doSomething() {
|
||||
// implementation
|
||||
},
|
||||
};
|
||||
return { state, computedValue, ...actions };
|
||||
};
|
||||
```
|
||||
|
||||
### API Route Pattern
|
||||
|
||||
API routes are defined in `server/api/` and are exported as a single object.
|
||||
|
||||
File names contain information about the route, e.g. `user.get.ts` specifies a
|
||||
GET route to `/user`. This applies for all HTTP methods.
|
||||
## API Route Pattern
|
||||
|
||||
```typescript
|
||||
// an example GET route to /user
|
||||
// File: server/api/endpoint.method.ts
|
||||
import { protectRoute } from '~/server/utils/protect';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
await protectRoute(event);
|
||||
// implementation
|
||||
return { success: true, data: result };
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Error description",
|
||||
});
|
||||
}
|
||||
return { /* response */ };
|
||||
});
|
||||
```
|
||||
|
||||
## Database Query Pattern (Triplit)
|
||||
|
||||
```typescript
|
||||
import { users } from "~~/db/schema";
|
||||
// Client queries use useQuery() with auto-includes
|
||||
useQuery('collection', triplit, triplit.query('collection').Include('relation'));
|
||||
|
||||
// an example POST route to /user
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
|
||||
const { email, name, password } = body;
|
||||
|
||||
const [user] = await db
|
||||
.insert(users)
|
||||
.values({ email, name, password })
|
||||
.returning();
|
||||
|
||||
return user;
|
||||
});
|
||||
// Server-side
|
||||
import { httpClient } from '~~/server/lib/triplit';
|
||||
await httpClient.fetchOne(httpClient.query('collection').Where('id', '=', providerId));
|
||||
```
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
1. **Login**: Client uses `authClient.signIn.email()` → derives encryption key from password
|
||||
2. **Session**: Better-auth creates session token → stored in cookie
|
||||
3. **Triplit**: Session token passed to `triplit.startSession(token)` for DB access
|
||||
4. **API Keys**: Encrypted client-side with AES-GCM (key derived from password + userId)
|
||||
|
||||
**Key files**: `lib/auth.ts`, `lib/auth-client.ts`, `app/plugins/auth.*.ts`, `app/middleware/auth.global.ts`
|
||||
|
||||
## Styling System
|
||||
|
||||
**UnoCSS** with semantic color variables in `app/assets/css/base.css`:
|
||||
- `--color-accent`, `--color-neutral`, `--color-text`, `--color-muted`
|
||||
- `--color-highlight` (for borders/hover states)
|
||||
- Theme-aware: `:root.dark` / `:root.light` with `color-mix()`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `TRIPLIT_SERVICE_TOKEN` | Admin DB access |
|
||||
| `NUXT_TRIPLIT_ANON_TOKEN` | Anonymous DB access |
|
||||
| `BETTER_AUTH_SECRET` | Auth encryption |
|
||||
| `NUXT_PUBLIC_TRIPLIT_URL` | DB server URL |
|
||||
|
||||
## Key Composables
|
||||
|
||||
| Composable | Purpose |
|
||||
|------------|---------|
|
||||
| `useAuth()` | Session/user state, signIn/signOut |
|
||||
| `useChat(id)` | Send/regenerate messages, create topics |
|
||||
| `useModels()` | Providers + models with auto-subscription |
|
||||
| `useAgents()` | User's agents with topics |
|
||||
| `useSidebar()` | Sidebar state + resize |
|
||||
| `useTheme()` | Accent/neutral theme cookies |
|
||||
| `useSettings()` | Settings dialog state |
|
||||
|
||||
## Key Routes
|
||||
|
||||
| Route | File |
|
||||
|-------|------|
|
||||
| `/` | `app/pages/index.vue` |
|
||||
| `/auth/login` | `app/pages/auth/login.vue` |
|
||||
| `/auth/register` | `app/pages/auth/register.vue` |
|
||||
| `/agent/:id` | `app/pages/agent/[id]/index.vue` |
|
||||
| `/agent/:id/topic/:topicId` | `app/pages/agent/[id]/topic/[topicId].vue` |
|
||||
| `/agent/:id/profile` | `app/pages/agent/[id]/profile.vue` |
|
||||
|
||||
## Server API Routes
|
||||
|
||||
| Endpoint | File |
|
||||
|----------|------|
|
||||
| `POST /api/chat/generate` | `server/api/chat/generate.post.ts` |
|
||||
| `POST /api/chat/cancel/:id` | `server/api/chat/cancel/[generationId].post.ts` |
|
||||
| `POST /api/provider/:id/models` | `server/api/provider/[providerId]/models.post.ts` |
|
||||
| `* /api/auth/*` | `server/api/auth/[...all].ts` |
|
||||
|
||||
## Known Issues (from BUGS.md)
|
||||
|
||||
1. Triplit occasionally makes duplicate connections (race condition)
|
||||
2. Sidebar hover animation occasionally glitches on agent routes
|
||||
3. Theme switcher + sidebar interaction bug
|
||||
|
||||
## Development Notes
|
||||
|
||||
- **Never run dev server as agent** - just complete tasks and signal done
|
||||
- Always run `bunx triplit schema push` after schema changes
|
||||
- Use `protectRoute()` in all API routes for auth
|
||||
- All UI state that persists → use cookies (`useCookie()`)
|
||||
- Real-time data → Triplit subscriptions via `useQuery()`
|
||||
- API keys → encrypted client-side before DB storage
|
||||
|
||||
Reference in New Issue
Block a user