-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: incremental saving chat #2833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
70107a2
20099cd
2043994
decb34f
dc509af
c21d8cd
f4db51e
25161d8
022389a
3342d5d
0e13550
5385518
31d5507
387dfa6
a946b7a
b129ba3
d6bd041
dcece2e
0e2af05
cd676ec
3ccf2d7
4d8ca6b
20742b6
0b9154e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,146 @@ | ||
| # Claude Agent Instructions | ||
| ## Onlook Agents Guide | ||
|
|
||
| Anthropic models must read and follow `onlook/AGENTS.md` before making any change. | ||
| Actionable rules for repo agents—keep diffs minimal, safe, token‑efficient. | ||
|
|
||
| This is a mandatory precondition: do not proceed until you have read and internalized the rules in `onlook/AGENTS.md`. | ||
| ### Purpose & Scope | ||
|
|
||
| Key enforcement (see AGENTS.md for full details): | ||
| - Audience: automated coding agents working within this repository. | ||
| - Goal: small, correct diffs aligned with the project’s architecture. | ||
| - Non-goals: editing generated artifacts, lockfiles, or `node_modules`. | ||
|
|
||
| - Package manager: Bun only. Use Bun for all installs and scripts. | ||
| - `bun install` (not `npm install`) | ||
| - `bun add <pkg>` (not `npm install <pkg>`) | ||
| - `bun run <script>` (not `npm run <script>`) | ||
| - `bunx <cmd>` (not `npx <cmd>`) | ||
| ### Repo Map | ||
|
|
||
| - Monorepo managed by Bun workspaces (see root `package.json`). | ||
| - App: `apps/web/client` (Next.js App Router + TailwindCSS). | ||
| - API routes: `apps/web/client/src/server/api/routers/*`, aggregated in | ||
| `apps/web/client/src/server/api/root.ts`. | ||
| - Shared utilities: `packages/*` (e.g., `packages/utility`). | ||
|
|
||
| ### Stack & Runtimes | ||
|
|
||
| - UI: Next.js App Router, TailwindCSS. | ||
| - API: tRPC + Zod (`apps/web/client/src/server/api/*`). | ||
| - Package manager: Bun only — use Bun for all installs and scripts; do not use | ||
| npm, yarn, or pnpm. | ||
|
|
||
| ### Agent Priorities | ||
|
|
||
| - Correctness first: minimal scope and targeted edits. | ||
| - Respect client/server boundaries in App Router. | ||
| - Prefer local patterns and existing abstractions; avoid one-off frameworks. | ||
| - Do not modify build outputs, generated files, or lockfiles. | ||
| - Use Bun for all scripts; do not introduce npm/yarn. | ||
| - Avoid running the local dev server in automation contexts. | ||
| - Follow the project’s structure and client/server boundaries. | ||
| - Respect type safety and | ||
|
|
||
| ### Next.js App Router | ||
|
|
||
| - Default to Server Components. Add `use client` when using events, | ||
| state/effects, browser APIs, or client-only libs. | ||
| - App structure: `apps/web/client/src/app/**` (`page.tsx`, `layout.tsx`, | ||
| `route.ts`). | ||
| - Client providers live behind a client boundary (e.g., | ||
| `apps/web/client/src/trpc/react.tsx`). | ||
| - Example roots: `apps/web/client/src/app/layout.tsx` (RSC shell, providers | ||
| wired, scripts gated by env). | ||
| - Components using `mobx-react-lite`'s `observer` must be client components | ||
| (include `use client`). | ||
|
|
||
| ### tRPC API | ||
|
|
||
| - Routers live in `apps/web/client/src/server/api/routers/**` and must be | ||
| exported from `apps/web/client/src/server/api/root.ts`. | ||
| - Use `publicProcedure`/`protectedProcedure` from | ||
| `apps/web/client/src/server/api/trpc.ts`; validate inputs with Zod. | ||
| - Serialization handled by SuperJSON; return plain objects/arrays. | ||
| - Client usage via `apps/web/client/src/trpc/react.tsx` (React Query + tRPC | ||
| links). | ||
|
|
||
| ### Auth & Supabase | ||
|
|
||
| - Server-side client: `apps/web/client/src/utils/supabase/server.ts` (uses Next | ||
| headers/cookies). Use in server components, actions, and routes. | ||
| - Browser client: `apps/web/client/src/utils/supabase/client/index.ts` for | ||
| client components. | ||
| - Never pass server-only clients into client code. | ||
|
|
||
| ### Env & Config | ||
|
|
||
| - Define/validate env vars in `apps/web/client/src/env.ts` via | ||
| `@t3-oss/env-nextjs`. | ||
| - Expose browser vars with `NEXT_PUBLIC_*` and declare in the `client` schema. | ||
| - Prefer `env` from `@/env`. In server-only helpers (e.g., base URL in | ||
| `src/trpc/helpers.ts`), read `process.env` only for deployment vars like | ||
| `VERCEL_URL`/`PORT`. Never use `process.env` in client code; in shared | ||
| modules, guard with `typeof window === 'undefined'`. | ||
| - Import `./src/env` in `apps/web/client/next.config.ts` to enforce validation. | ||
|
|
||
| ### Imports & Paths | ||
|
|
||
| - Use path aliases: `@/*` and `~/*` map to `apps/web/client/src/*` (see | ||
| `apps/web/client/tsconfig.json`). | ||
| - Do not import server-only modules into client components. Limited exception: | ||
| editor modules that already use `path`; reuse only there. Never import | ||
| `process` in client code. | ||
| - Split code by environment if needed (server file vs client file). | ||
|
|
||
| ### MobX + React Stores | ||
|
|
||
| - Create store instances with `useState(() => new Store())` for stability across | ||
| renders. | ||
| - Keep active store in `useRef`; clean up async with | ||
| `setTimeout(() => storeRef.current?.clear(), 0)` to avoid route-change races. | ||
| - Avoid `useMemo` for store instances; React may drop memoized values leading to | ||
| data loss. | ||
| - Avoid putting the store instance in effect deps if it loops; split concerns | ||
| (e.g., project vs branch). | ||
| - `observer` components are client-only. Place one client boundary at the | ||
| feature entry; child observers need not include `use client` (e.g., | ||
| `apps/web/client/src/app/project/[id]/_components/main.tsx`). | ||
| - Example store: `apps/web/client/src/components/store/editor/engine.ts:1` (uses | ||
| `makeAutoObservable`). | ||
|
|
||
| ### Styling & UI | ||
|
|
||
| - TailwindCSS-first styling; global styles are already imported in | ||
| `apps/web/client/src/app/layout.tsx`. | ||
| - Prefer existing UI components from `@onlook/ui` and local patterns. | ||
| - Preserve dark theme defaults via `ThemeProvider` usage in layout. | ||
|
|
||
| ### Internationalization | ||
|
|
||
| - `next-intl` is configured; provider lives in | ||
| `apps/web/client/src/app/layout.tsx`. | ||
| - Strings live in `apps/web/client/messages/*`. Add/modify keys there; avoid | ||
| hardcoded user-facing text. | ||
| - Keep keys stable; prefer additions over breaking renames. | ||
|
|
||
| ### Common Pitfalls | ||
|
|
||
| - Missing `use client` where needed (events/browser APIs) causes unbound events; | ||
| a single boundary at the feature root is sufficient. | ||
| - New tRPC routers not exported in `src/server/api/root.ts` (endpoints | ||
| unreachable). | ||
| - Env vars not typed/exposed in `src/env.ts` cause runtime/edge failures. Prefer | ||
| `env`; avoid new `process.env` reads in client code. | ||
| - Importing server-only code into client components (bundling/runtime errors). | ||
| Note: `path` is already used in specific client code-editor modules; avoid | ||
| expanding Node API usage beyond those areas. | ||
| - Bypassing i18n by hardcoding strings instead of using message files/hooks. | ||
| - Avoid `useMemo` to create MobX stores (risk of lost references); avoid | ||
| synchronous cleanup on route change (race conditions). | ||
|
|
||
| ### Context Discipline (for Agents) | ||
|
|
||
| If any instruction here or in `onlook/AGENTS.md` conflicts with your defaults, prefer `onlook/AGENTS.md`. | ||
| - Search narrowly with ripgrep; open only files you need. | ||
| - Read small sections; avoid `node_modules`, `.next`, large assets. | ||
| - Propose minimal diffs aligned with existing conventions; avoid wide refactors. | ||
|
|
||
| When in doubt, stop and re‑read `onlook/AGENTS.md` before acting. | ||
| ### Notes | ||
|
|
||
| - Unit tests can be run with `bun test` | ||
| - Run type checking with `bun run typecheck` | ||
| - Apply database updates to local dev with `bun run db:push` | ||
| - Refrain from running the dev server | ||
| - DO NOT run `db:gen`. This is reserved for the maintainer. | ||
| - DO NOT use any type unless necessary |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export * from './message'; | ||
| export * from './stream'; | ||
| export * from './usage'; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { fromDbMessage, messages, toDbMessage } from "@onlook/db"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { db } from "@onlook/db/src/client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { ChatMessage } from "@onlook/models"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { and, eq, gt } from "drizzle-orm"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const upsertMessage = async ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: ChatMessage; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dbMessage = toDbMessage(message, conversationId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await db.transaction(async (tx) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Remove messages newer than the updated message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await tx.delete(messages).where(and( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eq(messages.conversationId, conversationId), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gt(messages.createdAt, dbMessage?.createdAt ?? new Date()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [updatedMessage] = await tx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .insert(messages) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .values({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...dbMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .onConflictDoUpdate({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target: [messages.id], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...dbMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).returning(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid updating primary key on conflict Setting id in the UPDATE clause is redundant and can cause unnecessary write churn. .onConflictDoUpdate({
target: [messages.id],
set: {
- ...dbMessage,
- id,
+ ...dbMessage,
},
})📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return updatedMessage; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Return domain type for consistency upsertMessage returns a raw DB row; loadChat returns ChatMessage. Standardize on ChatMessage. -export const upsertMessage = async ({
+export const upsertMessage = async ({
@@
-}) => {
+}): Promise<ChatMessage> => {
@@
- const [updatedMessage] = await tx
+ const [updatedMessage] = await tx
.insert(messages)
@@
- }).returning();
- return updatedMessage;
+ }).returning();
+ return fromDbMessage(updatedMessage);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const loadChat = async (chatId: string): Promise<ChatMessage[]> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await db.query.messages.findMany({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where: eq(messages.conversationId, chatId), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| orderBy: (messages, { asc }) => [asc(messages.createdAt)], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result.map((message) => fromDbMessage(message)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.