feat: add better provider support, icons, regen, and a lot more

This commit is contained in:
Zoe
2026-02-12 14:56:13 +00:00
parent d5a5945c03
commit d29f95bacf
124 changed files with 6374 additions and 1861 deletions
+169 -197
View File
@@ -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